

# 使用函数在边缘进行自定义
<a name="edge-functions"></a>

借助 Amazon CloudFront，您可以编写自己的代码来自定义 CloudFront 分配如何处理 HTTP 请求和响应。该代码靠近查看者（用户）运行，以最大限度地减少延迟，而且您无需管理服务器或其他基础设施。您可以编写代码以操作通过 CloudFront 的请求和响应、执行基本身份验证和授权、在边缘生成 HTTP 响应等。

您编写并附加到 CloudFront 分配的代码称为*边缘函数*。CloudFront 提供了两种编写和管理边缘函数的方法：

**CloudFront Functions**  
您可以在 JavaScript 中编写轻量级函数，以实现大规模、注重延迟的 CDN 定制设置。CloudFront Functions 运行时环境提供亚毫秒的启动时间，可立即扩展，从而每秒处理数百万个请求，并且非常安全。CloudFront Functions 是 CloudFront 的原生功能，这意味着您可以完全在 CloudFront 中构建、测试和部署代码。

**Lambda@Edge**  
Lambda@Edge 是 [AWS Lambda](https://aws.amazon.com/lambda/) 的扩展，可为复杂的函数提供强大而灵活的计算功能，并带来更接近您的查看器的完整应用程序逻辑，并且具有高度安全性。Lambda@Edge 函数在 Node.js 或 Python 运行时环境中运行。您将函数发布到单个AWS 区域，当您关联该函数与 CloudFront 分配时，Lambda@Edge 可自动在全球复制您的代码。

如果您在 CloudFront 上运行 AWS WAF，则可以为 CloudFront Functions 和 Lambda@Edge 使用 AWS WAF 插入标头。这适用于查看器以及源请求和源响应。

**Topics**
+ [CloudFront Functions 与 Lambda@Edge 之间的区别](edge-functions-choosing.md)
+ [使用 CloudFront Functions 在边缘进行自定义](cloudfront-functions.md)
+ [使用 CloudFront 连接函数进行自定义](customize-connections-validation-with-connection-functions.md)
+ [使用 Lambda@Edge 在边缘进行自定义](lambda-at-the-edge.md)
+ [边缘函数的限制](edge-functions-restrictions.md)

# CloudFront Functions 与 Lambda@Edge 之间的区别
<a name="edge-functions-choosing"></a>

CloudFront Functions 和 Lambda@Edge 都提供了一种运行代码以响应 CloudFront 事件的方法。

CloudFront Functions 非常适合用于以下应用场景的轻量级短期运行的函数：
+ **缓存键标准化** – 转变 HTTP 请求属性（标头、查询字符串、Cookie，甚至是 URL 路径）以创建合适的[缓存键](understanding-the-cache-key.md)，这可以提高您的缓存命中率。
+ **标头操作** – 在请求或响应中插入、修改或删除 HTTP 标头。例如，您可以为每个请求添加 `True-Client-IP` 标头。
+ **URL 重定向或重写** – 根据请求中的信息将查看器重定向到其他页面，或者将所有请求从一个路径重写到另一个路径。
+ **请求授权** – 通过检查授权标头或其他请求元数据来验证哈希授权令牌，例如 JSON Web 令牌（JWT，JSON Web Token）。

要开始使用 CloudFront Functions，请参阅[使用 CloudFront Functions 在边缘进行自定义](cloudfront-functions.md)。

Lambda @Edge 非常适合以下应用场景：
+ 需要几毫秒或更长时间才能完成的函数
+ 需要可调节 CPU 或内存的函数
+ 依赖于第三方库（包括 AWS SDK，用于与其他AWS 服务服务集成）的函数
+ 需要网络访问权限以使用外部服务进行处理的函数
+ 需要文件系统访问权限或访问 HTTP 请求正文的函数

要开始使用 Lambda@Edge，请参阅[使用 Lambda@Edge 在边缘进行自定义](lambda-at-the-edge.md)。

为了帮助您根据自己的应用场景来选择选项，请使用下表来了解 CloudFront Functions 与 Lambda @Edge 之间的区别。有关适用于源修改辅助方法的差异的信息，请参阅[在 CloudFront Functions 和 Lambda@Edge 之间进行选择](helper-functions-origin-modification.md#origin-modification-considerations)。


|  | CloudFront Functions | Lambda@Edge | 
| --- | --- | --- | 
| 编程语言 | JavaScript（兼容 ECMAScript 5.1） | Node.js 和 Python | 
| 事件来源 |  [\[See the AWS documentation website for more details\]](http://docs.aws.amazon.com/zh_cn/AmazonCloudFront/latest/DeveloperGuide/edge-functions-choosing.html)  |  [\[See the AWS documentation website for more details\]](http://docs.aws.amazon.com/zh_cn/AmazonCloudFront/latest/DeveloperGuide/edge-functions-choosing.html)  | 
|  支持 [Amazon CloudFront KeyValueStore](kvs-with-functions.md)  |  是 CloudFront KeyValueStore 仅支持 [JavaScript 运行时 2.0](functions-javascript-runtime-20.md)  |  否  | 
| 比例尺 | 每秒最多数百万个请求 | 每个区域每秒最多 10000 个请求 | 
| 函数持续时间 | 亚毫秒 |  最长 30 秒（查看器请求和查看器响应） 最长 30 秒（源请求和源响应）  | 
|  最大函数内存大小  | 2 MB |  128 MB（查看器请求和查看器响应） 10240 MB（10 GB）（源请求和源响应） 有关更多信息，请参阅 [有关 Lambda@Edge 的配额](cloudfront-limits.md#limits-lambda-at-edge)。  | 
| 函数代码和包含的库的最大大小 | 10 KB |  50 MB（查看器请求和查看器响应） 50 MB（源请求和源响应）  | 
| 网络访问 | 否 | 是 | 
| 文件系统访问 | 否 | 是 | 
| 访问请求正文 | 否 | 是 | 
| 访问地理位置和设备数据 | 是 |  否（查看器请求和查看器响应） 是（源请求和源响应）  | 
| 可以完全在 CloudFront 内构建和测试 | 是 | 否 | 
| 函数日志记录和指标 | 是 | 是 | 

# 使用 CloudFront Functions 在边缘进行自定义
<a name="cloudfront-functions"></a>

借助 CloudFront Functions，您可以在 JavaScript 中编写轻量级函数，以实现大规模、延迟敏感的 CDN 自定义。您的函数可以操作通过 CloudFront 的请求和响应、执行基本身份验证和授权、在边缘生成 HTTP 响应等。CloudFront Functions 运行时环境提供亚毫秒的启动时间，可立即扩展，从而每秒处理数百万个请求，并且非常安全。CloudFront Functions 是 CloudFront 的原生功能，这意味着您可以完全在 CloudFront 中构建、测试和部署代码。

在将 CloudFront 函数与 CloudFront 分配相关联时，CloudFront 在 CloudFront 边缘站点中截获请求和响应并将它们传递到您的函数。当发生以下事件时，您可以调用 CloudFront Functions：
+ 在 CloudFront 收到查看器的请求时 (查看器请求)
+ 在 CloudFront 将响应返回到查看器之前（查看器响应）
+ 在 TLS 连接建立过程中（连接请求）：目前可用于双向 TLS（mTLS）连接

有关 CloudFront Functions 的更多信息，请参阅以下主题：

**Topics**
+ [教程：使用 CloudFront Functions 创建简单函数](functions-tutorial.md)
+ [教程：创建包含键值的 CloudFront 函数](functions-tutorial-kvs.md)
+ [编写函数代码](writing-function-code.md)
+ [创建函数](create-function.md)
+ [测试函数](test-function.md)
+ [更新函数](update-function.md)
+ [发布函数](publish-function.md)
+ [将函数与分配关联](associate-function.md)
+ [Amazon CloudFront KeyValueStore](kvs-with-functions.md)

# 教程：使用 CloudFront Functions 创建简单函数
<a name="functions-tutorial"></a>

本教程介绍如何开始使用 CloudFront Functions。您可以创建一个简单的函数，以便将查看器重定向到其他 URL，并返回自定义响应标头。

**Contents**
+ [先决条件](#functions-tutorial-prerequisites)
+ [创建函数](#functions-tutorial-create)
+ [验证函数](#functions-tutorial-verify)

## 先决条件
<a name="functions-tutorial-prerequisites"></a>

要使用 CloudFront Functions，您需要一个 CloudFront 分配。如果您还有分配，请参阅[开始使用 CloudFront 标准分配](GettingStarted.SimpleDistribution.md)。

## 创建函数
<a name="functions-tutorial-create"></a>

您可以使用 CloudFront 控制台创建一个简单函数，用于将查看器重定向到其他 URL，并返回自定义响应标头。

**创建 CloudFront 函数**

1. 登录 AWS 管理控制台，并通过以下网址打开 CloudFront 控制台：[https://console.aws.amazon.com/cloudfront/v4/home](https://console.aws.amazon.com/cloudfront/v4/home)。

1. 在导航窗格中，选择**函数**，然后选择**创建函数**。

1. 在**创建函数**页面上，在**名称**中输入函数名称，例如 *MyFunctionName*。

1. （可选）对于**描述**，输入函数描述，例如**Simple test function**。

1. 对于**运行时**，请保留默认选定的 JavaScript 版本。

1. 选择**创建函数**。

1. 复制以下函数代码。此函数代码将查看器重定向到其他 URL，并返回自定义响应标题。

   ```
   function handler(event) {
       // NOTE: This example function is for a viewer request event trigger. 
       // Choose viewer request for event trigger when you associate this function with a distribution. 
       var response = {
           statusCode: 302,
           statusDescription: 'Found',
           headers: {
               'cloudfront-functions': { value: 'generated-by-CloudFront-Functions' },
               'location': { value: 'https://aws.amazon.com/cloudfront/' }
           }
       };
       return response;
   }
   ```

1. 对于**函数代码**，将代码粘贴到代码编辑器中以替换默认代码。

1. 选择**保存更改**。

1. （可选）您可以在发布函数之前对其进行测试。本教程不介绍如何测试函数。有关更多信息，请参阅 [测试函数](test-function.md)。

1. 选择**发布**选项卡，然后选择**发布函数**。您*必须* 先发布该函数，然后才能将其与 CloudFront 分配相关联。

1. 接下来，您可以将函数与分配或缓存行为相关联。在 *MyFunctionName* 页面上，选择**发布**选项卡。
**警告**  
在以下步骤中，选择用于测试的分配或缓存行为。不要将此演示函数与生产环境中使用的分配或缓存行为相关联。

1. 选择**添加关联**。

1. 在**关联**对话框中，选择分配和/或缓存行为。对于**事件类型**，请保留默认值。

1. 选择**添加关联**。

   **关联的分配**表显示了关联的分配。

1. 等待几分钟，以便相关的分配完成部署。要检查分配的状态，请在**关联的分配**表中选择分配，然后选择**查看分配**。

   当分配的状态为 **Deployed**（已部署）时，您即可验证函数是否正常工作。

## 验证函数
<a name="functions-tutorial-verify"></a>

部署该函数后，您可以验证它是否适用于分配。

**验证函数**

1. 在您的 Web 浏览器中，导航到您的分配的域名（例如 `https://d111111abcdef8.cloudfront.net`）。

   该函数返回到浏览器的重定向，因此浏览器会自动转到 `https://aws.amazon.com/cloudfront/`。

1. 在命令行窗口中，您可以使用 **curl** 等工具，向分配的域名发送请求。

   ```
   curl -v https://d111111abcdef8.cloudfront.net/
   ```

   在响应中，您可以看到函数添加的重定向响应（`302 Found`）和自定义响应标头。您的响应可能类似于下例。  
**Example**  

   ```
   curl -v https://d111111abcdef8.cloudfront.net/
   > GET / HTTP/1.1
   > Host: d111111abcdef8.cloudfront.net
   > User-Agent: curl/7.64.1
   > Accept: */*
   >
   < HTTP/1.1 302 Found
   < Server: CloudFront
   < Date: Tue, 16 Mar 2021 18:50:48 GMT
   < Content-Length: 0
   < Connection: keep-alive
   < Location: https://aws.amazon.com/cloudfront/
   < Cloudfront-Functions: generated-by-CloudFront-Functions
   < X-Cache: FunctionGeneratedResponse from cloudfront
   < Via: 1.1 3035b31bddaf14eded329f8d22cf188c.cloudfront.net (CloudFront)
   < X-Amz-Cf-Pop: PHX50-C2
   < X-Amz-Cf-Id: ULZdIz6j43uGBlXyob_JctF9x7CCbwpNniiMlmNbmwzH1YWP9FsEHg==
   ```

# 教程：创建包含键值的 CloudFront 函数
<a name="functions-tutorial-kvs"></a>

本教程向您演示如何在 CloudFront 函数中包含键值。键值是键值对的一部分。您可以在函数代码中包含名称（来自键值对）。函数运行时，CloudFront 会将该名称替换为相应的值。

键值对是存储在键值存储中的变量。当您在函数中使用键（而不是硬编码值）时，您的函数会更加灵活。您可以更改键的值，而无需部署代码更改。键值对也可以减小函数的大小。有关更多信息，请参阅 [Amazon CloudFront KeyValueStore](kvs-with-functions.md)。

**Contents**
+ [先决条件](#functions-kvs-tutorial-prerequisites)
+ [创建键值存储](#functions-kvs-tutorial-kvs-step)
+ [向键值存储添加键值对](#add-key-value-pairs-to-store)
+ [将键值存储与函数相关联](#functions-kvs-tutorial-functions-step)
+ [测试并发布函数代码](#test-and-publish-function-code)

## 先决条件
<a name="functions-kvs-tutorial-prerequisites"></a>

如果您不熟悉 CloudFront Functions 和键值存储，建议您按照[教程：使用 CloudFront Functions 创建简单函数](functions-tutorial.md)中的教程操作。

完成该教程后，您可以按照本教程来扩展您创建的函数。在本教程中，我们建议您首先创建键值存储。

## 创建键值存储
<a name="functions-kvs-tutorial-kvs-step"></a>

首先，创建用于您的函数的键值存储。

**创建键值存储**

1. 规划要包含在函数中的键值对。记下键名称。您要在某个函数中使用的键值对，必须都位于单个键值存储中。

1. 确定工作顺序。可采用以下两种方法来继续操作：
   + 创建键值存储，并将键值对添加到存储中。然后创建（或修改）函数并纳入键名称。
   + 或者，创建（或修改）函数并纳入要使用的键名称。然后创建键值存储，并添加键值对。

1. 登录 AWS 管理控制台，并通过以下网址打开 CloudFront 控制台：[https://console.aws.amazon.com/cloudfront/v4/home](https://console.aws.amazon.com/cloudfront/v4/home)。

1. 在导航窗格中，选择**函数**，然后选择 **KeyValueStores** 选项卡。

1. 选择**创建 KeyValueStore** 并输入以下字段：
   + 输入存储的名称和（可选）描述。
   + 将 **S3 URI** 留空。在本教程中，您将手动输入键值对。

1. 选择**创建**。此时将显示新键值存储的详细信息页面。此页面包含一个**键值对**部分，该部分目前为空。

## 向键值存储添加键值对
<a name="add-key-value-pairs-to-store"></a>

接下来，手动将键值对列表添加到您之前创建的键值存储中。

**向键值存储添加键值对**

1. 在**键值对**部分，选择**添加键值对**。

1. 选择**添加对**，然后输入键和值。选中复选标记以确认您的更改，然后重复此步骤来添加更多键值对。

1. 完成后，选择**保存更改**，将键值对保存到键值存储中。在确认对话框中，选择**完成**。

现在，您拥有了一个包含一组键值对的键值存储。



## 将键值存储与函数相关联
<a name="functions-kvs-tutorial-functions-step"></a>

现在，您已经创建了键值存储。并且您已经创建或修改了一个包含键值存储中的键名称的函数。现在可以将键值存储与该函数进行关联。您可以从函数内部创建该关联。

**将键值存储与函数相关联**

1. 在导航窗格中，选择**函数**。默认情况下，**函数**选项卡显示在顶部。

1. 选择函数名称，在 **Associated KeyValueStore** 部分中，选择 **关联现有 KeyValueStore**。

1. 选择键值存储并选择**关联 KeyValueStore**。

**注意**  
每个函数只能关联一个键值存储。

## 测试并发布函数代码
<a name="test-and-publish-function-code"></a>

将键值存储与您的函数关联后，您可以测试并发布函数代码。每次修改函数代码（包括执行以下操作）时，都应始终对其进行测试：
+ 将键值存储与函数相关联。
+ 修改函数及其键值存储以包含新的键值对。
+ 更改键值对的值。

**测试并发布函数代码**

1. 有关如何测试函数的信息，请参阅[测试函数](test-function.md)。确保您选择在 `DEVELOPMENT` 阶段测试函数。

1. 当您准备好在 `LIVE` 环境中使用该函数（具有新的或修订的键值对）时，请发布该函数。

   当您发布函数时，CloudFront 会将该函数的版本从 `DEVELOPMENT` 阶段复制到实时阶段。该函数具有新代码，并与键值存储相关联。（无需在实时阶段再次执行此关联。）

   有关如何发布函数的信息，请参阅[发布函数](publish-function.md)。

# 编写函数代码
<a name="writing-function-code"></a>

您可以使用 CloudFront Functions，在 JavaScript 中编写轻量级函数，以实现大规模、注重延迟的 CDN 定制设置。您的函数代码可以操作通过 CloudFront 的请求和响应、执行基本身份验证和授权、在边缘生成 HTTP 响应等。

如需为 CloudFront Functions 编写函数代码的帮助，请参阅以下主题。有关代码示例，请参阅[CloudFront 的 CloudFront Functions 示例](service_code_examples_cloudfront_functions_examples.md)以及 GitHub 上的 [amazon-cloudfront-functions](https://github.com/aws-samples/amazon-cloudfront-functions)。

**Topics**
+ [确定函数用途](function-code-choose-purpose.md)
+ [事件结构](functions-event-structure.md)
+ [JavaScript 运行时功能](functions-javascript-runtime-features.md)
+ [键值存储的帮助程序方法](functions-custom-methods.md)
+ [源修改的辅助方法](helper-functions-origin-modification.md)
+ [CloudFront SaaS Manager 属性的辅助方法](saas-specific-logic-function-code.md)
+ [使用 async 和 await](async-await-syntax.md)
+ [CloudFront Functions 中的 CWT 支持](cwt-support-cloudfront-functions.md)
+ [通用辅助方法](general-helper-methods.md)

# 确定函数用途
<a name="function-code-choose-purpose"></a>

在编写函数代码之前，请确定函数的用途。CloudFront Functions 中的大多数函数都具有以下用途之一。

**Topics**
+ [在查看器请求事件类型中修改 HTTP 请求](#function-code-modify-request)
+ [在查看器请求事件类型中生成 HTTP 响应](#function-code-generate-response)
+ [在查看器响应事件类型中修改 HTTP 响应](#function-code-modify-response)
+ [在连接请求事件类型中验证 mTLS 连接](#function-code-connection-request)
+ [相关信息](#related-information-cloudfront-functions-purpose)

无论函数的用途是什么，`handler` 都是任何函数的入口点。它采用一个名为 `event` 的参数，该参数通过 CloudFront 传递到函数中。`event` 是一个 JSON 对象，它包含 HTTP 请求的表示形式（以及响应，如果您的函数修改了 HTTP 响应）。

## 在查看器请求事件类型中修改 HTTP 请求
<a name="function-code-modify-request"></a>

您的函数可以修改 CloudFront 从查看器（客户端）收到的 HTTP 请求，然后将修改后的请求返回给 CloudFront 以继续处理。例如，您的函数代码可能会归一化[缓存键](understanding-the-cache-key.md)或修改请求标头。

在您创建并发布用于修改 HTTP 请求的函数后，请确保为*查看器请求*事件类型添加关联。有关更多信息，请参阅 [创建函数](functions-tutorial.md#functions-tutorial-create)。这使得该函数在每次 CloudFront 收到来自查看器的请求时运行，之后进行检查来查看所请求的对象是否在 CloudFront 缓存中。

**Example 示例**  
以下伪代码显示了修改 HTTP 请求的函数的结构。  

```
function handler(event) {
    var request = event.request;

    // Modify the request object here.

    return request;
}
```
该函数将修改后的 `request` 对象返回给 CloudFront。CloudFront 继续处理返回的请求，方法是检查 CloudFront 缓存是否存在缓存命中，并在必要时将请求发送到源。

## 在查看器请求事件类型中生成 HTTP 响应
<a name="function-code-generate-response"></a>

您的函数可以在边缘生成 HTTP 响应并直接将其返回给查看器（客户端），而无需检查缓存的响应或由 CloudFront 进一步处理。例如，您的函数代码可能会将请求重定向到新的 URL，或者检查授权并将 `401` 或 `403` 响应返回至未经授权的请求。

创建生成 HTTP 响应的函数时，请确保选择*查看器请求*事件类型。这意味着，每当 CloudFront 收到来自查看器的请求时，在 CloudFront 对请求进行任何进一步处理之前，该函数都会运行。

**Example 示例**  
以下伪代码显示了生成 HTTP 响应的函数的结构。  

```
function handler(event) {
    var request = event.request;

    var response = ...; // Create the response object here,
                        // using the request properties if needed.

    return response;
}
```
该函数将 `response` 对象返回给 CloudFront，CloudFront 立即将其返回给查看器，而无需检查 CloudFront 缓存或向源发送请求。

## 在查看器响应事件类型中修改 HTTP 响应
<a name="function-code-modify-response"></a>

您的函数可以在 CloudFront 将 HTTP 响应发送到查看器（客户端）之前修改 HTTP 响应，无论响应来自 CloudFront 缓存还是源。例如，您的函数代码可能会添加或修改响应标头、状态代码和正文内容。

当您创建修改 HTTP 响应的函数时，请确保选择*查看器响应*事件类型。这意味着，该函数在 CloudFront 向查看器返回响应之前运行，无论响应来自 CloudFront 缓存还是源。

**Example 示例**  
以下伪代码显示了修改 HTTP 响应的函数的结构。  

```
function handler(event) {
    var request = event.request;
    var response = event.response;

    // Modify the response object here,
    // using the request properties if needed.

    return response;
}
```
该函数将修改后的 `response` 对象返回给 CloudFront，CloudFront 会立即将其返回给查看器。

## 在连接请求事件类型中验证 mTLS 连接
<a name="function-code-connection-request"></a>

连接函数是一种在 TLS 连接期间运行的 CloudFront Functions，用于提供自定义验证和身份验证逻辑。连接函数目前可用于双向 TLS（mTLS）连接，您可验证客户端证书并实施标准证书验证之外的自定义身份验证逻辑。连接函数在 TLS 握手过程中运行，可根据证书属性、客户端 IP 地址或其他标准来允许或拒绝连接。

创建并发布连接函数后，请务必为*连接请求*事件类型添加与已启用 mTLS 的分配的关联。这样一来，只要客户端尝试与 CloudFront 建立 mTLS 连接，该函数就会运行。

**Example**  
以下伪代码展示了连接函数的结构：  

```
function connectionHandler(connection) {
    // Validate certificate and connection properties here.
    
    if (/* validation passes */) {
        connection.allow();
    } else {
        connection.deny();
    }
}
```
该函数使用辅助方法来确定是允许还是拒绝连接。与查看器请求和查看器响应函数不同，连接函数无法修改 HTTP 请求或响应。

## 相关信息
<a name="related-information-cloudfront-functions-purpose"></a>

有关使用 CloudFront Functions 的更多信息，请参阅以下主题：
+ [事件结构](functions-event-structure.md)
+ [JavaScript 运行时功能](functions-javascript-runtime-features.md)
+ [CloudFront Functions 示例 ](service_code_examples_cloudfront_functions_examples.md)
+ [边缘函数的限制](edge-functions-restrictions.md)

# CloudFront Functions 事件结构
<a name="functions-event-structure"></a>

CloudFront Functions 在运行函数时将 `event` 对象作为输入传递给函数代码。当您[测试函数](test-function.md)时，可以创建 `event` 对象并将其传递至您的函数。创建用于测试函数的 `event` 对象时，您可以省略 `context` 对象中的 `distributionDomainName`、`distributionId` 和 `requestId` 字段。请确保标头的名称为小写字母，在生产环境中 CloudFront Functions 传递给您的函数的 `event` 对象中情况总是如此。

下面显示了此事件对象的结构概述。

```
{
    "version": "1.0",
    "context": {
        <context object>
    },
    "viewer": {
        <viewer object>
    },
    "request": {
        <request object>
    },
    "response": {
        <response object>
    }
}
```

有关更多信息，请参阅以下主题：

**Topics**
+ [版本字段](#functions-event-structure-version)
+ [Context 对象](#functions-event-structure-context)
+ [连接事件结构](#functions-event-structure-connection)
+ [查看器对象](#functions-event-structure-viewer)
+ [请求对象](#functions-event-structure-request)
+ [响应对象](#functions-event-structure-response)
+ [状态代码和正文](#functions-event-structure-status-body)
+ [查询字符串、标头或 Cookie 的结构](#functions-event-structure-query-header-cookie)
+ [示例响应对象](#functions-response-structure-example)
+ [示例事件对象](#functions-event-structure-example)

## 版本字段
<a name="functions-event-structure-version"></a>

`version` 字段包含一个字符串，用于指定 CloudFront Functions 事件对象的版本。当前版本为 `1.0`。

## Context 对象
<a name="functions-event-structure-context"></a>

`context` 对象包含有关事件的上下文信息。其中包括以下字段：

**`distributionDomainName`**  
与事件关联的标准分配的 CloudFront 域名（例如 d111111abcdef8.cloudfront.net）。  
`distributionDomainName` 字段仅在针对标准分配调用您的函数时才会出现。

**`endpoint`**  
与事件关联的连接组的 CloudFront 域名（例如 d111111abcdef8.cloudfront.net）。  
`endpoint` 字段仅在针对多租户分配调用您的函数时才会出现。

**`distributionId`**  
与事件关联的分配的 ID（例如 EDFDVBD6EXAMPLE）。

**`eventType`**  
事件类型，`viewer-request` 或 `viewer-response`。

**`requestId`**  
唯一标识 CloudFront 请求（及其关联响应）的字符串。

## 连接事件结构
<a name="functions-event-structure-connection"></a>

连接函数接收的事件结构与查看器函数的不同。有关连接事件结构和响应格式的详细信息，请参阅[关联 CloudFront 连接函数](connection-functions.md)。

## 查看器对象
<a name="functions-event-structure-viewer"></a>

`viewer` 对象包含一个 `ip` 字段，其值为发送请求的查看器（客户端）的 IP 地址。如果查看器请求来自 HTTP 代理或负载均衡器，则值为该代理或负载均衡器的 IP 地址。

## 请求对象
<a name="functions-event-structure-request"></a>

`request` 对象包含查看器至 CloudFront HTTP 请求的表示形式。在传递至您的函数的 `event` 对象中，`request` 对象代表 CloudFront 从查看器中接收的实际请求。

如果您的函数代码将 `request` 对象返回到 CloudFront，则它必须使用相同的结构。

`request` 对象包含以下字段：

**`method`**  
请求中的 HTTP 方法。如果您的函数代码返回 `request`，则无法修改此字段。这是 `request` 对象中唯一的只读字段。

**`uri`**  
所请求对象的相对路径。  
如果您的函数修改了 `uri` 值，则会出现以下情况：  
+ 新的 `uri` 值必须以正斜杠（`/`）开头。
+ 如果某个函数更改 `uri` 值，则它会更改查看器请求的对象。
+ 如果某个函数更改 `uri` 值，它*不会*更改请求或源请求发送到的源的缓存行为。

**`querystring`**  
表示请求中的查询字符串的对象。如果请求中没有包括查询字符串，则 `request` 对象仍然包括空的 `querystring` 对象。  
`querystring` 对象为请求中的每个查询字符串参数包含一个字段。

**`headers`**  
表示请求中的 HTTP 标头的对象。如果请求包含任何 `Cookie` 标头，则这些标头不属于 `headers` 对象的一部分。Cookies 在 `cookies` 对象中单独表示。  
`headers` 对象为请求中的每个标头包含一个字段。标头名称在事件对象中转换为 ASCII 小写，当您的函数代码添加标头名称时，它们必须为 ASCII 小写。当 CloudFront Functions 将事件对象转换回 HTTP 请求时，如果标头名称中的字符是 ASCII 字母，则每个单词的第一个字母均为大写。CloudFront Functions 不会对标头名称中的非 ASCII 符号应用任何更改。例如，`TÈst-header` 在函数内部将变为 `tÈst-header`。非 ASCII 符号 `È` 保持不变。  
单词由连字符 (`-`) 分隔。例如，如果您的函数代码添加了名为 `example-header-name` 的标头，CloudFront 会将其转换为 HTTP 请求中的 `Example-Header-Name`。

**`cookies`**  
表示请求中的 Cookies 的对象（`Cookie` 标头）。  
`cookies` 对象为请求中的每个 Cookie 包含一个字段。

有关查询字符串、标头和 Cookies 结构的更多信息，请参阅 [查询字符串、标头或 Cookie 的结构](#functions-event-structure-query-header-cookie)。

有关示例 `event` 对象，请参阅 [示例事件对象](#functions-event-structure-example)。

## 响应对象
<a name="functions-event-structure-response"></a>

`response` 对象包含 CloudFront 至查看器 HTTP 响应的表示形式。在传递至您的函数的 `event` 对象中，`response` 对象表示 CloudFront 对查看器请求的实际响应。

如果您的函数代码返回一个 `response` 对象，它必须使用这个相同的结构。

`response` 对象包含以下字段：

**`statusCode`**  
响应的 HTTP 状态代码。该值是一个整数，不是字符串。  
您的函数可以生成或修改 `statusCode`。

**`statusDescription`**  
响应的 HTTP 状态描述。如果函数代码生成响应，则此字段为可选字段。

**`headers`**  
表示响应中的 HTTP 标头的对象。如果响应包含任何 `Set-Cookie` 标头，则这些标头不属于 `headers` 对象的一部分。Cookies 在 `cookies` 对象中单独表示。  
`headers` 对象为响应中的每个标头包含一个字段。标头名称在事件对象中转换为小写，当您的函数代码添加它们时，标头名称必须为小写。当 CloudFront 函数将事件对象转换回 HTTP 响应时，标头名称中每个单词的第一个字母都会大写。单词由连字符 (`-`) 分隔。例如，如果您的函数代码添加了名为 `example-header-name` 的标头，CloudFront 会将其转换为 HTTP 响应中的 `Example-Header-Name`。

**`cookies`**  
表示响应中的 Cookies 的对象（`Set-Cookie` 标头）。  
`cookies` 对象为响应中的每个 Cookie 包含一个字段。

**`body`**  
添加 `body` 字段是可选的，除非您在函数中指定该字段，否则它不会出现在 `response` 对象中。您的函数无权访问 CloudFront 缓存或源返回的源正文。如果您未在查看器响应函数中指定 `body` 字段，CloudFront 缓存或源返回的源正文会返回到查看器。  
如果您希望 CloudFront 将自定义正文返回查看器，请在 `data` 字段中指定正文内容，并在 `encoding` 字段中指定正文编码。您可以将编码指定为纯文本 (`"encoding": "text"`) 或 Base64 编码的内容 (`"encoding": "base64"`)。  
作为快捷方式，您也可以直接在 `body` 字段 (`"body": "<specify the body content here>"`) 中指定正文内容。执行此操作时，忽略 `data` 和 `encoding` 字段。在这种情况下，CloudFront 将正文视为纯文本。    
`encoding`  
`body` 内容（`data` 字段）的编码。有效编码仅为 `text` 和 `base64`。  
如果将 `encoding` 指定为 `base64`，但正文不是有效的 base64，CloudFront 将返回错误。  
`data`  
`body` 内容。

有关修改后的状态代码和正文内容的更多信息，请参阅 [状态代码和正文](#functions-event-structure-status-body)。

有关标头和 Cookies 结构的更多信息，请参阅 [查询字符串、标头或 Cookie 的结构](#functions-event-structure-query-header-cookie)。

有关示例 `response` 对象，请参阅 [示例响应对象](#functions-response-structure-example)。

## 状态代码和正文
<a name="functions-event-structure-status-body"></a>

使用 CloudFront Functions，您可以更新查看器响应状态代码，将整个响应正文替换为新响应正文，或删除响应正文。在评估来自 CloudFront 缓存或源的响应的各个方面后更新查看器响应的一些常见场景包括：
+ 更改状态以设置 HTTP 200 状态代码并创建要返回查看器的静态正文内容。
+ 更改状态以设置将用户重定向到其他网站的 HTTP 301 或 302 状态代码。
+ 决定是提供还是丢弃查看器响应的正文。

**注意**  
如果源返回 400 及以上的 HTTP 错误，则 CloudFront Functions 将无法运行。有关更多信息，请参阅 [所有边缘函数的限制](edge-function-restrictions-all.md)。

当您在处理 HTTP 响应时，CloudFront Functions 无权访问响应正文。您可以通过将正文内容设置为所需值来替换正文内容，或者通过将值设置为空来删除正文。如果您不更新函数中的正文字段，CloudFront 缓存或源返回的源正文将返回到查看器。

**提示**  
使用 CloudFront Functions 替换正文时，请确保将相应的标头（例如 `content-encoding`、`content-type` 或 `content-length`）与新的正文内容对齐。  
例如，如果 CloudFront 源或缓存返回 `content-encoding: gzip`，但查看器响应函数设置的正文为纯文本，则该函数还需要相应地更改 `content-encoding` 和 `content-type` 标头。

如果您的 CloudFront 函数配置为返回 400 或以上的 HTTP 错误，则您的查看器将看不到您为相同状态代码指定的[自定义错误页面](creating-custom-error-pages.md)。

## 查询字符串、标头或 Cookie 的结构
<a name="functions-event-structure-query-header-cookie"></a>

查询字符串、标头和 Cookie 共享同一个结构。查询字符串可以出现在请求中。标头出现在请求和响应中。Cookie 出现在请求和响应中。

每个查询字符串、标头或 Cookie 都是父项 `querystring`、`headers` 或 `cookies` 对象内的唯一字段。字段名称是查询字符串、标头或 Cookie 的名称。每个字段都包含一个 `value` 属性，并带有查询字符串、标头或 Cookie 的值。

**Contents**
+ [查询字符串值或查询字符串对象](#functions-event-structure-query)
+ [标头的特殊注意事项](#functions-event-structure-headers)
+ [重复的查询字符串、标头和 Cookies（`multiValue` 数组）](#functions-event-structure-multivalue)
+ [Cookie 属性](#functions-event-structure-cookie-attributes)

### 查询字符串值或查询字符串对象
<a name="functions-event-structure-query"></a>

除了查询字符串对象之外，函数还可以返回查询字符串值。查询字符串值可用于按任何自定义顺序排列查询字符串参数。

**Example 示例**  
要修改函数代码中的查询字符串，请使用如下代码。  

```
var request = event.request; 
request.querystring = 'ID=42&Exp=1619740800&TTL=1440&NoValue=&querymv=val1&querymv=val2,val3';
```

### 标头的特殊注意事项
<a name="functions-event-structure-headers"></a>

仅对于标头，标头名称在事件对象中转换为小写，当您的函数代码添加它们时，标头名称必须为小写。当 CloudFront 函数将事件对象转换回 HTTP 请求或响应时，标头名称中每个单词的第一个字母都会大写。单词由连字符 (`-`) 分隔。例如，如果您的函数代码添加了名为 `example-header-name` 的标头，CloudFront 会将其转换为 HTTP 请求或响应中的 `Example-Header-Name`。

**Example 示例**  
请考虑 HTTP 请求中的以下 `Host` 标头。  

```
Host: video.example.com
```
此标头在 `request` 对象中表示如下：  

```
"headers": {
    "host": {
        "value": "video.example.com"
    }
}
```
要访问函数代码中的 `Host` 标头，请使用如下代码：  

```
var request = event.request;
var host = request.headers.host.value;
```
要在函数代码中添加或修改标头，请使用如下代码（此代码添加了一个名为 `X-Custom-Header` 且带有值 `example value` 的标头）：  

```
var request = event.request;
request.headers['x-custom-header'] = {value: 'example value'};
```

### 重复的查询字符串、标头和 Cookies（`multiValue` 数组）
<a name="functions-event-structure-multivalue"></a>

HTTP 请求或响应可以包含多个具有相同名称的查询字符串、标头或 Cookie。在这种情况下，重复的查询字符串、标头或 Cookie 会折叠为 `request` 或 `response` 对象中的一个字段，但此字段包含名为 `multiValue` 的额外属性。`multiValue` 属性包含一个数组，其中包含每个重复查询字符串、标头或 Cookies 的值。

**Example 示例**  
请考虑具有以下 `Accept` 标头的 HTTP 请求。  

```
Accept: application/json
Accept: application/xml
Accept: text/html
```
这些标头在 `request` 对象中表示如下。  

```
"headers": {
    "accept": {
        "value": "application/json",
        "multiValue": [
            {
                "value": "application/json"
            },
            {
                "value": "application/xml"
            },
            {
                "value": "text/html"
            }
        ]
    }
}
```

**注意**  
第一个标头值（在本例中为 `application/json`）在 `value` 和 `multiValue` 属性中重复。这样一来，您可以通过循环 `multiValue` 数组来访问*全部*值。

如果函数代码修改具有 `multiValue` 数组的查询字符串、标头或 Cookie，CloudFront Functions 将使用以下规则来应用更改：

1. 如果 `multiValue` 数组存在并进行了任何修改，则应用该修改。`value` 属性中的第一个元素将被忽略。

1. 否则，将应用对 `value` 属性的任何修改，后续值（如果存在）保持不变。

仅当 HTTP 请求或响应中包含重复的查询字符串、标头或具有相同名称的 Cookie 时，才使用 `multiValue` 属性，如前面的示例所示。但是，如果单个查询字符串、标头或 Cookie 中有多个值，则不使用 `multiValue` 属性。

**Example 示例**  
请考虑带有一个 `Accept` 标头的请求，该表头中包含三个值。  

```
Accept: application/json, application/xml, text/html
```
此标头在 `request` 对象中表示如下。  

```
"headers": {
    "accept": {
        "value": "application/json, application/xml, text/html"
    }
}
```

### Cookie 属性
<a name="functions-event-structure-cookie-attributes"></a>

在 HTTP 响应的 `Set-Cookie` 标头中，标头包含 Cookie 的名称-值对以及可选的一组用分号分隔的属性。

**Example 示例**  

```
Set-Cookie: cookie1=val1; Secure; Path=/; Domain=example.com; Expires=Wed, 05 Apr 2021 07:28:00 GMT
```
在 `response` 对象中，这些属性在 Cookie 字段的 `attributes` 属性中表示。例如，前面的 `Set-Cookie` 标头表示如下：  

```
"cookie1": {
    "value": "val1",
    "attributes": "Secure; Path=/; Domain=example.com; Expires=Wed, 05 Apr 2021 07:28:00 GMT"
}
```

## 示例响应对象
<a name="functions-response-structure-example"></a>

以下示例显示了一个 `response` 对象（查看器响应函数的输出），其中的正文已被查看器响应函数替换。

```
{
  "response": {
    "statusCode": 200,
    "statusDescription": "OK",
    "headers": {
      "date": {
        "value": "Mon, 04 Apr 2021 18:57:56 GMT"
      },
      "server": {
        "value": "gunicorn/19.9.0"
      },
      "access-control-allow-origin": {
        "value": "*"
      },
      "access-control-allow-credentials": {
        "value": "true"
      },
      "content-type": {
        "value": "text/html"
      },
      "content-length": {
        "value": "86"
      }
    },
    "cookies": {
      "ID": {
        "value": "id1234",
        "attributes": "Expires=Wed, 05 Apr 2021 07:28:00 GMT"
      },
      "Cookie1": {
        "value": "val1",
        "attributes": "Secure; Path=/; Domain=example.com; Expires=Wed, 05 Apr 2021 07:28:00 GMT",
        "multiValue": [
          {
            "value": "val1",
            "attributes": "Secure; Path=/; Domain=example.com; Expires=Wed, 05 Apr 2021 07:28:00 GMT"
          },
          {
            "value": "val2",
            "attributes": "Path=/cat; Domain=example.com; Expires=Wed, 10 Jan 2021 07:28:00 GMT"
          }
        ]
      }
    },
    
    // Adding the body field is optional and it will not be present in the response object
    // unless you specify it in your function.
    // Your function does not have access to the original body returned by the CloudFront
    // cache or origin.
    // If you don't specify the body field in your viewer response function, the original
    // body returned by the CloudFront cache or origin is returned to viewer.

     "body": {
      "encoding": "text",
      "data": "<!DOCTYPE html><html><body><p>Here is your custom content.</p></body></html>"
    }
  }
}
```

## 示例事件对象
<a name="functions-event-structure-example"></a>

以下示例显示了一个完整的 `event` 对象。这是标准分配（而不是多租户分配）的调用示例。对于多租户分配，使用 `endpoint` 字段而非 `distributionDomainName` 字段。`endpoint` 的值是与事件关联的连接组的 CloudFront 域名（例如 d111111abcdef8.cloudfront.net）。

**注意**  
`event` 对象是函数的输入。您的函数只返回 `request` 或 `response` 对象，而不返回完整的 `event` 对象。

```
{
    "version": "1.0",
    "context": {
        "distributionDomainName": "d111111abcdef8.cloudfront.net",
        "distributionId": "EDFDVBD6EXAMPLE",
        "eventType": "viewer-response",
        "requestId": "EXAMPLEntjQpEXAMPLE_SG5Z-EXAMPLEPmPfEXAMPLEu3EqEXAMPLE=="
    },
    "viewer": {"ip": "198.51.100.11"},
    "request": {
        "method": "GET",
        "uri": "/media/index.mpd",
        "querystring": {
            "ID": {"value": "42"},
            "Exp": {"value": "1619740800"},
            "TTL": {"value": "1440"},
            "NoValue": {"value": ""},
            "querymv": {
                "value": "val1",
                "multiValue": [
                    {"value": "val1"},
                    {"value": "val2,val3"}
                ]
            }
        },
        "headers": {
            "host": {"value": "video.example.com"},
            "user-agent": {"value": "Mozilla/5.0 (Windows NT 10.0; Win64; x64; rv:83.0) Gecko/20100101 Firefox/83.0"},
            "accept": {
                "value": "application/json",
                "multiValue": [
                    {"value": "application/json"},
                    {"value": "application/xml"},
                    {"value": "text/html"}
                ]
            },
            "accept-language": {"value": "en-GB,en;q=0.5"},
            "accept-encoding": {"value": "gzip, deflate, br"},
            "origin": {"value": "https://website.example.com"},
            "referer": {"value": "https://website.example.com/videos/12345678?action=play"},
            "cloudfront-viewer-country": {"value": "GB"}
        },
        "cookies": {
            "Cookie1": {"value": "value1"},
            "Cookie2": {"value": "value2"},
            "cookie_consent": {"value": "true"},
            "cookiemv": {
                "value": "value3",
                "multiValue": [
                    {"value": "value3"},
                    {"value": "value4"}
                ]
            }
        }
    },
    "response": {
        "statusCode": 200,
        "statusDescription": "OK",
        "headers": {
            "date": {"value": "Mon, 04 Apr 2021 18:57:56 GMT"},
            "server": {"value": "gunicorn/19.9.0"},
            "access-control-allow-origin": {"value": "*"},
            "access-control-allow-credentials": {"value": "true"},
            "content-type": {"value": "application/json"},
            "content-length": {"value": "701"}
        },
        "cookies": {
            "ID": {
                "value": "id1234",
                "attributes": "Expires=Wed, 05 Apr 2021 07:28:00 GMT"
            },
            "Cookie1": {
                "value": "val1",
                "attributes": "Secure; Path=/; Domain=example.com; Expires=Wed, 05 Apr 2021 07:28:00 GMT",
                "multiValue": [
                    {
                        "value": "val1",
                        "attributes": "Secure; Path=/; Domain=example.com; Expires=Wed, 05 Apr 2021 07:28:00 GMT"
                    },
                    {
                        "value": "val2",
                        "attributes": "Path=/cat; Domain=example.com; Expires=Wed, 10 Jan 2021 07:28:00 GMT"
                    }
                ]
            }
        }
    }
}
```

# 适用于 CloudFront Functions 的 JavaScript 运行时系统特征
<a name="functions-javascript-runtime-features"></a>

CloudFront Functions JavaScript 运行时系统环境符合 [ECMAScript (ES) 5.1](https://www.ecma-international.org/ecma-262/5.1/) 的要求，也支持 ES 版本 6 至 9 的某些特征。

为了获得最新的功能，建议您使用 JavaScript 运行时 2.0。

与 1.0 相比，JavaScript 运行时 2.0 有以下变化：
+ 可使用缓冲区模块方法
+ 以下非标准字符串原型方法不可用：
  + `String.prototype.bytesFrom()`
  + `String.prototype.fromBytes()`
  + `String.prototype.fromUTF8()`
  + `String.prototype.toBytes()`
  + `String.prototype.toUTF8()`
+ 加密模块包含以下更改：
  + `hash.digest()` - 如果未提供编码，则返回类型更改为 `Buffer`
  + `hmac.digest()` - 如果未提供编码，则返回类型更改为 `Buffer`
+ 有关其他新功能的更多信息，请参阅[适用于 CloudFront Functions 的 JavaScript 运行时系统 2.0 特征](functions-javascript-runtime-20.md)。

**Topics**
+ [JavaScript 运行时 1.0 功能](functions-javascript-runtime-10.md)
+ [JavaScript 运行时系统 2.0 特征](functions-javascript-runtime-20.md)

# 适用于 CloudFront Functions 的 JavaScript 运行时 1.0 功能
<a name="functions-javascript-runtime-10"></a>

CloudFront Functions JavaScript 运行时环境符合 [ECMAScript (ES) 版本 5.1](https://262.ecma-international.org/5.1/)，也支持 ES 版本 6 至 9 的某些功能。它还提供了一些不属于 ES 规范的非标准方法。

以下主题列出了所有支持的语言功能。

**Topics**
+ [核心功能](#writing-functions-javascript-features-core)
+ [原语对象](#writing-functions-javascript-features-primitive-objects)
+ [内置对象](#writing-functions-javascript-features-builtin-objects)
+ [错误类型](#writing-functions-javascript-features-error-types)
+ [全局变量](#writing-functions-javascript-features-globals)
+ [内置模块](#writing-functions-javascript-features-builtin-modules)
+ [受限功能](#writing-functions-javascript-features-restricted-features)

## 核心功能
<a name="writing-functions-javascript-features-core"></a>

支持 ES 的以下核心功能。

**类型**  
支持所有 ES 5.1 类型。这包括布尔值、数字、字符串、对象、数组、函数、函数构造函数和正则表达式。

**运算符**  
支持所有 ES 5.1 运算符。  
支持 ES 7 幂运算符 (`**`)。

**语句**  
不支持 `const` 和 `let` 语句。
支持以下 ES 5.1 语句：  
+ `break`
+ `catch`
+ `continue`
+ `do-while`
+ `else`
+ `finally`
+ `for`
+ `for-in`
+ `if`
+ `return`
+ `switch`
+ `throw`
+ `try`
+ `var`
+ `while`
+ 带标签的语句

**文本**  
支持 ES 6 模板文本：多行字符串、表达式插值和嵌套模板。

**函数**  
支持所有的 ES 5.1 函数功能。  
支持 ES 6 箭头函数，并支持 ES 6 rest 参数语法。

**Unicode**  
源文本和字符串文本可以包含 Unicode 编码的字符。还支持由六个字符组成的 Unicode 代码点转义序列（例如，`\uXXXX`）。

**严格模式**  
默认情况下，函数在严格模式下运行，因此您无需在函数代码中添加 `use strict` 语句。无法对其进行更改。

## 原语对象
<a name="writing-functions-javascript-features-primitive-objects"></a>

支持 ES 的以下原语对象。

**对象**  
支持对对象使用以下 ES 5.1 方法：  
+ `create`（没有属性列表）
+ `defineProperties`
+ `defineProperty`
+ `freeze`
+ `getOwnPropertyDescriptor`
+ `getOwnPropertyNames`
+ `getPrototypeOf`
+ `hasOwnProperty`
+ `isExtensible`
+ `isFrozen`
+ `prototype.isPrototypeOf`
+ `isSealed`
+ `keys`
+ `preventExtensions`
+ `prototype.propertyIsEnumerable`
+ `seal`
+ `prototype.toString`
+ `prototype.valueOf`
支持对对象使用以下 ES 6 方法：  
+ `assign`
+ `is`
+ `prototype.setPrototypeOf`
支持对对象使用以下 ES 8 方法：  
+ `entries`
+ `values`

**字符串**  
支持对字符串使用以下 ES 5.1 方法：  
+ `fromCharCode`
+ `prototype.charAt`
+ `prototype.concat`
+ `prototype.indexOf`
+ `prototype.lastIndexOf`
+ `prototype.match`
+ `prototype.replace`
+ `prototype.search`
+ `prototype.slice`
+ `prototype.split`
+ `prototype.substr`
+ `prototype.substring`
+ `prototype.toLowerCase`
+ `prototype.trim`
+ `prototype.toUpperCase`
支持对字符串使用以下 ES 6 方法：  
+ `fromCodePoint`
+ `prototype.codePointAt`
+ `prototype.endsWith`
+ `prototype.includes`
+ `prototype.repeat`
+ `prototype.startsWith`
支持对字符串使用以下 ES 8 方法：  
+ `prototype.padStart`
+ `prototype.padEnd`
支持对字符串使用以下 ES 9 方法：  
+ `prototype.trimStart`
+ `prototype.trimEnd`
支持对字符串使用以下非标准方法：  
+ `prototype.bytesFrom(array | string, encoding)`

  从八位数组或编码字符串创建字节字符串。字符串编码选项为 `hex`、`base64` 和 `base64url`。
+ `prototype.fromBytes(start[, end])`

  从字节字符串创建 Unicode 字符串，其中每个字节都被替换为相应的 Unicode 代码点。
+ `prototype.fromUTF8(start[, end])`

  从 UTF-8 编码的字节字符串创建 Unicode 字符串。如果编码不正确，则会返回 `null`。
+ `prototype.toBytes(start[, end])`

  从 Unicode 字符串创建字节字符串。所有字符都必须在 [0,255] 范围内。如果不在该范围内，它会返回 `null`。
+ `prototype.toUTF8(start[, end])`

  从 Unicode 字符串创建 UTF-8 编码的字节字符串。

**数字**  
支持对数字使用所有 ES 5.1 方法。  
支持对数字使用以下 ES 6 方法：  
+ `isFinite`
+ `isInteger`
+ `isNaN`
+ `isSafeInteger`
+ `parseFloat`
+ `parseInt`
+ `prototype.toExponential`
+ `prototype.toFixed`
+ `prototype.toPrecision`
+ `EPSILON`
+ `MAX_SAFE_INTEGER`
+ `MAX_VALUE`
+ `MIN_SAFE_INTEGER`
+ `MIN_VALUE`
+ `NEGATIVE_INFINITY`
+ `NaN`
+ `POSITIVE_INFINITY`

## 内置对象
<a name="writing-functions-javascript-features-builtin-objects"></a>

支持 ES 的以下内置对象。

**数学**  
支持所有 ES 5.1 数学方法。  
在 CloudFront Functions 运行时环境中，`Math.random()` 实现使用植入有函数运行时间戳的 OpenBSD `arc4random`。
支持以下 ES 6 数学方法：  
+ `acosh`
+ `asinh`
+ `atanh`
+ `cbrt`
+ `clz32`
+ `cosh`
+ `expm1`
+ `fround`
+ `hypot`
+ `imul`
+ `log10`
+ `log1p`
+ `log2`
+ `sign`
+ `sinh`
+ `tanh`
+ `trunc`
+ `E`
+ `LN10`
+ `LN2`
+ `LOG10E`
+ `LOG2E`
+ `PI`
+ `SQRT1_2`
+ `SQRT2`

**日期**  
支持所有 ES 5.1 `Date` 功能。  
出于安全原因，在单个函数运行的生命周期内，`Date` 始终返回相同的值（函数的开始时间）。有关更多信息，请参阅 [受限功能](#writing-functions-javascript-features-restricted-features)。

**函数**  
支持 `apply`、`bind` 和 `call` 方法。  
不支持函数构造函数。

**正则表达式**  
支持所有 ES 5.1 正则表达式功能。正则表达式语言与 Perl 兼容。支持 ES 9 命名的捕获组。

**JSON**  
支持所有 ES 5.1 JSON 功能，包括 `parse` 和 `stringify`。

**数组**  
支持对数组使用以下 ES 5.1 方法：  
+ `isArray`
+ `prototype.concat`
+ `prototype.every`
+ `prototype.filter`
+ `prototype.forEach`
+ `prototype.indexOf`
+ `prototype.join`
+ `prototype.lastIndexOf`
+ `prototype.map`
+ `prototype.pop`
+ `prototype.push`
+ `prototype.reduce`
+ `prototype.reduceRight`
+ `prototype.reverse`
+ `prototype.shift`
+ `prototype.slice`
+ `prototype.some`
+ `prototype.sort`
+ `prototype.splice`
+ `prototype.unshift`
支持对数组使用以下 ES 6 方法：  
+ `of`
+ `prototype.copyWithin`
+ `prototype.fill`
+ `prototype.find`
+ `prototype.findIndex`
支持对数组使用以下 ES 7 方法：  
+ `prototype.includes`

**类型化数组**  
支持对数组使用以下 ES 6 类型化数组：  
+ `Int8Array`
+ `Uint8Array`
+ `Uint8ClampedArray`
+ `Int16Array`
+ `Uint16Array`
+ `Int32Array`
+ `Uint32Array`
+ `Float32Array`
+ `Float64Array`
+ `prototype.copyWithin`
+ `prototype.fill`
+ `prototype.join`
+ `prototype.set`
+ `prototype.slice`
+ `prototype.subarray`
+ `prototype.toString`

**ArrayBuffer**  
支持对 `ArrayBuffer` 使用以下方法：  
+ `prototype.isView`
+ `prototype.slice`

**Promise**  
支持以下承诺方法：  
+ `reject`
+ `resolve`
+ `prototype.catch`
+ `prototype.finally`
+ `prototype.then`

**加密**  
加密模块提供标准哈希和基于哈希的消息身份验证码 (HMAC) 帮助程序。您可以使用 `require('crypto')` 加载模块。该模块公开了以下方法，这些方法的行为与 Node.js 对应方法完全相同：  
+ `createHash(algorithm)`
+ `hash.update(data)`
+ `hash.digest([encoding])`
+ `createHmac(algorithm, secret key)`
+ `hmac.update(data)`
+ `hmac.digest([encoding])`
有关更多信息，请参阅内置模块部分中的 [加密（哈希和 HMAC）](#writing-functions-javascript-features-builtin-modules-crypto)。

**控制台**  
这是调试的帮助对象。它只支持 `log()` 方法来记录日志消息。  
CloudFront Functions 不支持逗号语法，例如 `console.log('a', 'b')`。改为使用 `console.log('a' + ' ' + 'b')` 格式。

## 错误类型
<a name="writing-functions-javascript-features-error-types"></a>

支持以下错误对象：
+ `Error`
+ `EvalError`
+ `InternalError`
+ `MemoryError`
+ `RangeError`
+ `ReferenceError`
+ `SyntaxError`
+ `TypeError`
+ `URIError`

## 全局变量
<a name="writing-functions-javascript-features-globals"></a>

支持 `globalThis` 对象。

支持以下 ES 5.1 全局函数：
+ `decodeURI`
+ `decodeURIComponent`
+ `encodeURI`
+ `encodeURIComponent`
+ `isFinite`
+ `isNaN`
+ `parseFloat`
+ `parseInt`

支持以下全局常数：
+ `NaN`
+ `Infinity`
+ `undefined`

## 内置模块
<a name="writing-functions-javascript-features-builtin-modules"></a>

支持以下内置模块。

**Topics**
+ [加密（哈希和 HMAC）](#writing-functions-javascript-features-builtin-modules-crypto)
+ [查询字符串](#writing-functions-javascript-features-builtin-modules-query-string)

### 加密（哈希和 HMAC）
<a name="writing-functions-javascript-features-builtin-modules-crypto"></a>

加密模块 (`crypto`) 提供标准哈希和基于哈希的消息身份验证码 (HMAC) 帮助程序。您可以使用 `require('crypto')` 加载模块。该模块提供了以下方法，这些方法的行为与 Node.js 对应方法完全相同：

**哈希方法**

`crypto.createHash(algorithm)`  
创建并返回哈希对象，您可以使用给定的算法：`md5`、`sha1` 或 `sha256` 通过它生成哈希摘要。

`hash.update(data)`  
用给定的 `data` 更新哈希内容。

`hash.digest([encoding])`  
计算使用 `hash.update()` 传递的所有数据的摘要。编码可以是 `hex`、`base64` 或 `base64url`。

**HMAC 方法**

`crypto.createHmac(algorithm, secret key)`  
创建并返回使用给定的 `algorithm` 和 `secret key` 的 HMAC 对象。算法可以是 `md5`、`sha1` 或 `sha256`。

`hmac.update(data)`  
用给定的 `data` 更新 HMAC 内容。

`hmac.digest([encoding])`  
计算使用 `hmac.update()` 传递的所有数据的摘要。编码可以是 `hex`、`base64` 或 `base64url`。

### 查询字符串
<a name="writing-functions-javascript-features-builtin-modules-query-string"></a>

**注意**  
[CloudFront Functions 事件对象](functions-event-structure.md)自动为您解析 URL 查询字符串。这意味着，在大多数情况下您不需要使用此模块。

查询字符串模块 (`querystring`) 提供了解析和格式化 URL 查询字符串的方法。您可以使用 `require('querystring')` 加载模块。该模块提供了以下方法。

`querystring.escape(string)`  
URL - 对给定的 `string` 进行编码，从而返回转义的查询字符串。该方法由 `querystring.stringify()` 使用，不应直接使用。

`querystring.parse(string[, separator[, equal[, options]]])`  
解析查询字符串 (`string`) 并返回对象。  
`separator` 参数是用于在查询字符串中分隔键和值对的子字符串。默认为 `&`。  
`equal` 参数是用于在查询字符串中分隔键和值的子字符串。默认为 `=`。  
`options` 参数是具有以下键的对象：    
`decodeURIComponent function`  
用于解码查询字符串中百分比编码字符的函数。默认为 `querystring.unescape()`。  
`maxKeys number`  
要解析的最大密钥数。默认为 `1000`。使用 `0` 的值取消键的计数限制。
默认情况下，假定查询字符串中的百分比编码字符使用 UTF-8 编码。无效的 UTF-8 序列将被替换为 `U+FFFD` 替换字符。  
例如，对于以下查询字符串：  

```
'name=value&abc=xyz&abc=123'
```
`querystring.parse()` 的返回值为：  

```
{
name: 'value',
abc: ['xyz', '123']
}
```
`querystring.decode()` 是 `querystring.parse()` 的别名。

`querystring.stringify(object[, separator[, equal[, options]]])`  
序列化 `object` 并返回查询字符串。  
`separator` 参数是用于在查询字符串中分隔键和值对的子字符串。默认为 `&`。  
`equal` 参数是用于在查询字符串中分隔键和值的子字符串。默认为 `=`。  
`options` 参数是具有以下键的对象：    
`encodeURIComponent function`  
用于将 URL 不安全字符转换为查询字符串中的百分比编码的函数。默认为 `querystring.escape()`。
默认情况下，查询字符串中需要百分比编码的字符将编码为 UTF-8。要使用其他编码，请指定 `encodeURIComponent` 选项。  
例如，在下面的代码中：  

```
querystring.stringify({ name: 'value', abc: ['xyz', '123'], anotherName: '' });
```
返回值为：  

```
'name=value&abc=xyz&abc=123&anotherName='
```
`querystring.encode()` 是 `querystring.stringify()` 的别名。

`querystring.unescape(string)`  
对给定的 `string` 中的 URL 百分比编码字符进行解码，以返回未转义的查询字符串。此方法由 `querystring.parse()` 使用，不应直接使用。

## 受限功能
<a name="writing-functions-javascript-features-restricted-features"></a>

由于安全考虑，以下 JavaScript 语言功能或不受支持，或收到限制。

**动态代码评估**  
不支持动态代码评估。如果尝试，`eval()` 和 `Function` 构造函数都会引发错误。例如，`const sum = new Function('a', 'b', 'return a + b')` 引发错误。

**计时器**  
不支持 `setTimeout()`、`setImmediate()` 和 `clearTimeout()` 函数。在函数运行中没有可以推迟或生成的预置。您的函数必须同步运行才能完成。

**日期和时间戳**  
出于安全原因，无法访问高分辨率计时器。在单个函数运行的生命周期内，查询当前时间的所有 `Date` 方法始终返回相同的值。返回的时间戳是函数开始运行的时间。因此，您无法度量函数中的经过时间。

**文件系统访问**  
没有文件系统访问权限。例如，没有像 Node.js 的 `fs` 模块可以进行文件系统访问。

**处理访问权限**  
没有处理访问权限。例如，没有像 Node.js 中那样用于处理信息访问权限的 `process` 全局对象。

**环境变量**  
无法访问环境变量。  
相反，可以使用 CloudFront KeyValueStore 为 CloudFront Functions 创建集中式的键值对数据存储。借助 CloudFront KeyValueStore，无需部署代码更改即对配置数据进行动态更新。必须使用 [JavaScript 运行时 2.0](functions-javascript-runtime-20.md) 才能使用 CloudFront KeyValueStore。有关更多信息，请参阅 [Amazon CloudFront KeyValueStore](kvs-with-functions.md)。

**网络访问**  
不支持网络调用。例如，不支持 XHR、HTTP (S) 和套接字。

# 适用于 CloudFront Functions 的 JavaScript 运行时系统 2.0 特征
<a name="functions-javascript-runtime-20"></a>

CloudFront Functions JavaScript 运行时系统环境符合 [ECMAScript (ES) 5.1](https://262.ecma-international.org/5.1/) 的要求，也支持 ES 版本 6 至 9 的某些特征。它还提供了一些不属于 ES 规范的非标准方法。以下主题列出了此运行时系统中支持的所有特征。

**Topics**
+ [核心功能](#writing-functions-javascript-features-core-20)
+ [原语对象](#writing-functions-javascript-features-primitive-objects-20)
+ [内置对象](#writing-functions-javascript-features-builtin-objects-20)
+ [错误类型](#writing-functions-javascript-features-error-types-20)
+ [全局变量](#writing-functions-javascript-features-globals-20)
+ [内置模块](#writing-functions-javascript-features-builtin-modules-20)
+ [受限功能](#writing-functions-javascript-features-restricted-features-20)

## 核心功能
<a name="writing-functions-javascript-features-core-20"></a>

支持 ES 的以下核心功能。

**类型**  
支持所有 ES 5.1 类型。其中包括布尔值、数字、字符串、对象、数组、函数和正则表达式。

**运算符**  
支持所有 ES 5.1 运算符。  
支持 ES 7 幂运算符 (`**`)。

**语句**  
支持以下 ES 5.1 语句：  
+ `break`
+ `catch`
+ `continue`
+ `do-while`
+ `else`
+ `finally`
+ `for`
+ `for-in`
+ `if`
+ `label`
+ `return`
+ `switch`
+ `throw`
+ `try`
+ `var`
+ `while`
支持以下 ES 6 语句：  
+ `const`
+ `let`
支持以下 ES 8 语句：  
+ `async`
+ `await`
JavaScript 运行时 2.0 支持 `async`、`await`、`const` 和 `let`。  
`await` 只能在 `async` 函数内部使用。不支持 `async` 参数和闭包。

**文本**  
支持 ES 6 模板文本：多行字符串、表达式插值和嵌套模板。

**函数**  
支持所有的 ES 5.1 函数功能。  
支持 ES 6 箭头函数，并支持 ES 6 rest 参数语法。

**Unicode**  
源文本和字符串文本可以包含 Unicode 编码的字符。还支持由六个字符组成的 Unicode 代码点转义序列（例如，`\uXXXX`）。

**严格模式**  
默认情况下，函数在严格模式下运行，因此您无需在函数代码中添加 `use strict` 语句。无法对其进行更改。

## 原语对象
<a name="writing-functions-javascript-features-primitive-objects-20"></a>

支持 ES 的以下原语对象。

**对象**  
支持对对象使用以下 ES 5.1 方法：  
+ `Object.create()`（没有属性列表）
+ `Object.defineProperties()`
+ `Object.defineProperty()`
+ `Object.freeze()`
+ `Object.getOwnPropertyDescriptor()`
+ `Object.getOwnPropertyDescriptors()`
+ `Object.getOwnPropertyNames()`
+ `Object.getPrototypeOf()`
+ `Object.isExtensible()`
+ `Object.isFrozen()`
+ `Object.isSealed()`
+ `Object.keys()`
+ `Object.preventExtensions()`
+ `Object.seal()`
支持对对象使用以下 ES 6 方法：  
+ `Object.assign()`
支持对对象使用以下 ES 8 方法：  
+ `Object.entries()`
+ `Object.values()`
支持对对象使用以下 ES 5.1 原型方法：  
+ `Object.prototype.hasOwnProperty()`
+ `Object.prototype.isPrototypeOf()`
+ `Object.prototype.propertyIsEnumerable()`
+ `Object.prototype.toString()`
+ `Object.prototype.valueOf()`
支持对对象使用以下 ES 6 原型方法：  
+ `Object.prototype.is()`
+ `Object.prototype.setPrototypeOf()`

**字符串**  
支持对字符串使用以下 ES 5.1 方法：  
+ `String.fromCharCode()`
支持对字符串使用以下 ES 6 方法：  
+ `String.fromCodePoint()`
支持对字符串使用以下 ES 5.1 原型方法：  
+ `String.prototype.charAt()`
+ `String.prototype.concat()`
+ `String.prototype.indexOf()`
+ `String.prototype.lastIndexOf()`
+ `String.prototype.match()`
+ `String.prototype.replace()`
+ `String.prototype.search()`
+ `String.prototype.slice()`
+ `String.prototype.split()`
+ `String.prototype.substr()`
+ `String.prototype.substring()`
+ `String.prototype.toLowerCase()`
+ `String.prototype.trim()`
+ `String.prototype.toUpperCase()`
支持对字符串使用以下 ES 6 原型方法：  
+ `String.prototype.codePointAt()`
+ `String.prototype.endsWith()`
+ `String.prototype.includes()`
+ `String.prototype.repeat()`
+ `String.prototype.startsWith()`
支持对字符串使用以下 ES 8 原型方法：  
+ `String.prototype.padStart()`
+ `String.prototype.padEnd()`
支持对字符串使用以下 ES 9 原型方法：  
+ `String.prototype.trimStart()`
+ `String.prototype.trimEnd()`
支持对字符串使用以下 ES 12 原型方法：  
+ `String.prototype.replaceAll()`
**注意**  
`String.prototype.replaceAll()` 是 JavaScript 运行时系统 2.0 中的新功能。

**数字**  
支持所有 ES 5 数字。  
支持对数字使用以下 ES 6 属性：  
+ `Number.EPSILON`
+ `Number.MAX_SAFE_INTEGER`
+ `Number.MIN_SAFE_INTEGER`
+ `Number.MAX_VALUE`
+ `Number.MIN_VALUE`
+ `Number.NaN`
+ `Number.NEGATIVE_INFINITY`
+ `Number.POSITIVE_INFINITY`
支持对数字使用以下 ES 6 方法：  
+ `Number.isFinite()`
+ `Number.isInteger()`
+ `Number.isNaN()`
+ `Number.isSafeInteger()`
+ `Number.parseInt()`
+ `Number.parseFloat()`
支持对数字使用以下 ES 5.1 原型方法：  
+ `Number.prototype.toExponential()`
+ `Number.prototype.toFixed()`
+ `Number.prototype.toPrecision()`
支持 ES 12 数字分隔符。  
ES 12 数字分隔符是 JavaScript 运行时系统 2.0 中的新功能。

## 内置对象
<a name="writing-functions-javascript-features-builtin-objects-20"></a>

支持 ES 的以下内置对象。

**数学**  
支持所有 ES 5.1 数学方法。  
在 CloudFront Functions 运行时环境中，`Math.random()` 实现使用植入有函数运行时间戳的 OpenBSD `arc4random`。
支持以下 ES 6 数学属性：  
+ `Math.E`
+ `Math.LN10`
+ `Math.LN2`
+ `Math.LOG10E`
+ `Math.LOG2E`
+ `Math.PI`
+ `Math.SQRT1_2`
+ `Math.SQRT2`
支持以下 ES 6 数学方法：  
+ `Math.abs()`
+ `Math.acos()`
+ `Math.acosh()`
+ `Math.asin()`
+ `Math.asinh()`
+ `Math.atan()`
+ `Math.atan2()`
+ `Math.atanh()`
+ `Math.cbrt()`
+ `Math.ceil()`
+ `Math.clz32()`
+ `Math.cos()`
+ `Math.cosh()`
+ `Math.exp()`
+ `Math.expm1()`
+ `Math.floor()`
+ `Math.fround()`
+ `Math.hypot()`
+ `Math.imul()`
+ `Math.log()`
+ `Math.log1p()`
+ `Math.log2()`
+ `Math.log10()`
+ `Math.max()`
+ `Math.min()`
+ `Math.pow()`
+ `Math.random()`
+ `Math.round()`
+ `Math.sign()`
+ `Math.sinh()`
+ `Math.sin()`
+ `Math.sqrt()`
+ `Math.tan()`
+ `Math.tanh()`
+ `Math.trunc()`

**日期**  
支持所有 ES 5.1 `Date` 功能。  
出于安全原因，在单个函数运行的生命周期内，`Date` 始终返回相同的值（函数的开始时间）。有关更多信息，请参阅 [受限功能](functions-javascript-runtime-10.md#writing-functions-javascript-features-restricted-features)。

**函数**  
支持以下 ES 5.1 原型方法：  
+ `Function.prototype.apply()`
+ `Function.prototype.bind()`
+ `Function.prototype.call()`
不支持函数构造函数。

**正则表达式**  
支持所有 ES 5.1 正则表达式功能。正则表达式语言与 Perl 兼容。  
支持以下 ES 5.1 原型存取器属性：  
+ `RegExp.prototype.global`
+ `RegExp.prototype.ignoreCase`
+ `RegExp.protoype.multiline`
+ `RegExp.protoype.source`
+ `RegExp.prototype.sticky`
+ `RegExp.prototype.flags`
**注意**  
`RegExp.prototype.sticky` 和 `RegExp.prototype.flags` 是 JavaScript 运行时系统 2.0 中的新功能。
支持以下 ES 5.1 原型方法：  
+ `RegExp.prototype.exec()`
+ `RegExp.prototype.test()`
+ `RegExp.prototype.toString()`
+ `RegExp.prototype[@@replace]()`
+ `RegExp.prototype[@@split]()`
**注意**  
`RegExp.prototype[@@split]()` 是 JavaScript 运行时系统 2.0 中的新功能。
支持以下 ES 5.1 实例属性：  
+ `lastIndex`
支持 ES 9 命名的捕获组。

**JSON**  
支持以下 ES 5.1 方法：  
+ `JSON.parse()`
+ `JSON.stringify()`

**数组**  
支持对数组使用以下 ES 5.1 方法：  
+ `Array.isArray()`
支持对数组使用以下 ES 6 方法：  
+ `Array.of()`
支持以下 ES 5.1 原型方法：  
+ `Array.prototype.concat()`
+ `Array.prototype.every()`
+ `Array.prototype.filter()`
+ `Array.prototype.forEach()`
+ `Array.prototype.indexOf()`
+ `Array.prototype.join()`
+ `Array.prototype.lastIndexOf()`
+ `Array.prototype.map()`
+ `Array.prototype.pop()`
+ `Array.prototype.push()`
+ `Array.prototype.reduce()`
+ `Array.prototype.reduceRight()`
+ `Array.prototype.reverse()`
+ `Array.prototype.shift()`
+ `Array.prototype.slice()`
+ `Array.prototype.some()`
+ `Array.prototype.sort()`
+ `Array.prototype.splice()`
+ `Array.prototype.unshift()`
支持以下 ES 6 原型方法  
+ `Array.prototype.copyWithin()`
+ `Array.prototype.fill()`
+ `Array.prototype.find()`
+ `Array.prototype.findIndex()`
支持以下 ES 7 原型方法：  
+ `Array.prototype.includes()`

**类型化数组**  
支持以下 ES 6 类型化数组构造函数：  
+ `Float32Array`
+ `Float64Array`
+ `Int8Array`
+ `Int16Array`
+ `Int32Array`
+ `Uint8Array`
+ `Uint8ClampedArray`
+ `Uint16Array`
+ `Uint32Array`
支持以下 ES 6 方法：  
+ `TypedArray.from()`
+ `TypedArray.of()`
**注意**  
`TypedArray.from()` 和 `TypedArray.of()` 是 JavaScript 运行时系统 2.0 中的新功能。
支持以下 ES 6 原型方法：  
+ `TypedArray.prototype.copyWithin()`
+ `TypedArray.prototype.every()`
+ `TypedArray.prototype.fill()`
+ `TypedArray.prototype.filter()`
+ `TypedArray.prototype.find()`
+ `TypedArray.prototype.findIndex()`
+ `TypedArray.prototype.forEach()`
+ `TypedArray.prototype.includes()`
+ `TypedArray.prototype.indexOf()`
+ `TypedArray.prototype.join()`
+ `TypedArray.prototype.lastIndexOf()`
+ `TypedArray.prototype.map()`
+ `TypedArray.prototype.reduce()`
+ `TypedArray.prototype.reduceRight()`
+ `TypedArray.prototype.reverse()`
+ `TypedArray.prototype.some()`
+ `TypedArray.prototype.set()`
+ `TypedArray.prototype.slice()`
+ `TypedArray.prototype.sort()`
+ `TypedArray.prototype.subarray()`
+ `TypedArray.prototype.toString()`
**注意**  
`TypedArray.prototype.every()`、`TypedArray.prototype.fill()`、`TypedArray.prototype.filter()`、`TypedArray.prototype.find()`、`TypedArray.prototype.findIndex()`、`TypedArray.prototype.forEach()`、`TypedArray.prototype.includes()`、`TypedArray.prototype.indexOf()`、`TypedArray.prototype.join()`、`TypedArray.prototype.lastIndexOf()`、`TypedArray.prototype.map()`、`TypedArray.prototype.reduce()`、`TypedArray.prototype.reduceRight()`、`TypedArray.prototype.reverse()` 和 `TypedArray.prototype.some()` 是 JavaScript 运行时系统 2.0 中的新功能。

**ArrayBuffer**  
支持对 ArrayBuffer 使用以下 ES 6 方法：  
+ `isView()`
支持对 ArrayBuffer 使用以下 ES 6 原型方法：  
+ `ArrayBuffer.prototype.slice()`

**Promise**  
支持对 Promise 使用 ES 6 方法：  
+ `Promise.all()`
+ `Promise.allSettled()`
+ `Promise.any()`
+ `Promise.reject()`
+ `Promise.resolve()`
+ `Promise.race()`
**注意**  
`Promise.all()`、`Promise.allSettled()`、`Promise.any()` 和 `Promise.race()` 是 JavaScript 运行时系统 2.0 中的新功能。
支持对 Promise 使用 ES 6 原型方法：  
+ `Promise.prototype.catch()`
+ `Promise.prototype.finally()`
+ `Promise.prototype.then()`

**DataView**  
支持以下 ES 6 原型方法：  
+ `DataView.prototype.getFloat32()`
+ `DataView.prototype.getFloat64()`
+ `DataView.prototype.getInt16()`
+ `DataView.prototype.getInt32()`
+ `DataView.prototype.getInt8()`
+ `DataView.prototype.getUint16()`
+ `DataView.prototype.getUint32()`
+ `DataView.prototype.getUint8()`
+ `DataView.prototype.setFloat32()`
+ `DataView.prototype.setFloat64()`
+ `DataView.prototype.setInt16()`
+ `DataView.prototype.setInt32()`
+ `DataView.prototype.setInt8()`
+ `DataView.prototype.setUint16()`
+ `DataView.prototype.setUint32()`
+ `DataView.prototype.setUint8()`
**注意**  
所有 Dataview ES 6 原型方法都是 JavaScript 运行时系统 2.0 中的新功能。

**符号**  
支持以下 ES 6 方法：  
+ `Symbol.for()`
+ `Symbol.keyfor()`
**注意**  
所有符号 ES 6 方法都是 JavaScript 运行时系统 2.0 中的新功能。

**文本解码器**  
支持以下原型方法：  
+ `TextDecoder.prototype.decode()`
支持以下原型存取器属性：  
+ `TextDecoder.prototype.encoding`
+ `TextDecoder.prototype.fatal`
+ `TextDecoder.prototype.ignoreBOM`

**文本编码器**  
支持以下原型方法：  
+ `TextEncoder.prototype.encode()`
+ `TextEncoder.prototype.encodeInto()`

## 错误类型
<a name="writing-functions-javascript-features-error-types-20"></a>

支持以下错误对象：
+ `Error`
+ `EvalError`
+ `InternalError`
+ `RangeError`
+ `ReferenceError`
+ `SyntaxError`
+ `TypeError`
+ `URIError`

## 全局变量
<a name="writing-functions-javascript-features-globals-20"></a>

支持 `globalThis` 对象。

支持以下 ES 5.1 全局函数：
+ `decodeURI()`
+ `decodeURIComponent()`
+ `encodeURI()`
+ `encodeURIComponent()`
+ `isFinite()`
+ `isNaN()`
+ `parseFloat()`
+ `parseInt()`

支持以下 ES 6 全局函数：
+ `atob()`
+ `btoa()`
**注意**  
`atob()` 和 `btoa()` 是 JavaScript 运行时系统 2.0 中的新功能。

支持以下全局常数：
+ `NaN`
+ `Infinity`
+ `undefined`
+ `arguments`

## 内置模块
<a name="writing-functions-javascript-features-builtin-modules-20"></a>

支持以下内置模块。

**Topics**
+ [Buffer](#writing-functions-javascript-features-builtin-modules-buffer-20)
+ [查询字符串](#writing-functions-javascript-features-builtin-modules-query-string-20)
+ [加密](#writing-functions-javascript-features-builtin-modules-crypto-20)

### Buffer
<a name="writing-functions-javascript-features-builtin-modules-buffer-20"></a>

该模块提供了以下方法：
+ `Buffer.alloc(size[, fill[, encoding]])`

  分配 `Buffer`。
  + `size`：缓冲区大小。输入一个整数。
  + `fill`：可选。输入字符串、`Buffer`、Uint8Array 或整数。默认值为 `0`。
  + `encoding`：可选。如果 `fill` 是字符串，请输入以下值之一：`utf8`、`hex`、`base64`、`base64url`。默认值为 `utf8`。
+ `Buffer.allocUnsafe(size)`

  分配一个未初始化的 `Buffer`。
  + `size`：输入一个整数。
+ `Buffer.byteLength(value[, encoding])`

  返回值的长度，以字节为单位。
  + `value`：字符串、`Buffer`、TypedArray、Dataview 或 Arraybuffer。
  + `encoding`：可选。如果 `value` 是字符串，请输入以下值之一：`utf8`、`hex`、`base64`、`base64url`。默认值为 `utf8`。
+ `Buffer.compare(buffer1, buffer2)`

  比较两个 `Buffer` 以帮助对数组进行排序。如果它们相同，则返回 `0`，如果 `buffer1` 排在第一位，则返回 `-1`，如果 `buffer2` 排在第一位，则返回 `1`。
  + `buffer1`：输入一个 `Buffer`。
  + `buffer2`：输入不同的 `Buffer`。
+ `Buffer.concat(list[, totalLength])`

  连接多个 `Buffer`。如果为无，则返回 `0`。最多可返回 `totalLength`。
  + `list`：输入 `Buffer` 的列表。请注意，这将被截断为 `totalLength`。
  + `totalLength`：可选。输入一个无符号整数。如果为空，则使用列表中的 `Buffer` 实例总和。
+ `Buffer.from(array)`

  从数组中创建 `Buffer`。
  + `array`：输入从 `0` 到 `255` 的字节数组。
+ `Buffer.from(arrayBuffer, byteOffset[, length]))`

  从 `arrayBuffer` 中创建视图，从偏移量 `byteOffset` 开始，长度为 `length`。
  + `arrayBuffer`：输入 `Buffer` 数组。
  + `byteOffset`：输入一个整数。
  + `length`：可选。输入一个整数。
+ `Buffer.from(buffer)`

  创建 `Buffer` 的副本。
  + `buffer`：输入一个 `Buffer`。
+ `Buffer.from(object[, offsetOrEncoding[, length]])`

  从对象创建 `Buffer`。如果 `valueOf()` 不等于对象，则返回 `Buffer.from(object.valueOf(), offsetOrEncoding, length)`。
  + `object`：输入一个对象。
  + `offsetOrEncoding`：可选。输入整数或编码字符串。
  + `length`：可选。输入一个整数。
+ `Buffer.from(string[, encoding])`

  从字符串创建 `Buffer`。
  + `string`：输入一个字符串。
  + `encoding`：可选。输入以下值之一：`utf8`、`hex`、`base64`、`base64url`。默认值为 `utf8`。
+ `Buffer.isBuffer(object)`

  检查 `object` 是否为缓冲区。返回 `true` 或 `false`。
  + `object`：输入一个对象。
+ `Buffer.isEncoding(encoding)`

  检查 `encoding` 是否受支持。返回 `true` 或 `false`。
  + `encoding`：可选。输入以下值之一：`utf8`、`hex`、`base64`、`base64url`。默认值为 `utf8`。

该模块提供以下缓冲区原型方法：
+ `Buffer.prototype.compare(target[, targetStart[, targetEnd[, sourceStart[, sourceEnd]]]])`

  将 `Buffer` 与目标进行比较。如果它们相同，则返回 `0`，如果 `buffer` 排在第一位，则返回 `1`，如果 `target` 排在第一位，则返回 `-1`。
  + `target`：输入一个 `Buffer`。
  + `targetStart`：可选。输入一个整数。默认值为 0。
  + `targetEnd`：可选。输入一个整数。默认为 `target` 长度。
  + `sourceStart`：可选。输入一个整数。默认值为 0。
  + `sourceEnd`：可选。输入一个整数。默认为 `Buffer` 长度。
+ `Buffer.prototype.copy(target[, targetStart[, sourceStart[, sourceEnd]]])`

  将缓冲区复制到 `target`。
  + `target`：输入 `Buffer` 或 `Uint8Array`。
  + `targetStart`：可选。输入一个整数。默认值为 0。
  + `sourceStart`：可选。输入一个整数。默认值为 0。
  + `sourceEnd`：可选。输入一个整数。默认为 `Buffer` 长度。
+ `Buffer.prototype.equals(otherBuffer)`

  将 `Buffer` 与 `otherBuffer` 进行比较。返回 `true` 或 `false`。
  + `otherBuffer`：输入一个字符串。
+ `Buffer.prototype.fill(value[, offset[, end][, encoding])`

  使用 `value` 填充 `Buffer`。
  + `value`：输入字符串、`Buffer` 或整数。
  + `offset`：可选。输入一个整数。
  + `end`：可选。输入一个整数。
  + `encoding`：可选。输入以下值之一：`utf8`、`hex`、`base64`、`base64url`。默认值为 `utf8`。
+ `Buffer.prototype.includes(value[, byteOffset][, encoding])`

  在 `Buffer` 中搜索 `value`。返回 `true` 或 `false`。
  + `value`：输入字符串、`Buffer`、`Uint8Array` 或整数。
  + `byteOffset`：可选。输入一个整数。
  + `encoding`：可选。输入以下值之一：`utf8`、`hex`、`base64`、`base64url`。默认值为 `utf8`。
+ `Buffer.prototype.indexOf(value[, byteOffset][, encoding])`

  在 `Buffer` 中搜索第一个 `value`。如果找到，则返回 `index`；如果找不到，则返回 `-1`。
  + `value`：输入一个字符串、`Buffer`、Unit8Array 或 0 到 255 之间的整数。
  + `byteOffset`：可选。输入一个整数。
  + `encoding`：可选。如果 `value` 是字符串，则输入以下值之一：`utf8`、`hex`、`base64`、`base64url`。默认值为 `utf8`。
+ `Buffer.prototype.lastIndexOf(value[, byteOffset][, encoding])`

  在 `Buffer` 中搜索最后一个 `value`。如果找到，则返回 `index`；如果找不到，则返回 `-1`。
  + `value`：输入一个字符串、`Buffer`、Unit8Array 或 0 到 255 之间的整数。
  + `byteOffset`：可选。输入一个整数。
  + `encoding`：可选。如果 `value` 是字符串，则输入以下值之一：`utf8`、`hex`、`base64`、`base64url`。默认值为 `utf8`。
+ `Buffer.prototype.readInt8(offset)`

  从 `Buffer` 中按 `offset` 偏移量读取 `Int8`。
  + `offset`：输入一个整数。
+ `Buffer.prototype.readIntBE(offset, byteLength)`

  从 `Buffer` 中按 `offset` 偏移量以大端序形式读取 `Int`。
  + `offset`：输入一个整数。
  + `byteLength`：可选。输入一个从 `1` 到 `6` 的整数。
+ `Buffer.prototype.readInt16BE(offset)`

  从 `Buffer` 中按 `offset` 偏移量以大端序形式读取 `Int16`。
  + `offset`：输入一个整数。
+ `Buffer.prototype.readInt32BE(offset)`

  从 `Buffer` 中按 `offset` 偏移量以大端序形式读取 `Int32`。
  + `offset`：输入一个整数。
+ `Buffer.prototype.readIntLE(offset, byteLength)`

  从 `Buffer` 中按 `offset` 偏移量以小端序形式读取 `Int`。
  + `offset`：输入一个整数。
  + `byteLength`：输入一个从 `1` 到 `6` 的整数。
+ `Buffer.prototype.readInt16LE(offset)`

  从 `Buffer` 中按 `offset` 偏移量以小端序形式读取 `Int16`。
  + `offset`：输入一个整数。
+ `Buffer.prototype.readInt32LE(offset)`

  从 `Buffer` 中按 `offset` 偏移量以小端序形式读取 `Int32`。
  + `offset`：输入一个整数。
+ `Buffer.prototype.readUInt8(offset)`

  从 `Buffer` 中按 `offset` 偏移量读取 `UInt8`。
  + `offset`：输入一个整数。
+ `Buffer.prototype.readUIntBE(offset, byteLength)`

  从 `Buffer` 中按 `offset` 偏移量以大端序形式读取 `UInt`。
  + `offset`：输入一个整数。
  + `byteLength`：输入一个从 `1` 到 `6` 的整数。
+ `Buffer.prototype.readUInt16BE(offset)`

  从 `Buffer` 中按 `offset` 偏移量以大端序形式读取 `UInt16`。
+ 
  + `offset`：输入一个整数。
+ `Buffer.prototype.readUInt32BE(offset)`

  从 `Buffer` 中按 `offset` 偏移量以大端序形式读取 `UInt32`。
  + `offset`：输入一个整数。
+ `Buffer.prototype.readUIntLE(offset, byteLength)`

  从 `Buffer` 中按 `offset` 偏移量以小端序形式读取 `UInt`。
  + `offset`：输入一个整数。
  + `byteLength`：输入一个从 `1` 到 `6` 的整数。
+ `Buffer.prototype.readUInt16LE(offset)`

  从 `Buffer` 中按 `offset` 偏移量以小端序形式读取 `UInt16`。
  + `offset`：输入一个整数。
+ `Buffer.prototype.readUInt32LE(offset)`

  从 `Buffer` 中按 `offset` 偏移量以小端序形式读取 `UInt32`。
  + `offset`：输入一个整数。
+ `Buffer.prototype.readDoubleBE([offset])`

  从 `Buffer` 中按 `offset` 偏移量以大端序形式读取 64 位双精度浮点数。
  + `offset`：可选。输入一个整数。
+ `Buffer.prototype.readDoubleLE([offset])`

  从 `Buffer` 中按 `offset` 偏移量以小端序形式读取 64 位双精度浮点数。
  + `offset`：可选。输入一个整数。
+ `Buffer.prototype.readFloatBE([offset])`

  从 `Buffer` 中按 `offset` 偏移量以大端序形式读取 32 位浮点数。
  + `offset`：可选。输入一个整数。
+ `Buffer.prototype.readFloatLE([offset])`

  从 `Buffer` 中按 `offset` 偏移量以小端序形式读取 32 位浮点数。
  + `offset`：可选。输入一个整数。
+ `Buffer.prototype.subarray([start[, end]])`

  返回 `Buffer` 的一个副本，该副本经过偏移并用新的 `start` 和 `end` 进行裁剪。
  + `start`：可选。输入一个整数。默认值为 0。
  + `end`：可选。输入一个整数。默认为缓冲区长度。
+ `Buffer.prototype.swap16()`

  交换 `Buffer` 数组字节顺序，将其视为一个 16 位数字的数组。`Buffer` 长度必须可以被 2 整除，否则您将收到错误。
+ `Buffer.prototype.swap32()`

  交换 `Buffer` 数组字节顺序，将其视为一个 32 位数字的数组。`Buffer` 长度必须可以被 4 整除，否则您将收到错误。
+ `Buffer.prototype.swap64()`

  交换 `Buffer` 数组字节顺序，将其视为一个 64 位数字的数组。`Buffer` 长度必须可以被 8 整除，否则您将收到错误。
+ `Buffer.prototype.toJSON()`

  以 JSON 格式返回 `Buffer`。
+ `Buffer.prototype.toString([encoding[, start[, end]]])`

  将 `Buffer` 从 `start` 到 `end` 转换为编码字符串。
  + `encoding`：可选。输入以下值之一：`utf8`、`hex`、`base64` 或 `base64url`。默认值为 `utf8`。
  + `start`：可选。输入一个整数。默认值为 0。
  + `end`：可选。输入一个整数。默认为缓冲区长度。
+ `Buffer.prototype.write(string[, offset[, length]][, encoding])`

  如果有空格，则将编码的 `string` 写入 `Buffer`，如果空间不足，则写入截断的 `string`。
  + `string`：输入一个字符串。
  + `offset`：可选。输入一个整数。默认值为 0。
  + `length`：可选。输入一个整数。默认为字符串的长度。
  + `encoding`：可选。（可选）输入以下值之一：`utf8`、`hex`、`base64` 或 `base64url`。默认值为 `utf8`。
+ `Buffer.prototype.writeInt8(value, offset, byteLength)`

  按 `offset` 偏移量将 `byteLength` 的 `Int8` `value` 写入 `Buffer`。
  + `value`：输入一个整数。
  + `offset`：输入一个整数
  + `byteLength`：输入一个从 `1` 到 `6` 的整数。
+ `Buffer.prototype.writeIntBE(value, offset, byteLength)`

  使用大端序按 `offset` 偏移量将 `value` 写入 `Buffer`。
  + `value`：输入一个整数。
  + `offset`：输入一个整数
  + `byteLength`：输入一个从 `1` 到 `6` 的整数。
+ `Buffer.prototype.writeInt16BE(value, offset, byteLength)`

  使用大端序按 `offset` 偏移量将 `value` 写入 `Buffer`。
  + `value`：输入一个整数。
  + `offset`：输入一个整数
  + `byteLength`：输入一个从 `1` 到 `6` 的整数。
+ `Buffer.prototype.writeInt32BE(value, offset, byteLength)`

  使用大端序按 `offset` 偏移量将 `value` 写入 `Buffer`。
  + `value`：输入一个整数。
  + `offset`：输入一个整数
  + `byteLength`：输入一个从 `1` 到 `6` 的整数。
+ `Buffer.prototype.writeIntLE(offset, byteLength)`

  使用小端序按 `offset` 偏移量将 `value` 写入 `Buffer`。
  + `offset`：输入一个整数。
  + `byteLength`：输入一个从 `1` 到 `6` 的整数。
+ `Buffer.prototype.writeInt16LE(offset, byteLength)`

  使用小端序按 `offset` 偏移量将 `value` 写入 `Buffer`。
  + `offset`：输入一个整数。
  + `byteLength`：输入一个从 `1` 到 `6` 的整数。
+ `Buffer.prototype.writeInt32LE(offset, byteLength)`

  使用小端序按 `offset` 偏移量将 `value` 写入 `Buffer`。
  + `offset`：输入一个整数。
  + `byteLength`：输入一个从 `1` 到 `6` 的整数。
+ `Buffer.prototype.writeUInt8(value, offset, byteLength)`

  按 `offset` 偏移量将 `byteLength` 的 `UInt8` `value` 写入 `Buffer`。
  + `value`：输入一个整数。
  + `offset`：输入一个整数
  + `byteLength`：输入一个从 `1` 到 `6` 的整数。
+ `Buffer.prototype.writeUIntBE(value, offset, byteLength)`

  使用大端序按 `offset` 偏移量将 `value` 写入 `Buffer`。
  + `value`：输入一个整数。
  + `offset`：输入一个整数
  + `byteLength`：输入一个从 `1` 到 `6` 的整数。
+ `Buffer.prototype.writeUInt16BE(value, offset, byteLength)`

  使用大端序按 `offset` 偏移量将 `value` 写入 `Buffer`。
  + `value`：输入一个整数。
  + `offset`：输入一个整数
  + `byteLength`：输入一个从 `1` 到 `6` 的整数。
+ `Buffer.prototype.writeUInt32BE(value, offset, byteLength)`

  使用大端序按 `offset` 偏移量将 `value` 写入 `Buffer`。
  + `value`：输入一个整数。
  + `offset`：输入一个整数
  + `byteLength`：输入一个从 `1` 到 `6` 的整数。
+ `Buffer.prototype.writeUIntLE(value, offset, byteLength)`

  使用小端序按 `offset` 偏移量将 `value` 写入 `Buffer`。
  + `value`：输入一个整数。
  + `offset`：输入一个整数
  + `byteLength`：输入一个从 `1` 到 `6` 的整数。
+ `Buffer.prototype.writeUInt16LE(value, offset, byteLength)`

  使用小端序按 `offset` 偏移量将 `value` 写入 `Buffer`。
  + `value`：输入一个整数。
  + `offset`：输入一个整数
  + `byteLength`：输入一个从 `1` 到 `6` 的整数。
+ `Buffer.prototype.writeUInt32LE(value, offset, byteLength)`

  使用小端序按 `offset` 偏移量将 `value` 写入 `Buffer`。
  + `value`：输入一个整数。
  + `offset`：输入一个整数
  + `byteLength`：输入一个从 `1` 到 `6` 的整数。
+ `Buffer.prototype.writeDoubleBE(value, [offset])`

  使用大端序按 `offset` 偏移量将 `value` 写入 `Buffer`。
  + `value`：输入一个整数。
  + `offset`：可选。输入一个整数。默认值为 0。
+ `Buffer.prototype.writeDoubleLE(value, [offset])`

  使用小端序按 `offset` 偏移量将 `value` 写入 `Buffer`。
  + `value`：输入一个整数。
  + `offset`：可选。输入一个整数。默认值为 0。
+ `Buffer.prototype.writeFloatBE(value, [offset])`

  使用大端序按 `offset` 偏移量将 `value` 写入 `Buffer`。
  + `value`：输入一个整数。
  + `offset`：可选。输入一个整数。默认值为 0。
+ `Buffer.prototype.writeFloatLE(value, [offset])`

  使用小端序按 `offset` 偏移量将 `value` 写入 `Buffer`。
  + `value`：输入一个整数。
  + `offset`：可选。输入一个整数。默认值为 0。

支持以下实例方法：
+ `buffer[index]`

  在 `Buffer` 中的 `index` 处获取和设置八位字节（字节）。
  + 获取一个从 `0` 到 `255` 的数字。或者设置一个从 `0` 到 `255` 的数字。

支持以下实例属性：
+ `buffer`

  获取缓冲区的 `ArrayBuffer` 对象。
+ `byteOffset`

  获取缓冲区的 `Arraybuffer` 对象的 `byteOffset`。
+ `length`

  获取缓冲区字节计数。

**注意**  
所有缓冲区模块方法都是 JavaScript 运行时系统 2.0 中的新功能。

### 查询字符串
<a name="writing-functions-javascript-features-builtin-modules-query-string-20"></a>

**注意**  
[CloudFront Functions 事件对象](functions-event-structure.md)自动为您解析 URL 查询字符串。这意味着，在大多数情况下您不需要使用此模块。

查询字符串模块 (`querystring`) 提供了解析和格式化 URL 查询字符串的方法。您可以使用 `require('querystring')` 加载模块。该模块提供了以下方法。

`querystring.escape(string)`  
URL - 对给定的 `string` 进行编码，从而返回转义的查询字符串。该方法由 `querystring.stringify()` 使用，不应直接使用。

`querystring.parse(string[, separator[, equal[, options]]])`  
解析查询字符串 (`string`) 并返回对象。  
`separator` 参数是用于在查询字符串中分隔键和值对的子字符串。默认为 `&`。  
`equal` 参数是用于在查询字符串中分隔键和值的子字符串。默认为 `=`。  
`options` 参数是具有以下键的对象：    
`decodeURIComponent function`  
用于解码查询字符串中百分比编码字符的函数。默认为 `querystring.unescape()`。  
`maxKeys number`  
要解析的最大密钥数。默认为 `1000`。使用 `0` 的值取消键的计数限制。
默认情况下，假定查询字符串中的百分比编码字符使用 UTF-8 编码。无效的 UTF-8 序列将被替换为 `U+FFFD` 替换字符。  
例如，对于以下查询字符串：  

```
'name=value&abc=xyz&abc=123'
```
`querystring.parse()` 的返回值为：  

```
{
name: 'value',
abc: ['xyz', '123']
}
```
`querystring.decode()` 是 `querystring.parse()` 的别名。

`querystring.stringify(object[, separator[, equal[, options]]])`  
序列化 `object` 并返回查询字符串。  
`separator` 参数是用于在查询字符串中分隔键和值对的子字符串。默认为 `&`。  
`equal` 参数是用于在查询字符串中分隔键和值的子字符串。默认为 `=`。  
`options` 参数是具有以下键的对象：    
`encodeURIComponent function`  
用于将 URL 不安全字符转换为查询字符串中的百分比编码的函数。默认为 `querystring.escape()`。
默认情况下，查询字符串中需要百分比编码的字符将编码为 UTF-8。要使用其他编码，请指定 `encodeURIComponent` 选项。  
例如，在下面的代码中：  

```
querystring.stringify({ name: 'value', abc: ['xyz', '123'], anotherName: '' });
```
返回值为：  

```
'name=value&abc=xyz&abc=123&anotherName='
```
`querystring.encode()` 是 `querystring.stringify()` 的别名。

`querystring.unescape(string)`  
对给定的 `string` 中的 URL 百分比编码字符进行解码，以返回未转义的查询字符串。此方法由 `querystring.parse()` 使用，不应直接使用。

### 加密
<a name="writing-functions-javascript-features-builtin-modules-crypto-20"></a>

加密模块 (`crypto`) 提供标准哈希和基于哈希的消息身份验证码 (HMAC) 帮助程序。您可以使用 `require('crypto')` 加载模块。

**哈希方法**

`crypto.createHash(algorithm)`  
创建并返回哈希对象，您可以使用给定的算法：`md5`、`sha1` 或 `sha256` 通过它生成哈希摘要。

`hash.update(data)`  
用给定的 `data` 更新哈希内容。

`hash.digest([encoding])`  
计算使用 `hash.update()` 传递的所有数据的摘要。编码可以是 `hex`、`base64` 或 `base64url`。

**HMAC 方法**

`crypto.createHmac(algorithm, secret key)`  
创建并返回使用给定的 `algorithm` 和 `secret key` 的 HMAC 对象。算法可以是 `md5`、`sha1` 或 `sha256`。

`hmac.update(data)`  
用给定的 `data` 更新 HMAC 内容。

`hmac.digest([encoding])`  
计算使用 `hmac.update()` 传递的所有数据的摘要。编码可以是 `hex`、`base64` 或 `base64url`。

## 受限功能
<a name="writing-functions-javascript-features-restricted-features-20"></a>

由于安全考虑，以下 JavaScript 语言功能或不受支持，或收到限制。

**动态代码评估**  
不支持动态代码评估。如果尝试，`eval()` 和 `Function` 构造函数都会引发错误。例如，`const sum = new Function('a', 'b', 'return a + b')` 引发错误。

**计时器**  
不支持 `setTimeout()`、`setImmediate()` 和 `clearTimeout()` 函数。在函数运行中没有可以推迟或生成的预置。您的函数必须同步运行才能完成。

**日期和时间戳**  
出于安全原因，无法访问高分辨率计时器。在单个函数运行的生命周期内，查询当前时间的所有 `Date` 方法始终返回相同的值。返回的时间戳是函数开始运行的时间。因此，您无法度量函数中的经过时间。

**文件系统访问**  
没有文件系统访问权限。例如，没有像 Node.js 的 `fs` 模块可以进行文件系统访问。

**处理访问权限**  
没有处理访问权限。例如，没有像 Node.js 中那样用于处理信息访问权限的 `process` 全局对象。

**环境变量**  
无法访问环境变量。相反，可以使用 CloudFront KeyValueStore 为 CloudFront Functions 创建集中式的键值对数据存储。借助 CloudFront KeyValueStore，无需部署代码更改即对配置数据进行动态更新。有关更多信息，请参阅 [Amazon CloudFront KeyValueStore](kvs-with-functions.md)。

**网络访问**  
不支持网络调用。例如，不支持 XHR、HTTP (S) 和套接字。

# 键值存储的帮助程序方法
<a name="functions-custom-methods"></a>

**注意**  
来自 CloudFront Functions 的键值存储辅助方法调用不会触发 AWS CloudTrail 数据事件。这些事件不会记录在 CloudTrail 事件历史记录中。有关更多信息，请参阅 [使用 AWS CloudTrail 记录 Amazon CloudFront API 调用](logging_using_cloudtrail.md)。

如果您使用 [CloudFront 键值存储](kvs-with-functions.md)在您创建的函数中包含键值，则本节适用。CloudFront Functions 有一个模块，该模块提供了三种用于从键值存储中读取值的帮助程序方法。

要在函数代码中使用此模块，请确保已将[相关键值存储](kvs-with-functions-associate.md)与该函数关联。

接下来，在函数代码的第一行中添加以下语句：

```
import cf from 'cloudfront';
const kvsHandle = cf.kvs();
```



## `get()` 方法
<a name="functions-custom-methods-get"></a>

使用此方法可返回您指定的键名称的键值。

**请求**

```
get("key", options);
```
+ `key`：需要提取其值的键的名称
+ `options`：包含一个选项 `format`。它可以确保函数正确地解析数据。可能的值：
  + `string`：（默认）UTF8 编码
  + `json` 
  + `bytes`：原始二进制数据缓冲区

**请求示例**

```
const value = await kvsHandle.get("myFunctionKey", { format: "string"});
```

**响应**

响应是以使用 `options` 请求的格式解析为值的 `promise`。默认情况下，此值以字符串的形式返回。

### 错误处理
<a name="error-handling-exists-method"></a>

当您请求的键在关联的键值存储中不存在时，`get()` 方法将返回错误。要管理此使用案例，可以在代码中添加 `try` 和 `catch` 块。

**警告**  
使用 Promise 组合器（例如 `Promise.all`、`Promise.any`）和 Promise 链方法（例如 `then` 和 `catch`）可能需要大量的函数内存使用量。如果您的函数超过了[最大函数内存](cloudfront-limits.md#limits-functions)配额，它将无法执行。为避免出现此错误，我们建议您按顺序或循环使用 `await` 语法来请求多个值。  
**示例**  

```
var value1 = await kvs.get('key1');
var value2 = await kvs.get('key2');
```
目前，使用 Promise 组合器获取多个值并不能提高性能，如下例所示。  

```
var values = await Promise.all([kvs.get('key1'), kvs.get('key2'),]);
```

## `exists()` 方法
<a name="functions-custom-methods-exists"></a>

使用该方法可确定此键在键值存储中是否存在。

**请求**

```
exists("key");
```

**请求示例**

```
const exist = await kvsHandle.exists("myFunctionkey");
```

**响应**

响应是返回布尔值（`true` 或 `false`）的 `promise`。该值指定此键在键值存储中是否存在。

## `meta()` 方法
<a name="functions-custom-methods-meta"></a>

使用此方法返回有关键值存储的元数据。

**请求**

```
meta();
```

**请求示例**

```
const meta = await kvsHandle.meta();
```

**响应**

响应是解析为具有以下属性的对象的 `promise`：
+ `creationDateTime`：创建键值存储的日期和时间，采用 ISO 8601 格式。
+ `lastUpdatedDateTime`：上次从源中同步键值存储的日期和时间，采用 ISO 8601 格式。该值不包括向边缘传播的时间。
+ `keyCount`：上次从源同步后 KVS 中的键总数。

**响应示例**

```
{keyCount:3,creationDateTime:2023-11-30T23:07:55.765Z,lastUpdatedDateTime:2023-12-15T03:57:52.411Z}
```

# 源修改的辅助方法
<a name="helper-functions-origin-modification"></a>

如果您在 CloudFront Functions 代码中动态更新或更改请求中使用的源，则本节适用。您只能在*查看器请求* CloudFront Functions 上更新源。CloudFront Functions 有一个模块，模块提供了用于动态更新或更改源的辅助方法。

要使用此模块，请使用 JavaScript 运行时 2.0 创建 CloudFront 函数，并在函数代码的第一行包含以下语句：

```
import cf from 'cloudfront';
```

有关更多信息，请参阅 [适用于 CloudFront Functions 的 JavaScript 运行时系统 2.0 特征](functions-javascript-runtime-20.md)。

**注意**  
Test API 和 Test 控制台页面不测试是否修改了源。但是，测试可确保函数代码的执行没有错误。

## 在 CloudFront Functions 和 Lambda@Edge 之间进行选择
<a name="origin-modification-considerations"></a>

您可以使用 CloudFront Functions 或 Lambda@Edge 来更新源。

使用 CloudFront Functions 更新源时，您可以使用*查看器请求*事件触发器，这意味着使用此函数时，此逻辑将在每个请求上运行。使用 Lambda@Edge 时，源更新功能在*源请求*事件触发器上，这意味着此逻辑仅在缓存未命中时运行。

您的选择在很大程度上取决于您的工作负载以及您的分配中目前使用的是 CloudFront Functions 还是 Lambda@Edge。您可以参考注意事项，来决定使用 CloudFront Functions 还是 Lambda@Edge 更新您的源。

CloudFront Functions 在以下情况中更为有用：
+ 当您的请求是动态的（意味着无法缓存这些请求）并且将始终转向源时。CloudFront Functions 可提供更好的性能和更低的总体成本。
+ 如果您现在已有查看器请求 CloudFront 函数并将在每个请求上运行，则可以将源更新逻辑添加到现有函数中。

要使用 CloudFront Functions 更新源，请参阅以下主题中的辅助方法。

Lambda@Edge 在以下情况中更为有用：
+ 当您拥有可以很好地缓存的内容时，Lambda@Edge 更具成本效益，因为它仅在缓存未命中时运行，而 CloudFront Functions 在每次请求时都会运行。
+ 如果您现在已有查看器请求 Lambda@Edge 函数，则可以将源更新逻辑添加到现有函数中。
+ 当您的源更新逻辑需要从第三方数据来源（例如 Amazon DynamoDB 或 Amazon S3）提取数据时。

有关 Lambda@Edge 的更多信息，请参阅[使用 Lambda@Edge 在边缘进行自定义](lambda-at-the-edge.md)。

## updateRequestOrigin() 方法
<a name="update-request-origin-helper-function"></a>

使用 `updateRequestOrigin()` 方法更新请求的源设置。您可以使用此方法，更新分配中已定义源的现有源属性，或者为请求定义新的源。为此，请指定要更改的属性。

**重要**  
未在 `updateRequestOrigin()` 中指定的任何设置都将从现有源的配置继承*相同的设置*。

`updateRequestOrigin()` 方法设置的源可以是任意 HTTP 端点，并且不必是您的 CloudFront 分配中的现有源。

**备注**  
如果您要更新某个属于源组的源，则仅更新源组的*主源*。辅助源保持不变。来自已修改源的任何符合失效转移条件的响应代码都将触发到辅助源的失效转移。
如果要更改源类型并启用 OAC，请确保 `originAccessControlConfig` 中的源类型与新的源类型相匹配。
您不能使用 `updateRequestOrigin()` 方法更新 [VPC 源](private-content-vpc-origins.md)。请求将失败。

**请求**

```
updateRequestOrigin({origin properties})
```

`origin properties` 可包含以下内容：

**domainName（可选）**  
源的域名。如果未提供此信息，则改为使用分配的源的域名。    
**对于自定义源**  
指定 DNS 域名，例如 `www.example.com`。域名不能包含冒号（`:`），也不能为 IP 地址。域名最多可以有 253 个字符。  
**对于 S3 源**  
指定 Amazon S3 存储桶的 DNS 域名，例如 `amzn-s3-demo-bucket.s3.eu-west-1.amazonaws.com`。名称必须最多为 128 个字符，并且必须为全小写。

**hostHeader（可选，适用于非 S3 自定义源）**  
向源发出请求时使用的主机标头。如果未提供此值，则使用 domainName 参数中的值。如果既未提供主机标头也未提供域名参数，则使用来自分配的源的域名，或者如果转发到源（FTO）策略包括主机，则使用来自传入请求的主机标头。主机标头不能包含冒号（`:`），也不能为 IP 地址。主机标头最多可以有 253 个字符。

**originPath（可选）**  
源上的目录路径，请求应在其中查找内容。路径应该以正斜杠（/）开头，但不应该以正斜杠结尾。例如，路径结尾不能是 `example-path/`。如果未提供此信息，则使用所分配源的源路径。    
**对于自定义源**  
路径应为 URL 编码，最大长度为 255 个字符。

**customHeaders（可选）**  
您可以通过为每个自定义标头指定标头名称/值对，在请求中包括自定义标头。其格式与事件结构中请求和响应标头的格式不同。使用以下键/值对语法：  

```
{"key1": "value1", "key2": "value2", ...}
```
您不能添加不允许使用的标头，并且传入请求 `headers` 中不能存在同名标头。在函数代码中，标头名称必须为小写。当 CloudFront Functions 将事件对象转换回 HTTP 请求时，标头名称中每个单词的第一个字母都会大写，使用连字符分隔单词。  
例如，如果您的函数代码添加了名为 `example-header-name` 的标头，CloudFront 会将其转换为 HTTP 请求中的 `Example-Header-Name`。有关更多信息，请参阅[CloudFront 无法添加到源请求的自定义标头](add-origin-custom-headers.md#add-origin-custom-headers-denylist)和[边缘函数的限制](edge-functions-restrictions.md)。  
如果未提供此信息，则使用分配的源中的任意自定义标头。

**connectionAttempts（可选）**  
CloudFront 尝试连接到源的次数。最小值为 1，最大值为 3。如果未提供此信息，则使用分配的源中的连接尝试次数。

**originShield（可选）**  
此项启用或更新 CloudFront 源护盾。使用 Origin Shield 有助于减少源上的负载。有关更多信息，请参阅 [使用 Amazon CloudFront Origin Shield](origin-shield.md)。如果未提供此信息，则使用所分配源的源护盾设置。    
**enabled（必需）**  
用于启用或禁用源护盾的布尔表达式。接受的值为 `true` 或 `false`。  
**region（启用时为必需）**  
Origin Shield 的 AWS 区域。指定到源的延迟最低的 AWS 区域。使用区域代码，而不是区域名称。例如，使用 `us-east-2` 指定美国东部（俄亥俄州）区域。  
启用 CloudFront 源护盾时，您必须为其指定 AWS 区域。有关可用 AWS 区域列表以及帮助您为源选择合适区域的信息，请参阅[为 Origin Shield 选择 AWS 区域](origin-shield.md#choose-origin-shield-region)。

**originAccessControlConfig（可选）**  
此源的源访问控制（OAC，Origin Access Control）的唯一标识符。只有当源支持 CloudFront OAC（例如 Amazon S3、Lambda 函数 URL、MediaStore 和 MediaPackage V2）时，才使用此选项。如果未提供此信息，则使用分配的源的 OAC 设置。  
此项不支持传统来源访问身份（OAI）。有关更多信息，请参阅 [限制对AWS源的访问](private-content-restricting-access-to-origin.md)。    
**enabled（必需）**  
用于启用或禁用 OAC 的布尔表达式。接受的值为 `true` 或 `false`。  
**signingBehavior（启用时为必需）**  
指定 CloudFront 签署（将身份验证信息添加到）哪些请求。为最常见的使用案例指定 `always`。有关更多信息，请参阅 [源访问控制的高级设置](private-content-restricting-access-to-s3.md#oac-advanced-settings-s3)。  
此字段可能具有下列值之一：  
+ `always` - CloudFront 签署所有源请求，覆盖来自查看器请求的 `Authorization` 标头（如果存在）。
+ `never` – CloudFront 不签署任何源请求。此值将关闭源的源访问控制。
+ `no-override` – 如果查看器请求不包含 `Authorization` 标头，则 CloudFront 签署源请求。如果查看器请求包含 `Authorization` 标头，则 CloudFront 不会签署源请求，而是传递查看器请求的 `Authorization` 标头。
**警告**  
要传递查看器请求中的 `Authorization` 标头，您必须针对使用与此源访问控制关联的源的所有缓存行为，将标头添加到源请求策略中。有关更多信息，请参阅 [使用策略来控制源请求](controlling-origin-requests.md)。  
**signingProtocol（启用时为必需）**  
OAC 的签名协议，确定 CloudFront 如何签署（身份验证）请求。唯一有效值为 `sigv4`。  
**originType（启用时为必需）**  
此 OAC 的源类型。有效值包括 `s3`、`mediapackagev2`、`mediastore` 和 `lambda`。

**timeouts（可选）**  
超时值，您可以用来指定 CloudFront 尝试等待源响应或发送数据的时间。如果未提供此信息，则使用分配的源的超时设置。  
除非另有说明，否则这些超时同时支持自定义源和 Amazon S3 源。  
**readTimeout（可选）**  
`readTimeout` 适用于以下两个值：  
+ CloudFront 在将请求转发到源后等待响应的时间长度（以秒为单位）。
+ CloudFront 从收到来自源的一个响应数据包到收到下一个数据包之间等待的时间长度（以秒为单位）。
最短超时为 1 秒，最长超时为 120 秒。有关更多信息，请参阅 [响应超时](DownloadDistValuesOrigin.md#DownloadDistValuesOriginResponseTimeout)。  
**responseCompletionTimeout（可选）**  
从 CloudFront 向源发出的请求可以保持打开状态并等待响应的时间（以秒为单位）。如果此时未收到来自源的完整响应，CloudFront 将终止连接。  
`responseCompletionTimeout` 的值必须大于或等于 `readTimeout` 的值。有关更多信息，请参阅 [响应完成超时](DownloadDistValuesOrigin.md#response-completion-timeout)。  
**keepAliveTimeout（可选）**  
此超时仅适用于自定义源，而不适用于 Amazon S3 源。（S3 源配置将忽略这些设置。）   
`keepAliveTimeout` 指定 CloudFront 在收到响应的最后一个数据包后，应尝试与源保持连接的时间长度。最短超时为 1 秒，最长超时为 120 秒。有关更多信息，请参阅 [源保持连接超时（仅自定义源和 VPC 源）](DownloadDistValuesOrigin.md#DownloadDistValuesOriginKeepaliveTimeout)。  
**connectionTimeout（可选）**  
CloudFront 尝试建立与源的连接时等待的秒数。最短超时为 1 秒，最长超时为 10 秒。有关更多信息，请参阅 [连接超时](DownloadDistValuesOrigin.md#origin-connection-timeout)。

**customOriginConfig（可选）**  
使用 `customOriginConfig`，为*不是* Amazon S3 存储桶的源指定连接设置。但有一个例外：如果 S3 存储桶配置了静态网站托管，则您可以指定这些设置。（其他类型的 S3 存储桶配置将忽略这些设置。） 如果未提供 `customOriginConfig`，则使用分配的源的设置。    
**port（必需）**  
CloudFront 用于连接到源的 HTTP 端口。指定源侦听的 HTTP 端口。  
**protocol（必需）**  
指定 CloudFront 用于连接到源的协议（HTTP 或 HTTPS）。有效值如下所示：  
+ `http` – CloudFront 始终使用 HTTP 连接到源。
+ `https` – CloudFront 始终使用 HTTPS 连接到源。  
**sslProtocols（必需）**  
指定 CloudFront 在通过 HTTPS 连接到源时使用的最低 SSL/TLS 协议版本的列表。有效值包括 `SSLv3`、`TLSv1`、`TLSv1.1` 和 `TLSv1.2`。有关更多信息，请参阅 [最低限度源 SSL 协议](DownloadDistValuesOrigin.md#DownloadDistValuesOriginSSLProtocols)。  
**ipAddressType（可选）**  
指定 CloudFront 用于连接到源的 IP 地址类型。有效值包括 `ipv4`、`ipv6` 和 `dualstack`。仅当同时更改 `domainName` 属性时，才支持更改 `ipAddressType`。

**sni（可选，适用于非 S3 自定义源）**  
服务器名称指示（SNI）是传输层安全性协议（TLS）的扩展，客户端可用它在 TLS 握手过程开始时指示尝试连接到的主机名。此值应与原始服务器上的 TLS 证书中的常用名称匹配。否则，原始服务器可能会引发错误。  
如果未提供此值，则使用 `hostHeader` 参数中的值。如果未提供主机标头，则使用 `domainName` 参数中的值。  
如果既未提供主机标头也未提供域名参数，则使用来自分配的源的域名，或者如果转发到源（FTO）策略包括主机，则使用来自传入请求的主机标头。SNI 不能包含冒号（`:`），也不能为 IP 地址。SNI 最多可以有 253 个字符。

**allowedCertificateNames（可选，适用于非 S3 自定义源）**  
您可以配置一个有效证书名称列表，CloudFront 在与原始服务器进行 TLS 握手的过程中，可使用此列表来验证原始服务器 TLS 证书中的域匹配情况。此字段需传入一个有效域名数组，并且可包含通配符域，例如 `*.example.com`。  
最多可指定 20 个允许的证书名称。每个证书名称最多可包含 64 个字符。

**Example – 更新 Amazon S3 请求源**  
以下示例将查看器请求的源更改为 S3 存储桶，启用 OAC，并重置发送到源的自定义标头。  

```
cf.updateRequestOrigin({
    "domainName" : "amzn-s3-demo-bucket-in-us-east-1.s3.us-east-1.amazonaws.com",
    "originAccessControlConfig": {
        "enabled": true,
        "signingBehavior": "always",
        "signingProtocol": "sigv4",
        "originType": "s3"
    },
    // Empty object resets any header configured on the assigned origin
    "customHeaders": {}
});
```

**Example – 更新应用程序负载均衡器请求源**  
以下示例将查看器请求的源更改为应用程序负载均衡器源，并设置自定义标头和超时。  

```
cf.updateRequestOrigin({
    "domainName" : "example-1234567890.us-east-1.elb.amazonaws.com",
    "timeouts": {
        "readTimeout": 30,
        "connectionTimeout": 5
    },
    "customHeaders": {
        "x-stage": "production",
        "x-region": "us-east-1"
    }
});
```

**Example – 更新启用了源护盾的源**  
在以下示例中，分配中的源启用了源护盾。函数代码仅更新用于源的域名，忽略了所有其他可选参数。在这种情况下，由于未更新源护盾参数，源护盾仍将与修改后的源域名一起使用。  

```
cf.updateRequestOrigin({
    "domainName" : "www.example.com"
});
```

**Example ：更新主机标头、SNI 和允许的证书名称**  
对于大多数使用案例，您无需对转至源的请求进行此类修改。除非您了解更改这些值将产生的影响，否则不应使用上述参数。
以下示例将更改转至源的请求中的域名、主机标头、SNI 和允许的证书。  

```
cf.updateRequestOrigin({ 
    "domainName": "www.example.com", 
    "hostHeader": "test.example.com", 
    "sni": "test.example.net", 
    "allowedCertificateNames": ["*.example.com", "*.example.net"],
});
```

## selectRequestOriginById() 方法
<a name="select-request-origin-id-helper-function"></a>

使用 `selectRequestOriginById()` 可通过选择已在您的分配中配置的其它源来更新现有源。此方法使用由更新的源定义的所有相同设置。

此方法只接受在运行函数时使用的同一分配中已经定义的源。源由源 ID 引用，而源 ID 是您在设置源时定义的源名称。

如果您在分配中配置了 VPC 源，则可以使用此方法来将您的源更新为 VPC 源。有关更多信息，请参阅 [通过 VPC 源限制访问](private-content-vpc-origins.md)。

**备注**  
`selectRequestOriginById()` 函数无法选择已启用双向 TLS（源）的源。尝试使用此函数选择已启用双向 TLS（源）的源将导致验证错误。
如果使用案例要求使用双向 TLS（源）进行动态源选择，请改用 `updateRequestOrigin()`，确保所有目标源都使用相同的客户端证书。

**请求**

```
cf.selectRequestOriginById(origin_id, {origin_overrides})
```

在上一示例中，`origin_id` 是一个字符串，它指向运行该函数的分配中源的源名称。`origin_overrides ` 参数可包含：

**hostHeader（可选，适用于非 S3 自定义源）**  
向源发出请求时使用的主机标头。如果未提供此值，则使用 `domainName` 参数中的值。  
如果既未提供主机标头也未提供域名参数，则使用来自分配的源的域名，或者如果转发到源（FTO）策略包括主机，则使用来自传入请求的主机标头。主机标头不能包含冒号（`:`），也不能为 IP 地址。主机标头最多可以有 253 个字符。

**sni（可选，适用于非 S3 自定义源）**  
服务器名称指示（SNI）是传输层安全性协议（TLS）的扩展，客户端可用它在 TLS 握手过程开始时指示尝试连接到的主机名。此值应与原始服务器上的 TLS 证书中的常用名称匹配。否则，原始服务器可能会引发错误。  
如果未提供此值，则使用 `hostHeader` 参数中的值。如果未提供主机标头，则使用 `domainName` 参数中的值。  
如果既未提供主机标头也未提供域名参数，则使用来自分配的源的域名，或者如果转发到源（FTO）策略包括主机，则使用来自传入请求的主机标头。SNI 不能包含冒号（`:`），也不能为 IP 地址。SNI 最多可以有 253 个字符。

**allowedCertificateNames（可选，适用于非 S3 自定义源）**  
您可以配置一个有效证书名称列表，CloudFront 在与原始服务器进行 TLS 握手的过程中，可使用此列表来验证原始服务器 TLS 证书中的域匹配情况。此字段需传入一个有效域名数组，并且可包含通配符域，例如 `*.example.com`。  
最多可指定 20 个允许的证书名称。每个证书名称最多可包含 64 个字符。

**请求**

```
selectRequestOriginById(origin_id)
```

在前面的示例中，`origin_id` 是一个字符串，它指向运行该函数的分配中源的源名称。

**Example – 选择 Amazon S3 请求源**  
以下示例从与分配关联的源列表中选择名为 `amzn-s3-demo-bucket-in-us-east-1` 的源，并将 `amzn-s3-demo-bucket-in-us-east-1` 源的配置设置应用于请求。  

```
cf.selectRequestOriginById("amzn-s3-demo-bucket-in-us-east-1");
```

**Example – 选择应用程序负载均衡器请求源**  
以下示例从与分配关联的源列表中选择名为 `myALB-prod` 的应用程序负载均衡器源，并将 `myALB-prod` 的配置设置应用于请求。  

```
cf.selectRequestOriginById("myALB-prod");
```

**Example ：选择应用程序负载均衡器请求源并覆盖主机标头**  
与上一示例类似，以下示例从与分配关联的源列表中选择名为 `myALB-prod` 的应用程序负载均衡器源，并将 `myALB-prod` 的配置设置应用于请求。但是，此示例使用 `origin_overrides` 覆盖主机标头值。  

```
cf.overrideRequestOrigin("myALB-prod",{ 
        "hostHeader" : "test.example.com"
});
```

## createRequestOriginGroup() 方法
<a name="create-request-origin-group-helper-function"></a>

使用 `createRequestOriginGroup()` 可定义两个源，以便在需要高可用性的场景中用作失效转移的[源组](high_availability_origin_failover.md#concept_origin_groups.creating)。

源组包括两个源（主源和辅助源）以及您指定的失效转移条件。您可以创建一个源组以支持 CloudFront 中的源失效转移。使用此方法创建或更新源组时，您可以指定源组而不是单个源。CloudFront 将使用失效转移条件从主源失效转移到辅助源。

如果您在分配中配置了 VPC 源，则可以使用此方法通过 VPC 源创建源组。有关更多信息，请参阅 [通过 VPC 源限制访问](private-content-vpc-origins.md)。

**备注**  
`createRequestOriginGroup()` 函数不支持创建包含已启用双向 TLS（源）的源的源组。无法通过 CloudFront Functions 动态创建具有双向 TLS（源）源的源组。
如果您需要具有双向 TLS（源）的源失效转移功能，请直接在 CloudFront 分配设置中配置源组，而不是在函数中动态创建源组。

### 请求
<a name="create-origin-group-request"></a>

```
createRequestOriginGroup({origin_group_properties})
```

在前面的示例中，`origin_group_properties` 文件包含以下内容：

**originIds（必需）**  
`origin_ids` 的数组，其中 `origin_id` 是一个字符串，它指向运行该函数的分配中源的源名称。您必须提供两个源来作为数组的一部分。列表中的第一个源是主源，而第二个源用作用于进行失效转移的辅助源。

**originOverrides（可选）**  
 有几个高级设置可以使用 `{origin_overrides}` 参数进行覆盖。`origin overrides` 可包含以下内容：    
**hostHeader（可选，适用于非 S3 自定义源）**  
向源发出请求时使用的主机标头。如果未提供此值，则使用 `domainName` 参数中的值。  
如果既未提供主机标头也未提供域名参数，则使用来自分配的源的域名，或者如果转发到源（FTO）策略包括主机，则使用来自传入请求的主机标头。主机标头不能包含冒号（`:`），也不能为 IP 地址。主机标头最多可以有 253 个字符。  
**sni（可选，适用于非 S3 自定义源）**  
服务器名称指示（SNI）是传输层安全性协议（TLS）的扩展，客户端可用它在 TLS 握手过程开始时指示尝试连接到的主机名。此值应与原始服务器上的 TLS 证书中的常用名称匹配，否则原始服务器可能会引发错误。  
如果未提供此值，则使用 `hostHeader` 参数中的值。如果未提供主机标头，则使用 `domainName` 参数中的值。  
如果既未提供主机标头也未提供域名参数，则使用来自分配的源的域名，或者如果转发到源（FTO）策略包括主机，则使用来自传入请求的主机标头。SNI 不能包含冒号（`:`），也不能为 IP 地址。SNI 最多可以有 253 个字符。  
**allowedCertificateNames（可选，适用于非 S3 自定义源）**  
您可以配置一个有效证书名称列表，CloudFront 在与原始服务器进行 TLS 握手的过程中，可使用此列表来验证原始服务器 TLS 证书中的域匹配情况。此字段需传入一个有效域名数组，并且可包含通配符域，例如 `*.example.com`。  
最多可指定 20 个允许的证书名称。每个证书名称最多可包含 64 个字符。

**selectionCriteria（可选）**  
选择是使用 `default` 源失效转移条件，还是使用基于 `media-quality-score` 的失效转移逻辑。有效值如下所示：  
+ `default` 根据在 `failoverCriteria` 中指定的状态代码，使用失效转移条件。如果您未在函数中设置 `selectionCriteria`，将使用 `default`。
+ 当使用媒体感知路由功能时，使用 `media-quality-score`。

**failoverCriteria（必需）**  
从主源返回时，将触发 CloudFront 失效转移到辅助源的状态代码数组。如果您覆盖现有的源组，此数组将覆盖在源组的原始配置中设置的所有失效转移状态代码。  
当您使用 `media-quality-score` `selectionCriteria` 时，CloudFront 将尝试根据媒体质量分数来路由请求。如果所选源返回在此数组中设置的错误代码，则 CloudFront 将失效转移到另一个源。

**Example – 创建请求源组**  
以下示例使用源 ID 为请求创建源组。这些源 ID 来自用于运行此函数的分配的源组配置。  
（可选）您可以使用 `originOverrides` 覆盖 `sni`、`hostHeader` 和 `allowedCertificateNames` 的源组配置。  

```
import cf from 'cloudfront';

function handler(event) {
    cf.createRequestOriginGroup({
        "originIds": [
            {
                "originId": "origin-1",
                "originOverrides": {
                    "hostHeader": "hostHeader.example.com",
                    "sni": "sni.example.com",
                    "allowedCertificateNames": ["cert1.example.com", "cert2.example.com", "cert3.example.com"]
                }
            },
            {
                "originId": "origin-2",
                "originOverrides": {
                    "hostHeader": "hostHeader2.example.com",
                    "sni": "sni2.example.com",
                    "allowedCertificateNames": ["cert4.example.com", "cert5.example.com"]
                }
            }
        ],
        "failoverCriteria": {
            "statusCodes": [500]
        }
    });
    
    event.request.headers['x-hookx'] = { value: 'origin-overrides' };
    return event.request;
}
```

# CloudFront SaaS Manager 属性的辅助方法
<a name="saas-specific-logic-function-code"></a>

使用以下适用于 CloudFront SaaS Manager 的辅助函数，在您创建的函数中检索多租户分配的值。要使用此页上的示例，您必须先使用 JavaScript 运行时 2.0 创建 CloudFront 函数。有关更多信息，请参阅 [适用于 CloudFront Functions 的 JavaScript 运行时系统 2.0 特征](functions-javascript-runtime-20.md)。

**Topics**
+ [连接组](#connection-groups-helper-function)
+ [分配租户](#distribution-tenants-helper-functions)

## 连接组
<a name="connection-groups-helper-function"></a>

与分配租户关联的连接组具有域名。

要获取此值，请使用事件对象的 `context` 子对象的 `endpoint` 字段。

**请求**

```
const value = event.context.endpoint;
```

**响应**

响应是一个包含连接组域名的 `string`，例如 d111111abcdef8.cloudfront.net。`endpoint` 字段仅在针对具有关联连接组的多租户分配调用您的函数时才会出现。有关更多信息，请参阅 [Context 对象](functions-event-structure.md#functions-event-structure-context)。

## 分配租户
<a name="distribution-tenants-helper-functions"></a>

CloudFront Functions 有一个模块，该模块提供对特定分配租户值的访问权限。

要使用此模块，请在函数代码的第一行中包含以下语句：

```
import cf from 'cloudfront';
```

您只能在 `handler` 函数中使用以下示例，可以直接使用，也可以通过任何嵌套调用函数来使用。

### `distributionTenant.id`字段
<a name="distribution-tenants-field"></a>

使用此字段可获取分配租户 ID 的值。

**请求**

```
const value = cf.distributionTenant.id;
```

**响应**

响应是包含分配租户 ID 的 `string`，例如 `dt_1a2b3c4d5e6f7`。

**错误处理**

如果针对标准分配调用您的函数，则指定 `distributionTenant.id` 字段将返回 `distributionTenant module is not available` 类型错误。要处理此用例，您可以向代码中添加 `try` 和 `catch` 块。

### `distributionTenant.parameters.get()` 方法
<a name="distribution-tenant-parameters-get-method"></a>

使用此方法可返回您指定的分配租户参数名称的值。

```
distributionTenant.parameters.get("key");
```

`key`：您要获取其值的分配租户参数名称。

**请求**

```
const value = distributionTenant.parameters.get("key");
```

**响应**

响应是包含分配租户参数的值的 `string`。例如，如果您的键名称是 `TenantPath`，则此参数的值可能是 `tenant1`。

**错误处理**

您可能会收到以下错误：
+ 如果针对标准分配调用您的函数，则 `distributionTenant.parameters.get()` 方法将返回 `distributionTenant module is not available` 类型错误。
+ 当您指定的分配租户参数不存在时，将返回 `DistributionTenantParameterKeyNotFound` 错误。

要管理这些用例，您可以向代码中添加 `try` 和 `catch` 块。

# 使用 async 和 await
<a name="async-await-syntax"></a>

CloudFront Functions JavaScript 运行时函数 2.0 提供了 `async` 和 `await` 语法来处理 `Promise` 对象。Promise 表示延迟的结果，可以通过标记为 `async` 的函数中的 `await` 关键字进行访问。各种新的 WebCrypto 函数都使用 Promise。

有关 `Promise` 对象的更多信息，请参阅 [Promise](https://developer.mozilla.org/en-US/docs/Web/JavaScript/Reference/Global_Objects/Promise)。

**注意**  
对于以下代码示例，您必须使用 JavaScript 运行时 2.0。  
`await` 只能在 `async` 函数内部使用。不支持 `async` 参数和闭包。

```
async function answer() {
    return 42;
}

// Note: async, await can be used only inside an async function. async arguments and closures are not supported.

async function handler(event) {
    // var answer_value = answer(); // returns Promise, not a 42 value
    let answer_value = await answer(); // resolves Promise, 42
    console.log("Answer"+answer_value);
    event.request.headers['answer'] = { value : ""+answer_value };
    return event.request;
}
```

以下 JavaScript 代码示例演示了如何使用 `then` 链式方法查看 Promise。您可以使用 `catch` 来查看错误。

**警告**  
使用 Promise 组合器（例如 `Promise.all`、`Promise.any`）和 Promise 链方法（例如 `then` 和 `catch`）可能需要大量的函数内存使用量。如果您的函数超过了[最大函数内存](cloudfront-limits.md#limits-functions)配额，它将无法执行。为避免出现此错误，建议您使用 `await` 语法，而不是 `promise` 方法。

```
async function answer() {
    return 42;
}

async function squared_answer() {
   return answer().then(value => value * value)
} 
// Note: async, await can be used only inside an async function. async arguments and closures are not supported.
async function handler(event) {
    // var answer_value = answer(); // returns Promise, not a 42 value
    let answer_value = await squared_answer(); // resolves Promise, 42
    console.log("Answer"+answer_value);
    event.request.headers['answer'] = { value : ""+answer_value };
    return event.request;
}
```

# CloudFront Functions 中的 CWT 支持
<a name="cwt-support-cloudfront-functions"></a>

此部分详细介绍 CloudFront Functions 中的 CBOR Web 令牌（CWT）支持功能，可使用该功能在 CloudFront 边缘站点实施基于令牌的安全身份验证和授权。此支持功能以模块形式提供，可通过 CloudFront 函数访问。

要使用此模块，请使用 JavaScript 运行时 2.0 创建 CloudFront 函数，并在函数代码的第一行包含以下语句：

```
import cf from 'cloudfront';
```

可通过以下方式访问与此模块关联的方法（其中，\$1 是表示模块中存在的不同函数的通配符）：

```
cf.cwt.*
```

有关更多信息，请参阅[适用于 CloudFront Functions 的 JavaScript 运行时系统 2.0 特征](functions-javascript-runtime-20.md)。

目前，此模块仅支持采用 HS256（HMAC-SHA256）算法的 MAC0 结构，最大令牌大小的限制为 1 KB。

## 令牌结构
<a name="token-structure"></a>

此部分介绍 CWT 模块所需的令牌结构。该模块要求令牌必须带有正确的标签且可识别（例如，COSE MAC0）。此外，在令牌结构方面，该模块遵循 [CBOR 对象签名与加密（COSE）[RFC 8152]](https://datatracker.ietf.org/doc/html/rfc8152) 设定的标准。

```
( // CWT Tag (Tag value: 61) --- optional    
    ( // COSE MAC0 Structure Tag (Tag value: 17) --- required        
        [            
            protectedHeaders,            
            unprotectedHeaders,            
            payload,            
            tag,        
        ]    
    )
)
```

**Example ：使用 COSE MAC0 结构的 CWT**  

```
61( // CWT tag     
    17( // COSE_MAC0 tag       
        [         
            { // Protected Headers           
                1: 4  // algorithm : HMAC-256-64         
            },         
            { // Unprotected Headers           
                4: h'53796d6d6574726963323536' // kid : Symmetric key id          
            },         
            { // Payload           
                1: "https://iss.example.com", // iss           
                2: "exampleUser", // sub           
                3: "https://aud.example.com", // aud           
                4: 1444064944, // exp           
                5: 1443944944, // nbf           
                6: 1443944944, // iat         
            },         
            h'093101ef6d789200' // tag       
        ]     
    )   
)
```
生成令牌时，CWT 标签是可选的。不过，COSE 结构标签是必需的。

## validateToken() 方法
<a name="validatetoken-method"></a>

该函数使用指定密钥对 CWT 令牌进行解码和验证。如果验证成功，则将返回已解码的 CWT 令牌。否则，将引发错误。请注意，该函数不执行任何声明集验证操作。

### 请求
<a name="validatetoken-request"></a>

```
cf.cwt.validateToken(token, handlerContext{key})
```参数

**token（必需）**  
用于验证的编码令牌。这必须是 JavaScript 缓冲区。

**handlerContext（必需）**  
一个 JavaScript 对象，用于存储 validateToken 调用的上下文。目前，仅支持 key 属性。

**key（必需）**  
用于消息摘要计算的私密密钥。可以字符串或 JavaScript 缓冲区的形式提供。

### 响应
<a name="validatetoken-response"></a>

当 `validateToken()` 方法返回成功验证的令牌时，来自函数的响应将为采用以下格式的 `CWTObject`。解码后，所有声明密钥都以字符串形式表示。

```
CWTObject {    
    protectedHeaders,    
    unprotectedHeaders,    
    payload
}
```

### 示例：使用作为令牌一部分发送的 kid 验证令牌
<a name="validatetoken-example"></a>

此示例演示了 CWT 令牌验证过程，其中会从标头中提取 kid。之后，将 kid 传入 CloudFront Functions KeyValueStore 中，以提取用于验证令牌的私密密钥。

```
import cf from 'cloudfront'

const CwtClaims = {
   iss: 1,
   aud: 3,
   exp: 4
}

async function handler(event) {
    try {
        let request = event.request;
        let encodedToken = request.headers['x-cwt-token'].value;
        let kid = request.headers['x-cwt-kid'].value;
                
        // Retrieve the secret key from the kvs
        let secretKey = await cf.kvs().get(kid);
                 
        // Now you can use the secretKey to decode & validate the token.
        let tokenBuffer = Buffer.from(encodedToken, 'base64url');
                
        let handlerContext = {
           key: secretKey,
        }
                
        try {
            let cwtObj = cf.cwt.validateToken(tokenBuffer, handlerContext);
                        
            // Check if token is expired
            const currentTime = Math.floor(Date.now() / 1000); // Current time in seconds
            if (cwtObj[CwtClaims.exp] && cwtObj[CwtClaims.exp] < currentTime) {
                return {
                    statusCode: 401,
                    statusDescription: 'Token expired'
                };
            }
        } catch (error) {
            return {
               statusCode: 401,
               statusDescription: 'Invalid token'
            };
         }
    } catch (error) {
        return {
            statusCode: 402,
            statusDescription: 'Token processing failed'
        };
     }
    return request;
}
```

## generateToken() 方法
<a name="generatetoken-method"></a>

此函数使用提供的有效载荷和上下文设置来生成新的 CWT 令牌。

### 请求
<a name="generatetoken-request"></a>

```
cf.cwt.generateToken(generatorContext, payload)
```参数

**generatorContext（必需）**  
这是一个 JavaScript 对象，用作生成令牌的上下文且包含以下键值对：    
**cwtTag（可选）**  
此值是一个布尔值，如果为 `true`，则指定应添加 `cwtTag`。  
**coseTag（必需）**  
指定 COSE 标签类型。目前仅支持 `MAC0`。  
**key（必需）**  
用于计算消息摘要的私密密钥。此值可以是字符串或 JavaScript `Buffer`。

**payload（必需）**  
用于编码的令牌有效载荷。有效载荷必须采用 `CWTObject` 格式。

### 响应
<a name="generatetoken-response"></a>

返回包含已编码的令牌的 JavaScript 缓冲区。

**Example ：生成 CWT 令牌**  

```
import cf from 'cloudfront';

const CwtClaims = {
    iss: 1,
    sub: 2,
    exp: 4
};

const CatClaims = {
    catu: 401,
    catnip: 402,
    catm: 403,
    catr: 404
};

const Catu = {
    host: 1,
    path: 2,
    ext: 3
};

const CatuMatchTypes = {
    prefix_match: 1,
    suffix_match: 2,
    exact_match: 3
};

const Catr = {
    renewal_method: 1,
    next_renewal_time: 2,
    max_uses: 3
};

async function handler(event) {
    try {
        const response = {
            statusCode: 200,
            statusDescription: 'OK',
            headers: {}
        };
        
        const commonAccessToken = {
            protected: {
                1: "5",
            },
            unprotected: {},
            payload: {
                [CwtClaims.iss]: "cloudfront-documentation",
                [CwtClaims.sub]: "cwt-support-on-cloudfront-functions",
                [CwtClaims.exp]: 1740000000,
                [CatClaims.catu]: {
                    [Catu.host]: {
                        [CatuMatchTypes.suffix_match]: ".cloudfront.net"
                    },
                    [Catu.path]: {
                        [CatuMatchTypes.prefix_match]: "/media/live-stream/cf-4k/"
                    },
                    [Catu.ext]: {
                        [CatuMatchTypes.exact_match]: [
                            ".m3u8",
                            ".ts",
                            ".mpd"
                        ]
                    }
                },
                [CatClaims.catnip]: [
                    "[IP_ADDRESS]",
                    "[IP_ADDRESS]"
                ],
                [CatClaims.catm]: [
                    "GET",
                    "HEAD"
                ],
                [CatClaims.catr]: {
                    [Catr.renewal_method]: "header_renewal",
                    [Catr.next_renewal_time]: 1750000000,
                    [Catr.max_uses]: 5
                }
            }
        };
        
        if (!request.headers['x-cwt-kid']) {
            throw new Error('Missing x-cwt-kid header');
        }
        
        const kid = request.headers['x-cwt-kid'].value;
        const secretKey = await cf.kvs().get(kid);
        
        if (!secretKey) {
            throw new Error('Secret key not found for provided kid');
        }
        
        try {
            const genContext = {
                cwtTag: true,
                coseTag: "MAC0",
                key: secretKey
            };
            
            const tokenBuffer = cf.cwt.generateToken(commonAccessToken, genContext);
            response.headers['x-generated-cwt-token'] = { value: tokenBuffer.toString('base64url') };
                        
            return response;
        } catch (tokenError) {
            return {
                statusCode: 401,
                statusDescription: 'Could not generate the token'
            };
        }
    } catch (error) {
        return {
            statusCode: 402,
            statusDescription: 'Token processing failed'
        };
    }
}
```

**Example ：根据某种逻辑刷新令牌**  

```
import cf from 'cloudfront'

const CwtClaims = {
   iss: 1,
   aud: 3,
   exp: 4
}

async function handler(event) {
    try {
        let request = event.request;
        let encodedToken = request.headers['x-cwt-token'].value;
        let kid = request.headers['x-cwt-kid'].value;
        let secretKey = await cf.kvs().get(kid); // Retrieve the secret key from the kvs
                
        // Now you can use the secretKey to decode & validate the token.
        let tokenBuffer = Buffer.from(encodedToken, 'base64url');
                
        let handlerContext = {
           key: secretKey,
        }
                
        try {
            let cwtJSON = cf.cwt.validateToken(tokenBuffer, handlerContext);
                        
            // Check if token is expired
            const currentTime = Math.floor(Date.now() / 1000); // Current time in seconds
            if (cwtJSON[CwtClaims.exp] && cwtJSON[CwtClaims.exp] < currentTime) {
                // We can regnerate the token and add 8 hours to the expiry time
                cwtJSON[CwtClaims.exp] = Math.floor(Date.now() / 1000) + (8 * 60 * 60);
                                
                let genContext = {
                  coseTag: "MAC0",
                  key: secretKey
                }
                                
                let newTokenBuffer = cf.cwt.generateToken(cwtJSON, genContext);
                 request.headers['x-cwt-regenerated-token'] = newTokenBuffer.toString('base64url');
            }
        } catch (error) {
            return {
               statusCode: 401,
               statusDescription: 'Invalid token'
            };
         }
    }
    catch (error) {
        return {
            statusCode: 402,
            statusDescription: 'Token processing failed'
        };
     }
    return request;
}
```

# 通用辅助方法
<a name="general-helper-methods"></a>

本页面提供了 CloudFront Functions 中的其他辅助方法。要使用这些方法，请使用 JavaScript 运行时 2.0 创建 CloudFront 函数。

```
import cf from 'cloudfront';
```

有关更多信息，请参阅[适用于 CloudFront Functions 的 JavaScript 运行时系统 2.0 特征](functions-javascript-runtime-20.md)。

## `edgeLocation` 元数据
<a name="edge-location-metadata"></a>

此方法需要使用 `cloudfront` 模块。

**注意**  
您只能将此方法用于查看器-请求函数。对于查看器-响应函数，此方法为空。

使用此 JavaScript 对象可获取边缘站点机场代码、预期的[区域边缘缓存](HowCloudFrontWorks.md#CloudFrontRegionaledgecaches)区域，或用于处理请求的 CloudFront 服务器 IP 地址。此元数据仅在查看器请求事件触发时可用。

```
cf.edgeLocation = {
    name: SEA
    serverIp: 1.2.3.4
    region: us-west-2
}
```

`cf.edgeLocation` 对象可包含：

**name**  
处理请求的边缘站点的三字母 [IATA 代码](https://en.wikipedia.org/wiki/IATA_airport_code)。

**serverIp**  
处理请求的服务器的 IPv4 或 IPv6 地址。

**region**  
发生缓存未命中时请求*预计*会使用的 CloudFront 区域边缘缓存（REC）。如果预期的 REC 不可用且请求使用备用 REC，则不会更新此值。这不包括正在使用的源护盾位置，除非主 REC 和源护盾的位置相同。

**注意**  
如果 CloudFront 已配置为使用源失效转移，则不会再次调用 CloudFront Functions。有关更多信息，请参阅[通过 CloudFront 源失效转移来优化高可用性](high_availability_origin_failover.md)。

## `rawQueryString()` 方法
<a name="raw-query-string-method"></a>

此方法不需要 `cloudFront` 模块。

使用 `rawQueryString()` 方法可以字符串形式检索未解析和未更改的查询字符串。

**请求**

```
function handler(event) {
    var request = event.request;
    const qs = request.rawQueryString();
}
```

**响应**

以不带前导 `?` 的字符串值形式返回传入请求的完整查询字符串。
+ 如果没有查询字符串，但存在 `?`，则函数将返回一个空字符串。
+ 如果没有查询字符串且 `?` 不存在，则函数将返回 `undefined`。

**场景 1：返回完整查询字符串（不带前导 `?`）**  
传入请求 URL：`https://example.com/page?name=John&age=25&city=Boston`  
`rawQueryString()` 返回：`"name=John&age=25&city=Boston"`

**场景 2：返回空字符串（当 `?` 存在但没有参数时）**  
传入请求 URL：`https://example.com/page?`  
`rawQueryString()` 返回：`""`

**场景 3：返回 `undefined`（无查询字符串和 `?`）**  
传入请求 URL：`https://example.com/page`  
`rawQueryString()` 返回：`undefined`

# 创建函数
<a name="create-function"></a>

创建函数的过程分为两个阶段：

1. 将函数代码编写为 JavaScript。您可以使用来自 CloudFront 控制台的默认示例，也可以自行编写。有关更多信息，请参阅以下主题：
   + [编写函数代码](writing-function-code.md)
   + [CloudFront Functions 事件结构](functions-event-structure.md)
   + [CloudFront 的 CloudFront Functions 示例](service_code_examples_cloudfront_functions_examples.md)

1. 使用 CloudFront 创建函数并包括您的代码。代码位于函数内部（不是作为引用）。

------
#### [ Console ]

**创建函数**

1. 通过 [https://console.aws.amazon.com/cloudfront/v4/home#/functions](https://console.aws.amazon.com/cloudfront/v4/home#/functions) 登录到 CloudFront 控制台，然后选择**函数**页面。

1. 选择 **Create function (创建函数)**。

1. 输入在 AWS 账户中唯一的函数名称，选择 JavaScript 版本，然后选择**继续**。此时将显示新函数的详细信息页面。
**注意**  
要在函数中使用[键值对](kvs-with-functions.md)，您必须选择 JavaScript 运行时 2.0。

1. 在**函数代码**部分，选择**构建**选项卡，然后输入您的函数代码。**构建**选项卡中包含的代码示例说明了函数代码的基本语法。

1. 选择**保存更改**。

1. 如果函数代码使用键值对，则必须关联键值存储。

   您可在首次创建函数时关联键值存储。或者，您也可以稍后通过[更新函数](update-function.md)来关联它。

   要立即关联键值存储，请执行以下步骤：
   + 转至**关联 KeyValueStore** 部分，选择**关联现有 KeyValueStore**。
   + 选择包含函数中键值对的键值存储，然后选择**关联 KeyValueStore**。

   CloudFront 会立即将存储与该函数关联。您无需保存此函数。

------
#### [ CLI ]

如果您使用 CLI，则通常需要首先在文件中创建函数代码，然后使用 AWS CLI 创建函数。

**创建函数**

1. 在文件中创建函数代码，并将其存储在计算机可以连接到的目录中。

1. 运行该命令，如示例所示。此示例使用 `fileb://` 表示法来传入文件。它还包括换行符，以使命令更具可读性。

   ```
   aws cloudfront create-function \
       --name MaxAge \
       --function-config '{"Comment":"Max Age 2 years","Runtime":"cloudfront-js-2.0","KeyValueStoreAssociations":{"Quantity":1,"Items":[{"KeyValueStoreARN":"arn:aws:cloudfront::111122223333:key-value-store/a1b2c3d4-5678-90ab-cdef-EXAMPLE11111"}]}}' \
       --function-code fileb://function-max-age-v1.js
   ```
**备注**  
`Runtime` – JavaScript 的版本。要在函数中使用[键值对](kvs-with-functions.md)，您必须指定版本 2.0。
`KeyValueStoreAssociations` – 如果您的函数使用键值对，则可以在首次创建函数时关联键值存储。或者，您可以稍后使用 `update-function` 关联它。`Quantity` 始终为 `1`，因为每个函数只能有一个与之关联的键值存储。

   该命令成功执行后，您会看到类似以下内容的输出。

   ```
   ETag: ETVABCEXAMPLE
   FunctionSummary:
     FunctionConfig:
       Comment: Max Age 2 years
       Runtime: cloudfront-js-2.0
       KeyValueStoreAssociations= \
         {Quantity=1, \
         Items=[{KeyValueStoreARN='arn:aws:cloudfront::111122223333:key-value-store/a1b2c3d4-5678-90ab-cdef-EXAMPLE11111'}]} \
     FunctionMetadata:
       CreatedTime: '2021-04-18T20:38:56.915000+00:00'
       FunctionARN: arn:aws:cloudfront::111122223333:function/MaxAge
       LastModifiedTime: '2023-11-19T20:38:56.915000+00:00'
       Stage: DEVELOPMENT
     Name: MaxAge
     Status: UNPUBLISHED
   Location: https://cloudfront.amazonaws.com/2020-05-31/function/arn:aws:cloudfront:::function/MaxAge
   ```

   请求中的大部分信息都是重复的。其他信息由 CloudFront 添加。
**备注**  
`ETag` – 每次修改键值存储时，此值都会更改。您可以使用此值和函数名称在将来引用该函数。确保您始终使用最新的 `ETag`。
`FunctionARN` – 您的 CloudFront 函数的 ARN。
111122223333 – AWS 账户。
`Stage` – 函数的阶段（`LIVE` 或 `DEVELOPMENT`）。
`Status` – 函数的状态（`PUBLISHED` 或 `UNPUBLISHED`）。

------

函数在创建后，将添加到 `DEVELOPMENT` 阶段。我们建议您在[发布函数](publish-function.md)之前对其[进行测试](test-function.md)。在您发布函数后，函数将变为 `LIVE` 状态。

# 测试函数
<a name="test-function"></a>

在将函数部署到实时阶段（生产环境）之前，您可以先对该函数进行测试以确保其按预期运行。要测试函数，您需要指定一个*事件对象*，该对象表示 CloudFront 分配可能在生产环境中接收的 HTTP 请求或响应。

CloudFront Functions 执行以下操作：

1. 将提供的事件对象作为输入来运行函数。

1. 返回函数的结果（修改后的事件对象）和任何函数日志或错误消息以及函数的*计算利用率*。有关计算利用率的更多信息，请参阅[了解计算利用率](#compute-utilization)。

**注意**  
测试函数时，CloudFront 仅验证函数的执行错误。CloudFront 不会验证请求在发布后是否会成功通过。例如，如果您的函数删除了所需标头，测试仍会成功，因为代码没有问题。但是，如果您发布函数并将其与分配相关联，则通过 CloudFront 发出请求时，函数将失败。

**Contents**
+ [设置事件对象](#test-function-create-event)
+ [测试此函数](#test-function-step-test)
+ [了解计算利用率](#compute-utilization)

## 设置事件对象
<a name="test-function-create-event"></a>

在测试函数之前，必须设置用于测试函数的事件对象。有多种选择。

**选项 1：在不保存的情况下设置事件对象**  
您可以在 CloudFront 控制台的可视化编辑器中设置事件对象，而不将其保存。  
您可以使用此事件对象从 CloudFront 控制台测试该函数，即使未保存该对象。

**选项 2：在可视化编辑器中创建事件对象**  
您可以在 CloudFront 控制台的可视化编辑器中设置事件对象，而不将其保存。您可以为每个函数创建 10 个事件对象，以便您可以测试不同的可能输入。  
以这种方式创建事件对象时，您可以使用事件对象在 CloudFront 控制台中测试该函数。您不能使用它通过 AWS API 或 SDK 来测试函数。

**选项 3：使用文本编辑器创建事件对象**  
您可以使用文本编辑器以 JSON 格式创建事件对象。有关事件对象结构的信息，请参阅[事件结构](functions-event-structure.md)。  
您可以使用此事件对象通过 CLI 测试函数。但不能使用它在 CloudFront 控制台中测试函数。

**创建事件对象（选项 1 或 2）**

1. 通过 [https://console.aws.amazon.com/cloudfront/v4/home#/functions](https://console.aws.amazon.com/cloudfront/v4/home#/functions) 登录到 CloudFront 控制台，然后选择**函数**页面。

   选择您要测试的函数。

1. 在函数详细信息页面上，选择**测试**选项卡。

1. 对于**事件类型**，选择以下选项之一：
   + 如果您的函数修改了 HTTP 请求或根据请求生成了响应，请选择**查看器请求**。此时将显示**请求**部分。
   + 选择**查看器响应**。此时将显示**请求**和**响应**部分。

1. 填写要包含在事件中的字段。您可以选择**编辑 JSON** 来查看原始 JSON。

1. （可选）要保存事件，请选择**保存**，在**保存测试事件**中输入名称，然后选择**保存**。

   您也可以选择**编辑 JSON** 并复制原始 JSON，然后将其保存在 CloudFront 之外您自己的文件中。

**创建事件对象（选项 3）**

使用文本编辑器创建事件对象。将文件存储在计算机可以连接到的目录中。

请确保遵循以下指导原则：
+ 忽略 `distributionDomainName`、`distributionId` 和 `requestId` 字段。
+ 标头、Cookie 和查询字符串的名称必须为小写。

以这种方式创建事件对象的一个选项是使用可视化编辑器创建示例。您可以确保示例的格式正确。然后，您可以复制原始 JSON，并将其粘贴到文本编辑器中，然后保存文件。

有关事件结构的更多信息，请参阅[事件结构](functions-event-structure.md)。

## 测试此函数
<a name="test-function-step-test"></a>

您可以在 CloudFront 控制台中或使用 AWS Command Line Interface（AWS CLI）测试函数。

------
#### [ Console ]

**测试此函数**

1. 通过 [https://console.aws.amazon.com/cloudfront/v4/home#/functions](https://console.aws.amazon.com/cloudfront/v4/home#/functions) 登录到 CloudFront 控制台，然后选择**函数**页面。

1. 选择您要测试的函数。

1. 选择**测试**选项卡。

1. 确保显示了正确的事件。要从当前显示的事件切换，请在**选择测试事件**字段中选择另一个事件。

1. 选择**测试函数**。控制台显示函数的输出，包括函数日志和计算利用率。

------
#### [ CLI ]

您可以使用 **aws cloudfront test-function** 命令测试函数。

**测试此函数**

1. 打开一个命令行窗口。

1. 您必须从包含指定文件的同一个目录运行以下命令。

   此示例使用 `fileb://` 表示法来传入事件对象文件。它还包括换行符，以使命令更具可读性。

   ```
   aws cloudfront test-function \
       --name MaxAge \
       --if-match ETVABCEXAMPLE \
       --event-object fileb://event-maxage-test01.json \
       --stage DEVELOPMENT
   ```
**备注**  
您可以通过函数的名称和 ETag（在 `if-match` 参数中）来引用该函数。您可以通过事件对象在文件系统中的位置来引用该对象。
阶段可以为 `DEVELOPMENT` 或 `LIVE`。

   该命令成功执行后，您会看到类似以下内容的输出。

   ```
   TestResult:
     ComputeUtilization: '21'
     FunctionErrorMessage: ''
     FunctionExecutionLogs: []
     FunctionOutput: '{"response":{"headers":{"cloudfront-functions":{"value":"generated-by-CloudFront-Functions"},"location":{"value":"https://aws.amazon.com/cloudfront/"}},"statusDescription":"Found","cookies":{},"statusCode":302}}'
     FunctionSummary:
       FunctionConfig:
         Comment: MaxAge function
         Runtime: cloudfront-js-2.0
         KeyValueStoreAssociations= \
         {Quantity=1, \
         Items=[{KeyValueStoreARN='arn:aws:cloudfront::111122223333:key-value-store/a1b2c3d4-5678-90ab-cdef-EXAMPLE11111'}]} \
       FunctionMetadata:
         CreatedTime: '2021-04-18T20:38:56.915000+00:00'
         FunctionARN: arn:aws:cloudfront::111122223333:function/MaxAge
         LastModifiedTime: '2023-17-20T10:38:57.057000+00:00'
         Stage: DEVELOPMENT
       Name: MaxAge
       Status: UNPUBLISHED
   ```

------

**备注**  
`FunctionExecutionLogs` 包含函数在 `console.log()` 语句中写入的日志行列表（如果有）。
`ComputeUtilization` 包含有关运行函数的信息。请参阅[了解计算利用率](#compute-utilization)。
`FunctionOutput` 包含函数返回的事件对象。

## 了解计算利用率
<a name="compute-utilization"></a>

**计算利用率**是函数运行所花费的时间占最大允许时间的百分比。例如，值为 35 表示函数在最大允许时间的 35% 内完成。

如果函数持续超过最大允许时间，CloudFront 将限制该函数。以下列表说明了函数根据计算利用率值受到限制的可能性。

**计算利用率值：**
+ **1 – 50** – 该函数远低于最大允许时间，应在不受限制的情况下运行。
+ **51 – 70** – 函数正在接近最大允许时间。考虑优化函数代码。
+ **71 – 100** – 函数非常接近或超过最大允许时间。如果您将此函数与分配关联，CloudFront 可能会限制此函数。

# 更新函数
<a name="update-function"></a>

您可以随时更新函数。仅对处于 `DEVELOPMENT` 阶段的函数版本进行更改。要将更新从 `DEVELOPMENT` 阶段复制到 `LIVE`，您必须[发布函数](publish-function.md)。

您可以在 CloudFront 控制台中或使用 AWS Command Line Interface（AWS CLI）更新函数的代码。

------
#### [ Console ]

**更新函数代码**

1. 通过 [https://console.aws.amazon.com/cloudfront/v4/home#/functions](https://console.aws.amazon.com/cloudfront/v4/home#/functions) 登录到 CloudFront 控制台，然后选择**函数**页面。

   选择要更新的函数。

1. 选择**编辑**并进行所需的更改：
   + 根据需要在**详细信息**部分中更新任意字段。
   + 更改或移除关联的键值存储。有关键值存储的更多信息，请参阅[Amazon CloudFront KeyValueStore](kvs-with-functions.md)。
   + 更改函数代码。选择**构建**选项卡，进行更改，然后选择**保存更改**来保存对代码的更改。

------
#### [ CLI ]

**更新函数代码**

1. 打开一个命令行窗口。

1. 运行如下命令。

   此示例使用 `fileb://` 表示法来传入文件。它还包括换行符，以使命令更具可读性。

   ```
   aws cloudfront update-function \
       --name MaxAge \
       --function-config '{"Comment":"Max Age 2 years","Runtime":"cloudfront-js-2.0","KeyValueStoreAssociations":{"Quantity":1,"Items":[{"KeyValueStoreARN":"arn:aws:cloudfront::111122223333:key-value-store/a1b2c3d4-5678-90ab-cdef-EXAMPLE11111"}]}}' \
       --function-code fileb://function-max-age-v1.js \
       --if-match ETVABCEXAMPLE
   ```
**备注**  
您可以通过函数的名称和 ETag（在 `if-match` 参数中）来标识该函数。确保您使用当前的 ETag。您可以从 [DescribeFunction](https://docs.aws.amazon.com/cloudfront/latest/APIReference/API_DescribeFunction.html) API 操作中获取此值。
即使您不想对 `function-code` 进行更改，也必须包含它。
务必注意 `function-config`。您应传递您想要在配置中保留的所有内容。具体而言，应按如下方式处理键值存储：  
要保留现有的键值存储关联（如果有），请指定*现有*存储的名称。
要更改关联，请指定*新*键值存储的名称。
要移除关联，请忽略 `KeyValueStoreAssociations` 参数。

   该命令成功执行后，您会看到类似以下内容的输出。

   ```
   ETag: ETVXYZEXAMPLE
   FunctionSummary:
     FunctionConfig:
       Comment: Max Age 2 years \
       Runtime: cloudfront-js-2.0 \
       KeyValueStoreAssociations= \
         {Quantity=1, \
         Items=[{KeyValueStoreARN='arn:aws:cloudfront::111122223333:key-value-store/a1b2c3d4-5678-90ab-cdef-EXAMPLE11111'}]} \
     FunctionMetadata: \
       CreatedTime: '2021-04-18T20:38:56.915000+00:00' \
       FunctionARN: arn:aws:cloudfront::111122223333:function/MaxAge \
       LastModifiedTime: '2023-12-19T23:41:15.389000+00:00' \
       Stage: DEVELOPMENT \
     Name: MaxAge \
     Status: UNPUBLISHED
   ```

------

请求中的大部分信息都是重复的。其他信息由 CloudFront 添加。

**备注**  
`ETag` – 每次修改键值存储时，此值都会更改。
`FunctionARN` – 您的 CloudFront 函数的 ARN。
`Stage` – 函数的阶段（`LIVE` 或 `DEVELOPMENT`）。
`Status` – 函数的状态（`PUBLISHED` 或 `UNPUBLISHED`）。

# 发布函数
<a name="publish-function"></a>

当您发布函数时，这会将函数从 `DEVELOPMENT` 阶段复制到 `LIVE` 阶段。

如果缓存行为没有与函数关联，则发布函数使您能够将其与缓存行为相关联。您只能将缓存行为与处于 `LIVE` 阶段中的函数相关联。

**重要**  
在您发布之前，我们建议您[测试函数](test-function.md)。
发布函数之后，在分配完成部署时，与该函数关联的所有缓存行为会立即自动开始使用新发布的副本。

您可以在 CloudFront 控制台中或使用 AWS CLI 发布函数。

------
#### [ Console ]

**发布函数**

1. 通过 [https://console.aws.amazon.com/cloudfront/v4/home#/functions](https://console.aws.amazon.com/cloudfront/v4/home#/functions) 登录到 CloudFront 控制台，然后选择**函数**页面。

1. 选择要更新的函数。

1. 选择**发布**选项卡，然后选择**发布**。如果您的函数已附加到一个或多个缓存行为，则选择**发布并更新**。

1. （可选）要查看与函数关联的分配，请选择**关联的 CloudFront 分配**以展开该部分。

成功后，页面顶部会显示一个横幅，说明***函数名称*已成功发布**。您还可以选择**构建**选项卡，然后选择**实时**以查看函数代码的实时版本。

------
#### [ CLI ]

**发布函数**

1. 打开一个命令行窗口。

1. 运行以下 **aws cloudfront publish-function** 命令：在示例中，提供换行符以使示例更具可读性。

   ```
   aws cloudfront publish-function \
       --name MaxAge \
       --if-match ETVXYZEXAMPLE
   ```

   该命令成功执行后，您会看到类似以下内容的输出。

   ```
   FunctionSummary:
     FunctionConfig:
       Comment: Max Age 2 years
       Runtime: cloudfront-js-2.0
     FunctionMetadata:
       CreatedTime: '2021-04-18T21:24:21.314000+00:00'
       FunctionARN: arn:aws:cloudfront::111122223333:function/ExampleFunction
       LastModifiedTime: '2023-12-19T23:41:15.389000+00:00'
       Stage: LIVE
     Name: MaxAge
     Status: UNASSOCIATED
   ```

------

# 将函数与分配关联
<a name="associate-function"></a>

要将一个函数与一个分配结合使用，您可以将该函数与该分配中的一个或多个缓存行为关联。您可以将函数与多个分配中的多个缓存行为相关联。

您可以将函数与以下任意一项相关联：
+ 现有缓存行为
+ 现有分配中的新缓存行为
+ 新分配中的新缓存行为

将函数与缓存行为关联时，必须选择*事件类型*。事件类型决定 CloudFront 何时运行函数。

您可以从以下事件类型中选择：
+ **查看器请求** – 当 CloudFront 收到来自查看器的请求时，该函数将运行。
+ **查看器请求** – 该函数在 CloudFront 向查看器返回响应之前运行。

您不能将面向源的事件类型（*源请求*和*源响应*）与 CloudFront Functions 结合使用。此时应该使用 Lambda@Edge。有关更多信息，请参阅 [可以触发 Lambda@Edge 函数的 CloudFront 事件](lambda-cloudfront-trigger-events.md)。

**注意**  
在关联函数之前，必须[将其发布](publish-function.md)到 `LIVE` 阶段。

您可以在 CloudFront 控制台中或使用 AWS Command Line Interface（AWS CLI）将函数与分配相关联。以下程序演示如何将函数与现有缓存行为关联。

------
#### [ Console ]

**将函数与现有缓存行为关联**

1. 通过 [https://console.aws.amazon.com/cloudfront/v4/home#/functions](https://console.aws.amazon.com/cloudfront/v4/home#/functions) 登录到 CloudFront 控制台，然后选择**函数**页面。

1. 选择要关联的函数。

1. 在**函数**页面上，选择**发布**选项卡。

1. 选择**发布函数**。

1. 选择**添加关联**。在出现的对话框中，选择分配、事件类型和/或缓存行为。

   对于事件类型，请选择您希望此函数在何时运行：
   + **查看器请求** – 在 CloudFront 每次收到请求时运行函数。
   + **查看器响应** – 在 CloudFront 每次返回响应时运行函数。

1. 要保存配置，请选择**添加关联**。

CloudFront 将分配与函数关联。等待几分钟，以便相关的分配完成部署。您可以在函数详细信息页面上选择**查看分配**来查看进度。

------
#### [ CLI ]

**将函数与现有缓存行为关联**

1. 打开一个命令行窗口。

1. 输入以下命令，以针对您要将其缓存行为与函数相关联的分配，保存分配的配置。此命令将分配配置保存到名为 `dist-config.yaml` 的文件中。要使用此命令，请执行以下操作：
   + 将 *`DistributionID`* 替换为分配的 ID。
   + 在一行上运行该命令。在示例中，提供换行符以使示例更具可读性。

   ```
   aws cloudfront get-distribution-config \
       --id DistributionID \
       --output yaml > dist-config.yaml
   ```

   命令成功后，AWS CLI 不返回任何输出。

1. 打开您创建的名为 `dist-config.yaml` 的文件。编辑该文件以进行以下更改。

   1. 将 `ETag` 字段重命名为 `IfMatch`，但不更改字段的值。

   1. 在缓存行为中，找到名为 `FunctionAssociations` 的对象。更新此对象以添加函数关联。函数关联的 YAML 语法如以下示例所示。
      + 下面的示例显示查看器请求事件类型（触发器）。要使用查看器响应事件类型，请将 `viewer-request` 替换为 `viewer-response`。
      + 将 *`arn:aws:cloudfront::111122223333:function/ExampleFunction`* 替换为您将其与此缓存行为关联的函数的 Amazon 资源名称 (ARN)。要获取函数 ARN，您可以使用 **aws cloudfront list-functions** 命令。

      ```
      FunctionAssociations:
        Items:
          - EventType: viewer-request
            FunctionARN: arn:aws:cloudfront::111122223333:function/ExampleFunction
        Quantity: 1
      ```

   1. 进行这些更改后，保存文件。

1. 使用以下命令更新分配，以添加函数关联。要使用此命令，请执行以下操作：
   + 将 *`DistributionID`* 替换为分配的 ID。
   + 在一行上运行该命令。在示例中，提供换行符以使示例更具可读性。

   ```
   aws cloudfront update-distribution \
       --id DistributionID \
       --cli-input-yaml file://dist-config.yaml
   ```

   命令成功后，您会看到类似以下内容的输出，该输出描述了刚通过函数关联更新的分配。为了便于阅读，下面的示例输出被截断了。

   ```
   Distribution:
     ARN: arn:aws:cloudfront::111122223333:distribution/EBEDLT3BGRBBW
     ... truncated ...
     DistributionConfig:
       ... truncated ...
       DefaultCacheBehavior:
         ... truncated ...
         FunctionAssociations:
           Items:
           - EventType: viewer-request
             FunctionARN: arn:aws:cloudfront::111122223333:function/ExampleFunction
           Quantity: 1
         ... truncated ...
     DomainName: d111111abcdef8.cloudfront.net
     Id: EDFDVBD6EXAMPLE
     LastModifiedTime: '2021-04-19T22:39:09.158000+00:00'
     Status: InProgress
   ETag: E2VJGGQEG1JT8S
   ```

------

分配的 `Status` 会在重新部署分配时更改为 `InProgress`。当新的分配配置到达一个 CloudFront 边缘站点时，该边缘站点就会开始使用关联的函数。完全部署分配后，`Status` 将变回 `Deployed`。这表明关联的 CloudFront 函数在全球所有 CloudFront 边缘站点均已上线。这通常需要花费几分钟的时间。

# Amazon CloudFront KeyValueStore
<a name="kvs-with-functions"></a>

CloudFront KeyValueStore 是一个安全、全局、低延迟的键值数据存储，允许从 [CloudFront Functions](cloudfront-functions.md) 内部进行读取访问，从而在 CloudFront 边缘站点实现高级可自定义逻辑。

使用 CloudFront KeyValueStore，您可以对函数代码进行更新，并对与该函数关联的数据进行相互独立的更新。这种分离简化了函数代码，并且无需部署代码更改即可轻松更新数据。

**注意**  
要使用 CloudFront keyValueStore，您的 CloudFront 函数必须使用 [JavaScript 运行时 2.0](functions-javascript-runtime-20.md)。

下面是使用键值对的一般过程：
+ 创建键值存储，并在其中填充一组键值对。您可以将键值存储添加到 Amazon S3 存储桶中，也可以手动输入。
+ 将键值存储与您的 CloudFront 函数相关联。
+ 在函数代码中，使用键的名称来检索与键关联的值或评估该键是否存在。有关在函数代码中使用键值对以及有关助手方法的更多信息，请参阅[键值存储的帮助程序方法](functions-custom-methods.md)。

## 使用案例
<a name="key-value-store-use-cases"></a>

您可以对以下示例使用键值对：
+ **URL 重写或重定向** – 键值对可以保存重写的 URL 或重定向 URL。
+ **A/B 测试和功能标志** – 您可以通过为网站的特定版本分配一定比例的流量来创建运行实验的函数。
+ **访问授权** – 您可以实施访问控制，根据您定义的标准和存储在键值存储中的数据来允许或拒绝请求。

## 支持的值格式
<a name="key-value-store-supported-formats"></a>

您可以采用以下任一格式存储键值对中的值：
+ 字符串
+ 字节编码的字符串
+ JSON 

## 安全性
<a name="key-value-store-security"></a>

CloudFront 函数及其所有键值存储数据均能得到安全处理，如下所示：
+ CloudFront 会在您调用 [CloudFront KeyValueStore](https://docs.aws.amazon.com/cloudfront/latest/APIReference/API_Operations_Amazon_CloudFront_KeyValueStore.html) API 操作时，在处于静态或在传输过程中（读取或写入键值存储时），对每个键值存储进行加密。
+ 在函数运行时，CloudFront 会在 CloudFront 边缘站点对内存中的每个键值对进行解密。

要开始使用 CloudFront KeyValueStore，请参阅以下主题。

**Topics**
+ [使用案例](#key-value-store-use-cases)
+ [支持的值格式](#key-value-store-supported-formats)
+ [安全性](#key-value-store-security)
+ [使用键值存储](kvs-with-functions-kvs.md)
+ [处理键值数据](kvs-with-functions-kvp.md)
+ 有关 CloudFront KeyValueStore 入门的更多信息，请参阅 [Amazon CloudFront KeyValueStore](https://aws.amazon.com/blogs/aws/introducing-amazon-cloudfront-keyvaluestore-a-low-latency-datastore-for-cloudfront-functions/) AWS 博客文章。

# 使用键值存储
<a name="kvs-with-functions-kvs"></a>

您必须创建一个键值存储来保存要在 CloudFront Functions 中使用的键值对。

创建键值存储并添加键值对后，即可在 CloudFront 函数代码中使用键值。

要开始使用，请参阅以下主题：

**Topics**
+ [创建键值存储](kvs-with-functions-create.md)
+ [将键值存储与函数相关联](kvs-with-functions-associate.md)
+ [更新键值存储](kvs-with-functions-edit.md)
+ [获取对键值存储的引用](kvs-with-functions-get-reference.md)
+ [删除键值存储](kvs-with-functions-delete.md)
+ [键值对的文件格式](kvs-with-functions-create-s3-kvp.md)

**注意**  
JavaScript 运行时 2.0 包含一些用于在函数代码中处理键值的帮助程序方法。有关更多信息，请参阅 [键值存储的帮助程序方法](functions-custom-methods.md)。

# 创建键值存储
<a name="kvs-with-functions-create"></a>



您可以同时创建键值存储及其键值对。您还可以立即创建一个空的键值存储，稍后再添加键值对。

**注意**  
如果您指定来自 Amazon S3 存储桶的数据来源，则必须对该存储桶具有 `s3:GetObject` 和 `s3:GetBucketLocation` 权限。如果您没有这些权限，CloudFront 将无法成功创建键值存储。

决定是否要在创建键值存储的同时添加键值对。可以使用 CloudFront 控制台、CloudFront API 或 AWS SDK 导入键值对。但是，您只能在*最初* 创建键值存储时导入键值对的文件。

要创建键值对的文件，请参阅[键值对的文件格式](kvs-with-functions-create-s3-kvp.md)。

------
#### [ Console ]

**创建键值存储**

1. 登录到 AWS 管理控制台 并通过以下网址打开 CloudFront 控制台中的**函数**页面：[https://console.aws.amazon.com/cloudfront/v4/home#/functions](https://console.aws.amazon.com/cloudfront/v4/home#/functions)。

1. 选择 **KeyValueStores** 选项卡，然后选择**创建 KeyValueStore**。

1. 输入键值存储的名称和可选描述。

1. 填写 **S3 URI**：
   + 如果您有一个键值对文件，请输入存储该文件的 Amazon S3 存储桶的路径。
   + 如果您打算手动输入键值对，请将此字段留空。

1. 选择**创建**。键值存储现已存在。

   此时将显示新键值存储的详细信息页面。页面上的信息包括键值存储的 ID 和 ARN。
   + ID 是一个随机字符串，在您的 AWS 账户中是唯一的。
   + ARN 的语法如下：

     *AWS 账户*`:key-value-store/`*键值存储 ID*

1. 查看**键值对**部分。如果您导入了文件，则此部分会显示一些键值对。您可执行以下操作：
   + 如果您导入了文件，还可以手动添加更多值。
   + 如果您没有从 Amazon S3 存储桶导入文件，但却想要立即添加键值对，则可以完成下一步。
   + 您可以跳过此步骤，稍后再添加键值对。

1. 立即添加键值对：

   1. 选择**添加键值对**。

   1. 选择**添加对**并输入名称和值。重复此步骤以添加更多键值对。

   1. 完成后，选择**保存更改**，将所有键值对保存在键值存储中。在随后显示的对话框中，选择**完成**。

1. 要将键值存储立即与函数关联，请完成**关联的函数**部分。有关更多信息，请参阅[创建函数](create-function.md)或[更新函数](update-function.md)。

   您也可以稍后通过此键值存储详细信息页面或通过函数的详细信息页面关联函数。

------
#### [ AWS CLI ]

**创建键值存储**
+ 运行以下命令来创建键值存储并从 Amazon S3 存储桶导入键值对。

  ```
  aws cloudfront create-key-value-store \
      --name=keyvaluestore1 \
      --comment="This is my key value store file" \
      --import-source=SourceType=S3,SourceARN=arn:aws:s3:::amzn-s3-demo-bucket1/kvs-input.json
  ```

  **响应**

  ```
  {
      "ETag": "ETVABCEXAMPLE",
      "Location": "https://cloudfront.amazonaws.com/2020-05-31/key-value-store/arn:aws:cloudfront::123456789012:key-value-store/8aa76c93-3198-462c-aaf6-example",
      "KeyValueStore": {
          "Name": "keyvaluestore1",
          "Id": "8aa76c93-3198-462c-aaf6-example",
          "Comment": "This is my key value store file",
          "ARN": "arn:aws:cloudfront::123456789012:key-value-store/8aa76c93-3198-462c-aaf6-example",
          "Status": "PROVISIONING",
          "LastModifiedTime": "2024-08-06T22:19:10.813000+00:00"
      }
  }
  ```

------
#### [ API ]

**创建键值存储**

1. 使用 [CloudFront CreateKeyValueStore](https://docs.aws.amazon.com/cloudfront/latest/APIReference/API_CreateKeyValueStore.html) 操作。该操作需要几个参数：
   + 键值存储的 `name`。
   + 包含注释的 `comment` 参数。
   + 一个 `import-source` 参数，允许您从存储在 Amazon S3 存储桶中的文件中导入键值对。只有在您首次创建键值存储时，才能从文件导入。有关文件结构的信息，请参阅[键值对的文件格式](kvs-with-functions-create-s3-kvp.md)。

操作响应包含以下信息：
+ 请求中传递的值，包括您分配的名称。
+ 诸如创建时间之类的数据。
+ `ETag`（例如 `ETVABCEXAMPLE`），即包含键值存储名称的 ARN（例如 `arn:aws:cloudfront::123456789012:key-value-store/keyvaluestore1`）。

  您将使用 `ETag`、ARN 和名称的某种组合以编程方式使用键值存储。

------

## 键值存储状态
<a name="key-value-store-status"></a>

创建键值存储时，数据存储可以具有以下状态值。


****  

| 值 | 说明 | 
| --- | --- | 
|  **预置**  |  键值存储已创建，CloudFront 正在处理您指定的数据来源。  | 
|  **就绪**  |  键值存储已创建，CloudFront 成功处理了您指定的数据来源。  | 
|  **导入失败**  |  CloudFront 无法处理您指定的数据来源。如果您的文件格式无效或超过大小限制，则会显示此状态。有关更多信息，请参阅 [键值对的文件格式](kvs-with-functions-create-s3-kvp.md)。  | 

# 将键值存储与函数相关联
<a name="kvs-with-functions-associate"></a>

创建密钥值存储后，您可以更新您的函数以将其与键值存储关联。必须进行此关联，才能在该函数中使用该存储中的键值对。以下规则适用：
+ 一个函数只能有一个键值存储。
+ 您可以将同一键值存储与多个函数关联

------
#### [ Console ]

**将键值存储与函数相关联**

1. 通过 [https://console.aws.amazon.com/cloudfront/v4/home#/functions](https://console.aws.amazon.com/cloudfront/v4/home#/functions) 登录到 CloudFront 控制台，然后选择**函数**页面。

1. 选择函数名称。

1. 转至**关联 KeyValueStore** 部分，选择**关联现有 KeyValueStore**。

1. 选择包含函数中键值对的键值存储，然后选择**关联 KeyValueStore**。

   CloudFront 会立即将存储与该函数关联。您无需保存此函数。

1. 要指定不同的键值存储，请选择**更新关联的 KeyValueStore**，选择另一个键值存储名称，然后选择**关联 KeyValueStore**。

有关更多信息，请参阅 [更新函数](update-function.md)。

------
#### [ AWS CLI ]

**将键值存储与函数相关联**
+ 运行以下命令来更新 `MaxAge` 函数并关联键值存储资源。

  ```
  aws cloudfront update-function \
      --name MaxAge \
      --function-config '{"Comment":"Max Age 2 years","Runtime":"cloudfront-js-2.0","KeyValueStoreAssociations":{"Quantity":1,"Items":[{"KeyValueStoreARN":"arn:aws:cloudfront::123456789012:key-value-store/8aa76c93-3198-462c-aaf6-example"}]}}' \
      --function-code fileb://function-max-age-v1.js \
      --if-match ETVABCEXAMPLE
  ```
+ 要将键值存储与函数相关联，请指定 `KeyValueStoreAssociations` 参数和键值存储 ARN。
+ 要更改关联，请指定其它键值存储 ARN。
+ 要取消关联，请移除 `KeyValueStoreAssociations` 参数。

有关更多信息，请参阅 [更新函数](update-function.md)。

------
#### [ API ]

**将键值存储与函数相关联**
+ 使用 [UpdateFunction](https://docs.aws.amazon.com/cloudfront/latest/APIReference/API_UpdateFunction.html) API 操作。有关更多信息，请参阅 [更新函数](update-function.md)。

------

**备注**  
如果修改键值存储而不更改键值对，或者只修改键值存储中的键值对，则无需再次关联键值存储。您也不需要重新发布该函数。  
不过，我们建议您对函数进行测试，验证它是否能正常工作。有关更多信息，请参阅 [测试函数](test-function.md)。
您可以查看使用特定键值存储的所有函数。在 CloudFront 控制台上，选择键值存储详细信息页面。

# 更新键值存储
<a name="kvs-with-functions-edit"></a>

更新键值存储时，您可以更改键值对，也可以更改键值存储和函数之间的关联。

------
#### [ Console ]

**更新键值存储**

1. 登录到 AWS 管理控制台 并通过以下网址打开 CloudFront 控制台中的**函数**页面：[https://console.aws.amazon.com/cloudfront/v4/home#/functions](https://console.aws.amazon.com/cloudfront/v4/home#/functions)。

1. 选择 **KeyValueStores** 选项卡。

1.  选择要更新的键值存储。
   + 要更新键值对，请在**键值对**部分中选择**编辑**。您可以添加或删除任何键值对。您也可以更改现有键值对的值。完成后，选择**保存更改**。
   + 要更新此键值存储的关联，请选择**转至函数**。有关更多信息，请参阅 [将键值存储与函数相关联](kvs-with-functions-associate.md)。

------
#### [ AWS CLI ]

**更新键值存储**

1. **更改键值对** – 您可以添加更多键值对，删除一个或多个键值对，以及更改现有键值对的值。有关更多信息，请参阅 [处理键值数据](kvs-with-functions-kvp.md)。

1. **更改键值存储的函数关联** - 要更新键值存储的函数关联，请参阅[将键值存储与函数相关联](kvs-with-functions-associate.md)。
**提示**  
您将需要键值存储的 ARN。有关更多信息，请参阅 [获取对键值存储的引用](kvs-with-functions-get-reference.md)。

------
#### [ API ]

**更新键值存储**

1. **更改键值对** – 您可以添加更多键值对，删除一个或多个键值对，以及更改现有键值对的值。有关更多信息，请参阅 [处理键值数据](kvs-with-functions-kvp.md)。

1. **更改键值存储的函数关联** - 要更新键值存储的函数关联，请使用 [UpdateFunction](https://docs.aws.amazon.com/cloudfront/latest/APIReference/API_UpdateFunction.html) API 操作。有关更多信息，请参阅 [更新函数](update-function.md)。
**提示**  
您将需要键值存储的 ARN。有关更多信息，请参阅 [获取对键值存储的引用](kvs-with-functions-get-reference.md)。

------

# 获取对键值存储的引用
<a name="kvs-with-functions-get-reference"></a>

为了以编程方式使用键值存储，您需要键值存储的 `ETag` 和名称。

要获得这两个值，可以使用 AWS Command Line Interface（AWS CLI）或 CloudFront API。

------
#### [ AWS CLI ]

**获取键值存储引用**

1. 要返回键值存储的列表，请运行以下命令，来查找要更改的键值存储的名称。

   ```
   aws cloudfront list-key-value-stores
   ```

1. 从响应中，找到您所需的键值存储的名称。

   **响应**

   ```
   {
       "KeyValueStoreList": {
           "Items": [
               {
                   "Name": "keyvaluestore3",
                   "Id": "37435e19-c205-4271-9e5c-example3",
                   "ARN": "arn:aws:cloudfront::123456789012:key-value-store/37435e19-c205-4271-9e5c-example3",
                   "Status": "READY",
                   "LastModifiedTime": "2024-05-08T14:50:18.876000+00:00"
               },
               {
                   "Name": "keyvaluestore2",
                   "Id": "47970d59-6408-474d-b850-example2",
                   "ARN": "arn:aws:cloudfront::123456789012:key-value-store/47970d59-6408-474d-b850-example2",
                   "Status": "READY",
                   "LastModifiedTime": "2024-05-30T21:06:22.113000+00:00"
               },
               {
                   "Name": "keyvaluestore1",
                   "Id": "8aa76c93-3198-462c-aaf6-example",
                   "ARN": "arn:aws:cloudfront::123456789012:key-value-store/8aa76c93-3198-462c-aaf6-example",
                   "Status": "READY",
                   "LastModifiedTime": "2024-08-06T22:19:30.510000+00:00"
               }
           ]
       }
   }
   ```

1. 运行以下命令来返回指定键值存储的 `ETag`。

   ```
   aws cloudfront describe-key-value-store \
       --name=keyvaluestore1
   ```

   **响应**

   ```
   {
       "ETag": "E3UN6WX5RRO2AG",
       "KeyValueStore": {
           "Name": "keyvaluestore1",
           "Id": "8aa76c93-3198-462c-aaf6-example",
           "Comment": "This is an example KVS",
           "ARN": "arn:aws:cloudfront::123456789012:key-value-store/8aa76c93-3198-462c-aaf6-example",
           "Status": "READY",
           "LastModifiedTime": "2024-08-06T22:19:30.510000+00:00"
       }
   }
   ```

------
#### [ API ]

**获取键值存储引用**

1. 使用 [https://docs.aws.amazon.com/cloudfront/latest/APIReference/API_ListKeyValueStores.html](https://docs.aws.amazon.com/cloudfront/latest/APIReference/API_ListKeyValueStores.html) API 操作返回键值存储列表。找到要更改的键值存储的名称。

1. 使用 [https://docs.aws.amazon.com/cloudfront/latest/APIReference/API_DescribeKeyValueStore.html](https://docs.aws.amazon.com/cloudfront/latest/APIReference/API_DescribeKeyValueStore.html) API 操作并指定您在上一步中返回的键值存储的名称。

------

响应包括 UUID、键值存储的 ARN 和键值存储的 `ETag`。
+ `ETag`，例如 `E3UN6WX5RRO2AG`
+ UUID 为 128 位，例如 `8aa76c93-3198-462c-aaf6-example`
+ ARN 包含 AWS 账户编号、常量 `key-value-store` 和 UUID，类似于以下示例：

  `arn:aws:cloudfront::123456789012:key-value-store/8aa76c93-3198-462c-aaf6-example`

有关 `DescribeKeyValueStore` 操作的更多信息，请参阅[关于 CloudFront KeyValueStore](kvs-with-functions-kvp.md#kvs-with-functions-api-describe)。

# 删除键值存储
<a name="kvs-with-functions-delete"></a>

您可以使用 Amazon CloudFront 控制台或 API 来删除键值存储。

------
#### [ Console ]

**删除键值存储**

1. 登录到 AWS 管理控制台 并通过以下网址打开 CloudFront 控制台中的**函数**页面：[https://console.aws.amazon.com/cloudfront/v4/home#/functions](https://console.aws.amazon.com/cloudfront/v4/home#/functions)。

1. 选择函数名称。

1. 在**关联 KeyValueStore** 部分下，验证某个键值存储是否与该函数关联。如果是，请依次选择**取消关联 KeyValueStore**和**删除关联**来删除关联。

1. 在导航窗格中，选择**函数**页面，然后选择 **KeyValueStores** 选项卡。

1. 选择要删除的键值存储，然后选择**删除**。

------
#### [ AWS CLI ]

**删除键值存储**

1. 获取键值存储的 `ETag` 和名称。有关更多信息，请参阅 [获取对键值存储的引用](kvs-with-functions-get-reference.md)。

1. 验证键值存储是否与函数相关联。如果关联，则取消关联。有关这两个步骤的更多信息，请参阅[更新函数](update-function.md)。

1. 在您有了键值存储的名称和 `ETag` 并且它不再与函数关联之后，可以将其删除。

   运行以下命令来删除指定的键值存储。

   ```
   aws cloudfront delete-key-value-store \
       --name=keyvaluestore1 \
       --if-match=E3UN6WX5RRO2AG
   ```

------
#### [ API ]

**删除键值存储**

1. 获取键值存储的 `ETag` 和名称。有关更多信息，请参阅 [获取对键值存储的引用](kvs-with-functions-get-reference.md)。

1. 验证键值存储是否与函数相关联。如果关联，则取消关联。有关这两个步骤的更多信息，请参阅[更新函数](update-function.md)。

1. 要删除键值存储，请使用 CloudFront [https://docs.aws.amazon.com/cloudfront/latest/APIReference/API_DeleteKeyValueStore.html](https://docs.aws.amazon.com/cloudfront/latest/APIReference/API_DeleteKeyValueStore.html) API 操作。

------

# 键值对的文件格式
<a name="kvs-with-functions-create-s3-kvp"></a>

创建 UTF-8 编码文件时，请使用以下 JSON 格式：

```
{
  "data":[
    {
      "key":"key1",
      "value":"value"
    },
    {
      "key":"key2",
      "value":"value"
    }
  ]
}
```

您的文件不能包含重复键。如果您在 Amazon S3 存储桶中指定了无效文件，则可以更新该文件以删除所有重复项，然后再次尝试创建键值存储。

有关更多信息，请参阅 [创建键值存储](kvs-with-functions-create.md)。

**注意**  
您的数据来源文件及其键值对具有以下限制：  
文件大小 – 5 MB
键大小 – 512 个字符
值大小 – 1024 个字符

# 处理键值数据
<a name="kvs-with-functions-kvp"></a>

本主题介绍如何将键值对添加到现有的键值存储。要在最初创建键值存储时包含键值对，请参阅[创建键值存储](kvs-with-functions-create.md)。

**Topics**
+ [使用键值对（控制台）](#kvs-with-functions-kvp-using-console)
+ [关于 CloudFront KeyValueStore](#kvs-with-functions-api-describe)
+ [使用键值对（AWS CLI）](#work-with-kvs-cli-keys)
+ [使用键值对（API）](#kvs-with-functions-kvp-using-api)

## 使用键值对（控制台）
<a name="kvs-with-functions-kvp-using-console"></a>

您可以使用 CloudFront 控制台处理键值对。

**使用键值对**

1. 登录到 AWS 管理控制台 并通过以下网址打开 CloudFront 控制台中的**函数**页面：[https://console.aws.amazon.com/cloudfront/v4/home#/functions](https://console.aws.amazon.com/cloudfront/v4/home#/functions)。

1. 选择 **KeyValueStores** 选项卡。

1. 选择要更改的键值存储。

1. 在**键值对**部分，选择**编辑**。

1. 您可以添加键值对、删除键值对或更改现有键值对的值。

1. 完成后，选择**保存更改**。

## 关于 CloudFront KeyValueStore
<a name="kvs-with-functions-api-describe"></a>

**提示**  
CloudFront KeyValueStore API 是一项使用签名版本 4A（SigV4A）进行身份验证的全局服务。在 SigV4A 中使用临时凭证需要版本 2 会话令牌。有关更多信息，请参阅 [将临时凭证与 CloudFront KeyValueStore API 结合使用](cloudfront-function-restrictions.md#regional-endpoint-for-key-value-store)。

如果您使用 AWS Command Line Interface（AWS CLI）或自己的代码调用 CloudFront KeyValueStore API，请参阅以下各节。

在使用键值存储及其键值对时，您调用的服务取决于您的使用案例：
+ 要在*现有* 键值存储中使用键值对，可以使用 CloudFront KeyValueStore 服务。
+ 要在*最初*创建键值存储时在键值存储中包含键值对，可以使用 CloudFront 服务。

CloudFront API 和 CloudFront KeyValueStore API 都有一项 `DescribeKeyValueStore` 操作。您出于不同的原因来调用它们。要了解差异，请参阅下表。


|  | CloudFront DescribeKeyValueStore API | CloudFront KeyValueStore DescribeKeyValueStore API | 
| --- | --- | --- | 
| 有关键值存储的数据 |  返回数据，例如状态以及键值存储本身上次被修改的日期。  |  返回有关存储资源的*内容*的数据，即存储中的键值对以及内容的大小。  | 
| 标识键值存储的数据 |  返回键值存储的 `ETag`、UUID 和 ARN。  |  返回键值存储的 `ETag` 和 ARN。  | 

**备注**  
每个 DescribeKeyValueStore 操作都会返回一个*不同的* `ETag`。`ETags` 不可互换。
当您调用 API 操作以完成某项操作时，您必须从相应的 API 指定 `ETag`。例如，在 CloudFront KeyValueStore [ DeleteKey](https://docs.aws.amazon.com/cloudfront/latest/APIReference/API_kvs_DeleteKey.html) 操作中，您可以指定从 CloudFront KeyValueStore [https://docs.aws.amazon.com/cloudfront/latest/APIReference/API_kvs_DescribeKeyValueStore.html](https://docs.aws.amazon.com/cloudfront/latest/APIReference/API_kvs_DescribeKeyValueStore.html) 操作返回的 `ETag`。
当您使用 CloudFront KeyValueStore 调用 CloudFront Functions 时，在调用函数期间，不会更新或更改键值存储中的值。更新是在函数的两次调用之间进行处理的。

## 使用键值对（AWS CLI）
<a name="work-with-kvs-cli-keys"></a>

您可以为 CloudFront KeyValueStore 运行以下 AWS Command Line Interface 命令。

**Contents**
+ [列出键值对](#kvs-cli-list-keys)
+ [获取键值对](#kvs-cli-get-keys)
+ [描述键值存储](#kvs-cli-describe-keys)
+ [创建键值对](#kvs-cli-create-keys)
+ [删除键值对](#kvs-cli-delete-keys)
+ [更新键值对](#kvs-cli-update-key)

### 列出键值对
<a name="kvs-cli-list-keys"></a>

要列出键值存储中的键值对，请运行以下命令。

```
aws cloudfront-keyvaluestore list-keys \
    --kvs-arn=arn:aws:cloudfront::123456789012:key-value-store/37435e19-c205-4271-9e5c-example
```

**响应**

```
{
    "Items": [
        {
            "Key": "key1",
            "Value": "value1"
        }
    ]
}
```

### 获取键值对
<a name="kvs-cli-get-keys"></a>

要获取键值存储中的键值对，请运行以下命令。

```
aws cloudfront-keyvaluestore get-key \
    --key=key1 \
    --kvs-arn=arn:aws:cloudfront::123456789012:key-value-store/37435e19-c205-4271-9e5c-example
```

**响应**

```
{
    "Key": "key1",
    "Value": "value1",
    "ItemCount": 1,
    "TotalSizeInBytes": 11
}
```

### 描述键值存储
<a name="kvs-cli-describe-keys"></a>

要描述键值存储，请运行以下命令。

```
aws cloudfront-keyvaluestore describe-key-value-store \
    --kvs-arn=arn:aws:cloudfront::123456789012:key-value-store/37435e19-c205-4271-9e5c-example
```

**响应**

```
{
    "ETag": "KV1F83G8C2ARO7P",
    "ItemCount": 1,
    "TotalSizeInBytes": 11,
    "KvsARN": "arn:aws:cloudfront::123456789012:key-value-store/37435e19-c205-4271-9e5c-example",
    "Created": "2024-05-08T07:48:45.381000-07:00",
    "LastModified": "2024-08-05T13:50:58.843000-07:00",
    "Status": "READY"
}
```

### 创建键值对
<a name="kvs-cli-create-keys"></a>

要在键值存储中创建键值对，请运行以下命令。

```
aws cloudfront-keyvaluestore put-key \
    --if-match=KV1PA6795UKMFR9 \
    --key=key2 \
    --value=value2 \
    --kvs-arn=arn:aws:cloudfront::123456789012:key-value-store/37435e19-c205-4271-9e5c-example
```

**响应**

```
{
    "ETag": "KV13V1IB3VIYZZH",
    "ItemCount": 3,
    "TotalSizeInBytes": 31
}
```

### 删除键值对
<a name="kvs-cli-delete-keys"></a>

要删除键值对，请运行以下命令：

```
aws cloudfront-keyvaluestore delete-key \
    --if-match=KV13V1IB3VIYZZH \
    --key=key1 \
    --kvs-arn=arn:aws:cloudfront::123456789012:key-value-store/37435e19-c205-4271-9e5c-example
```

**Output**

```
{
    "ETag": "KV1VC38T7YXB528",
    "ItemCount": 2,
    "TotalSizeInBytes": 22
}
```

### 更新键值对
<a name="kvs-cli-update-key"></a>

可以使用 `update-keys` 命令来更新多个键值对。例如，要删除现有键值对并创建另一个键值对，请运行以下命令。

```
aws cloudfront-keyvaluestore update-keys \
    --if-match=KV2EUQ1WTGCTBG2 \
    --kvs-arn=arn:aws:cloudfront::123456789012:key-value-store/37435e19-c205-4271-9e5c-example \
    --deletes '[{"Key":"key2"}]' \
    --puts '[{"Key":"key3","Value":"value3"}]'
```

**响应**

```
{
    "ETag": "KV3AEGXETSR30VB",
    "ItemCount": 3,
    "TotalSizeInBytes": 28
}
```

## 使用键值对（API）
<a name="kvs-with-functions-kvp-using-api"></a>

请按照本节内容以编程方式处理键值对。

**Contents**
+ [获取对键值存储的引用](#kvs-with-functions-api-ref)
+ [更改键值存储中的键值对](#kvs-with-functions-api-actions)
+ [CloudFront KeyValueStore 代码示例](#example-code-key-value-store)

### 获取对键值存储的引用
<a name="kvs-with-functions-api-ref"></a>

使用 CloudFront KeyValueStore API 调用写入操作时，需要指定键值存储的 ARN 和 `ETag`。要获取此数据，请执行以下操作：

**获取对键值存储的引用**

1. 使用 [https://docs.aws.amazon.com/cloudfront/latest/APIReference/API_ListKeyValueStores.html](https://docs.aws.amazon.com/cloudfront/latest/APIReference/API_ListKeyValueStores.html) API 操作获取键值存储列表。找到要更改的键值存储。

1. 使用 [CloudFrontKeyValueStore DescribeKeyValueStore API 操作](https://docs.aws.amazon.com/cloudfront/latest/APIReference/API_kvs_DescribeKeyValueStore.html)并指定上一步中返回的键值存储。

   响应包括键值存储的 ARN 和 `ETag`。
   + ARN 包含 AWS 账户编号、常量 `key-value-store` 和 UUID，类似于以下示例：

     `arn:aws:cloudfront::123456789012:key-value-store/a1b2c3d4-5678-90ab-cdef-EXAMPLE11111`
   + 一个 `ETag`，类似于以下示例：

     `ETVABCEXAMPLE2`

### 更改键值存储中的键值对
<a name="kvs-with-functions-api-actions"></a>

您可以指定包含要更新的键值对的键值存储。

请参阅以下 CloudFront keyValueStore API 操作：
+ [CloudFrontKeyValueStore DeleteKey](https://docs.aws.amazon.com/cloudfront/latest/APIReference/API_kvs_DeleteKey.html) – 删除键值对
+ [CloudFrontKeyValueStore GetKey](https://docs.aws.amazon.com/cloudfront/latest/APIReference/API_kvs_GetKey.html) – 返回键值对
+ [CloudFrontKeyValueStore ListKeys](https://docs.aws.amazon.com/cloudfront/latest/APIReference/API_kvs_ListKeys.html) – 返回键值对列表 
+ [CloudFrontKeyValueStore PutKey](https://docs.aws.amazon.com/cloudfront/latest/APIReference/API_kvs_PutKey.html) – 您可以执行以下任务：
  + 通过指定新的键名和键值，在一个键值存储中创建键值对。
  + 通过指定现有键名和新键值，在现有键值对中设置不同的值。
+ [CloudFrontKeyValueStore UpdateKeys](https://docs.aws.amazon.com/cloudfront/latest/APIReference/API_kvs_UpdateKeys.html) –您可以通过一项“要么全有，要么全无”操作执行以下一项或多项操作：
  + 删除一个或多个键值对
  + 创建一个或多个新的键值对
  + 在一个或多个现有键值对中设置不同的值

### CloudFront KeyValueStore 代码示例
<a name="example-code-key-value-store"></a>

**Example**  
以下代码演示了如何为键值存储调用 `DescribeKeyValueStore` API 操作。  

```
const {
  CloudFrontKeyValueStoreClient,
  DescribeKeyValueStoreCommand,
} = require("@aws-sdk/client-cloudfront-keyvaluestore");

require("@aws-sdk/signature-v4-crt");

(async () => {
  try {
    const client = new CloudFrontKeyValueStoreClient({
      region: "us-east-1"
    });
    const input = {
      KvsARN: "arn:aws:cloudfront::123456789012:key-value-store/a1b2c3d4-5678-90ab-cdef-EXAMPLE11111",
    };
    const command = new DescribeKeyValueStoreCommand(input);

    const response = await client.send(command);
  } catch (e) {
    console.log(e);
  }
})();
```

# 使用 CloudFront 连接函数进行自定义
<a name="customize-connections-validation-with-connection-functions"></a>

借助 CloudFront 连接函数，您可以为 mTLS 证书验证和自定义身份验证逻辑编写轻量级 JavaScript 函数。您的连接函数会在 mTLS 连接建立过程中运行，以验证客户端证书、实施设备特定的身份验证规则以及应对证书吊销场景。连接函数运行时环境提供亚毫秒的启动时间，可立即扩展，从而每秒处理数百万个连接，并且非常安全。连接函数是 CloudFront 的原生功能，这意味着您可以完全在 CloudFront 中构建、测试和部署代码。

在将连接函数与已启用 mTLS 的 CloudFront 分配相关联时，CloudFront 会在 CloudFront 边缘站点拦截 TLS 连接请求，并将证书信息传递给您的函数。当发生以下事件时，您可以调用连接函数：
+ 在 TLS 连接建立过程中（连接请求）：用于双向 TLS（mTLS）连接

有关连接函数的更多信息，请参阅以下主题。

**Topics**
+ [概览和工作流](connection-functions-overview.md)
+ [配置和限制](connection-function-configuration-limits.md)
+ [创建用于双向 TLS（查看器）验证的 CloudFront 连接函数](create-connection-functions.md)
+ [编写用于双向 TLS（查看器）验证的 CloudFront 连接函数](write-connection-function-code.md)
+ [部署前测试 CloudFront 连接函数](test-connection-functions.md)
+ [将连接函数与分配关联](associate-connection-functions.md)
+ [使用 CloudFront Functions 和 KeyValueStore 为双向 TLS（查看器）实施证书吊销](implement-certificate-revocation.md)

# 概览和工作流
<a name="connection-functions-overview"></a>

CloudFront 连接函数是一类专用 CloudFront Functions，当客户端尝试建立 mTLS 连接时，这些函数会在 TLS 握手过程中运行。您的连接函数可获取客户端证书信息、mTLS 配置参数、证书吊销检查结果和客户端 IP 地址。

连接函数将在 CloudFront 执行标准证书验证（信任链、到期、签名验证）后进行调用，即使证书吊销检查失败，函数也可以运行。这可让您实施自定义逻辑，以便处理已吊销证书或添加其他验证标准。

创建并发布连接函数后，请务必为连接请求事件类型添加与已启用 mTLS 的分配的关联。这样一来，只要客户端尝试与 CloudFront 建立 mTLS 连接，该函数就会运行。

CloudFront 连接函数遵循两阶段生命周期，可让您先开发并测试函数，然后再将函数部署到生产环境中。此工作流可确保您的连接函数正常运行，然后再用于处理实时流量。

**Topics**
+ [函数阶段](#connection-function-stages)
+ [开发工作流程](#connection-function-development-workflow)
+ [与其他函数类型的区别](#connection-function-differences)

## 函数阶段
<a name="connection-function-stages"></a>

连接函数存在于以下两个阶段之一：
+ **DEVELOPMENT**：可以修改、测试和更新处于此阶段的函数。当函数处于此阶段时，可以编写和调试函数代码。
+ **LIVE**：处于此阶段的函数是只读的，可以处理生产流量。您无法直接修改处于 LIVE 阶段的函数。

在创建新的连接函数时，它首先会处于 **DEVELOPMENT** 阶段。在测试和验证函数后，可以发布函数以将其移至 **LIVE** 阶段。

## 开发工作流程
<a name="connection-function-development-workflow"></a>

按照此工作流操作，开发和部署连接函数：

1. **创建**：使用初始代码和配置创建处于 DEVELOPMENT 阶段的新连接函数。

1. **测试**：在部署之前，使用测试功能通过示例连接事件验证您的函数。

1. **更新**：根据测试结果按需修改函数代码和配置。

1. **发布**：准备好用于生产环境后，发布函数以将其从 DEVELOPMENT 阶段移至 LIVE 阶段。

1. **关联**：将已发布的函数与已启用 mTLS 的分配相关联以处理实时连接。

要对 LIVE 函数进行更改，您必须更新 DEVELOPMENT 版本并将其重新发布。这将创建处于 LIVE 阶段的新版本。

## 与其他函数类型的区别
<a name="connection-function-differences"></a>

连接函数在几个关键方面与查看器请求和查看器响应函数不同：
+ 连接函数在 mTLS 握手之后且进行任何 HTTP 处理之前运行
+ 连接函数有权获取 TLS 证书信息，而非 HTTP 请求/响应数据
+ 连接函数只能允许或拒绝连接，而不能修改 HTTP 数据
+ 连接函数仅针对新 TLS 连接调用，而不用于连接重用场景
+ mTLS 不支持 TLS 会话恢复，以确保每次连接都进行证书验证
+ 连接函数在标准查看器请求和查看器响应函数之外独立运行
+ 您可以在分配级别而不是在缓存行为级别关联连接函数。
+ 连接函数仅支持 JavaScript 运行时 2.0。

# 配置和限制
<a name="connection-function-configuration-limits"></a>

CloudFront 连接函数因其在 TLS 连接验证中的专门角色以及边缘计算的性能要求，存在特定的配置要求与服务限制。

**Topics**
+ [函数代码要求](#connection-function-code-requirements)
+ [服务限制](#connection-function-service-limits)
+ [函数筛选选项](#connection-function-filtering-options)

## 函数代码要求
<a name="connection-function-code-requirements"></a>

连接函数需要用于处理 TLS 连接事件的 JavaScript 代码。函数代码必须：
+ 用 JavaScript 编写
+ 处理连接事件并做出允许/拒绝决定
+ 在时限内完成执行
+ 处理证书和连接验证逻辑

## 服务限制
<a name="connection-function-service-limits"></a>

连接函数受以下限制的约束：
+ **函数大小**：函数代码和配置存在大小限制
+ **执行时间**：对于 TLS 连接处理，函数有严格的执行时间限制
+ **关联限制**：每个分配只能关联一个连接函数
+ **阶段限制**：仅 LIVE 阶段函数能够与分配关联

## 函数筛选选项
<a name="connection-function-filtering-options"></a>

在列出连接函数时，您可以使用以下筛选器：
+ **阶段筛选器**：按 DEVELOPMENT 或 LIVE 阶段进行筛选
+ **关联筛选器**：按分配 ID 或键值存储 ID 关联进行筛选

这些筛选器有助于在不同的环境和使用案例中组织和管理连接函数。

# 创建用于双向 TLS（查看器）验证的 CloudFront 连接函数
<a name="create-connection-functions"></a>

通过两个阶段创建 CloudFront 连接函数：

1. 将函数代码编写为 JavaScript。您可以使用来自 CloudFront 控制台的默认示例，也可以自行编写。有关更多信息，请参阅以下主题：
   + 编写用于 mTLS 验证的 CloudFront 连接函数代码
   + CloudFront 连接函数事件结构和响应格式
   + 连接函数代码示例

1. 使用 CloudFront 创建连接函数并包括您的代码。代码位于函数内部（不是作为引用）。

**Topics**
+ [CloudFront 控制台](#create-connection-function-console)
+ [AWS CLI](#create-connection-function-cli)

## CloudFront 控制台
<a name="create-connection-function-console"></a>

**创建连接函数**

1. 登录 AWS 管理控制台，并通过以下网址打开 CloudFront 控制台：[https://console.aws.amazon.com/cloudfront/v4/home](https://console.aws.amazon.com/cloudfront/v4/home)。

1. 选择**创建函数**。

1. 输入在 AWS 账户中唯一的函数名称，选择**连接函数**作为函数类型，然后选择**继续**。

1. 此时将显示新连接函数的详细信息页面。
**注意**  
连接函数仅支持 JavaScript 运行时 2.0。要在函数中使用 CloudFront 连接函数 KeyValueStore 集成，您必须使用此运行时版本。

1. 在**函数代码**部分，选择**构建**选项卡，然后输入您的连接函数代码。“构建”选项卡中包含的代码示例说明了连接函数代码的基本语法。

1. 选择**保存更改**。

1. 如果连接函数代码使用 KeyValueStore 进行证书吊销检查或设备验证，则您必须关联 KeyValueStore。

   您可以在首次创建函数时关联 KeyValueStore。或者，您可以稍后通过关联连接函数来关联它。

   要立即关联 KeyValueStore，请执行以下步骤：
   + 转至**关联 KeyValueStore** 部分，选择**关联现有 KeyValueStore**。
   + 选择包含连接函数的证书数据的 KeyValueStore，然后选择**关联 KeyValueStore**。

   CloudFront 会立即将存储与该函数关联。您无需保存此函数。

## AWS CLI
<a name="create-connection-function-cli"></a>

如果您使用 AWS CLI，则通常需要首先在文件中创建连接函数代码，然后使用 AWS CLI 创建函数。

**创建连接函数**

1. 在文件中创建连接函数代码，并将其存储在您的计算机可以连接到的目录中。

1. 运行该命令，如示例所示。此示例使用 `fileb://` 表示法来传入文件。它还包括换行符，以使命令更具可读性。

   ```
   aws cloudfront create-connection-function \
       --name CertificateValidator \
       --connection-function-config '{
           "Comment":"Device certificate validation",
           "Runtime":"cloudfront-js-2.0",
           "KeyValueStoreAssociations":{
               "Quantity":1,
               "Items":[{
                   "KeyValueStoreARN":"arn:aws:cloudfront::111122223333:key-value-store/a1b2c3d4-5678-90ab-cdef-EXAMPLE11111"
               }]
           }
       }' \
       --connection-function-code fileb://certificate-validator.js
   ```
**注意**  
**运行时**：连接函数仅支持 JavaScript 运行时 2.0（cloudfront-js-2.0）。
**KeyValueStoreAssociations**：如果您的连接函数使用 KeyValueStore 进行证书验证，则可以在首次创建函数时关联 KeyValueStore。或者，您可以在以后使用 update-connection-function 关联它。“数量”始终为 1，因为每个连接函数只能关联一个 KeyValueStore。

1. 该命令成功执行后，您会看到类似以下内容的输出。

   ```
   ETag: ETVABCEXAMPLE
   ConnectionFunctionSummary:
     ConnectionFunctionConfig:
       Comment: Device certificate validation
       Runtime: cloudfront-js-2.0
       KeyValueStoreAssociations:
         Quantity: 1
         Items:
           - KeyValueStoreARN: arn:aws:cloudfront::111122223333:key-value-store/a1b2c3d4-5678-90ab-cdef-EXAMPLE11111
     ConnectionFunctionMetadata:
       CreatedTime: '2024-09-04T16:32:54.292000+00:00'
       ConnectionFunctionARN: arn:aws:cloudfront::111122223333:connection-function/CertificateValidator
       LastModifiedTime: '2024-09-04T16:32:54.292000+00:00'
       Stage: DEVELOPMENT
     Name: CertificateValidator
     Status: UNPUBLISHED
   Location: https://cloudfront.amazonaws.com/2020-05-31/connection-function/arn:aws:cloudfront:::connection-function/CertificateValidator
   ```

   请求中的大部分信息都是重复的。其他信息由 CloudFront 添加。
**注意**  
**ETag**：每次修改连接函数时，此值都会发生更改。您需要使用此值来更新或发布函数。
**阶段**：新的连接函数从 DEVELOPMENT 阶段开始。必须先发布该函数以将其移至 LIVE 阶段，然后再将其与分配关联。
**状态**：在您将函数发布到 LIVE 阶段之前，其状态为 UNPUBLISHED。

# 编写用于双向 TLS（查看器）验证的 CloudFront 连接函数
<a name="write-connection-function-code"></a>

借助 CloudFront 连接函数，您可以为 mTLS 证书验证和自定义身份验证逻辑编写轻量级 JavaScript 函数。您的连接函数代码可在全球范围内的 CloudFront 边缘站点验证客户端证书、实施设备特定的身份验证规则、处理证书吊销场景，以及做出允许/拒绝 TLS 连接的决定。

连接函数提供了一种强大的方式，可让您通过自己的业务逻辑来扩展 CloudFront 的内置证书验证功能。与处理 HTTP 数据的查看器请求和查看器响应函数不同，连接函数在 TLS 层运行，并且有权获取证书信息、客户端 IP 地址和 TLS 连接详细信息。这使得它们非常适合实施零信任安全模型、设备身份验证系统和超出标准 PKI 验证范围的自定义证书验证策略。

您的连接函数代码在安全的隔离环境中运行，启动时间以亚毫秒级计，并且可扩展以每秒处理数百万次连接。运行时已针对证书验证工作负载进行优化，并提供了与 CloudFront KeyValueStore 的内置集成以用于实时数据查找操作，从而满足证书吊销列表检查和设备允许列表验证等复杂的身份验证场景的需求。

要获得帮助以编写有效的连接函数代码，请参阅以下主题。有关完整的代码示例和分步教程，请参阅本指南中的教程部分，并浏览 CloudFront 控制台中提供的连接函数示例。

**Topics**
+ [CloudFront 连接函数使用案例和用途](#connection-function-use-cases)
+ [CloudFront 连接函数事件结构和响应格式](#connection-function-event-structure)
+ [CloudFront 连接函数 JavaScript 运行时特征](#connection-function-javascript-runtime)
+ [CloudFront 连接函数辅助方法和 API](#connection-function-helper-methods)
+ [CloudFront 连接函数 KeyValueStore 集成](#connection-function-kvs-integration)
+ [使用 async 和 await](#connection-function-async-await)
+ [连接函数代码示例](#connection-function-code-examples)

## CloudFront 连接函数使用案例和用途
<a name="connection-function-use-cases"></a>

在编写 CloudFront 连接函数之前，请仔细确定需要实施哪种类型的证书验证或身份验证逻辑。连接函数专为需要超出标准 PKI 证书检查范围的自定义验证的特定使用案例而设计。了解您的使用案例可以帮助您设计高效代码，以满足安全要求并保持最佳性能。

常见连接函数使用案例包括：
+ **证书吊销处理**：实施自定义策略来处理已吊销的证书，包括证书轮换的宽限期、内部设备的可信网络异常或已吊销证书可能需要临时访问权限的紧急访问场景。
+ **可选的 mTLS 支持**：使用不同的身份验证策略处理 mTLS 和非 mTLS 连接，让您能够提升支持证书的客户端的安全性，同时保持与传统客户端的兼容性。
+ **基于 IP 的身份验证**：将证书验证与客户端 IP 地址检查相结合以提升安全性，例如，限制来自特定地理区域、企业网络或已知恶意 IP 范围的访问。
+ **多租户证书验证**：实施租户特定的验证规则，其中根据客户端证书颁发者或主题属性应用不同的证书颁发机构或验证标准。
+ **基于时间的访问控制**：实施基于时间的限制，即证书仅在特定时间、维护时段或业务时段有效，即使证书本身尚未过期也是如此。

连接函数在 CloudFront 执行标准证书验证（信任链验证、到期检查和签名验证）之后且在 TLS 连接建立之前运行。该执行时序既可让您灵活地添加自定义验证标准，又可充分利用 CloudFront 的内置证书验证功能。您的函数会收到标准验证的结果，并且可根据标准和自定义标准来明智地决定是允许还是拒绝连接。

在设计连接函数时，请考虑验证逻辑对性能产生的影响。函数的执行限制为 5 毫秒，因此应加快复杂操作的执行速度。使用 KeyValueStore 进行快速数据查找而不是复杂的计算，并构建验证逻辑以对无效证书实施快速失效机制。

## CloudFront 连接函数事件结构和响应格式
<a name="connection-function-event-structure"></a>

CloudFront 连接函数接收的事件结构与查看器请求和查看器响应函数的不同。连接函数接收的不是 HTTP 请求/响应数据，而是可用于做出身份验证决策的证书和连接信息。

**Topics**
+ [连接函数的事件结构](#connection-function-event-structure-details)
+ [连接函数响应格式](#connection-function-response-format)

### 连接函数的事件结构
<a name="connection-function-event-structure-details"></a>

连接函数会接收一个包含证书和连接信息的事件对象。该函数的事件结构如下所示：

```
{
  "clientCertificate": {
    "certificates": {
      "leaf": {
        "serialNumber": "string",
        "issuer": "string",
        "subject": "string",
        "validity": {
          "notBefore": "string",
          "notAfter": "string",
        },
        "sha256Fingerprint": "string"
      }
    }
  },
  "clientIp": "string",
  "endpoint": "string",
  "distributionId": "string",
  "connectionId": "string"
}
```

以下是事件对象结构的示例：

```
{
  "clientCertificate": {
    "certificates": {
      "leaf": {
        "serialNumber": "00:9e:2a:af:16:56:e5:47:25:7d:2e:38:c3:f9:9d:57:fa",
        "issuer": "C=US, O=Ram, OU=Edge, ST=WA, CN=mTLS-CA, L=Snoqualmie",
        "subject": "C=US, O=Ram, OU=Edge, ST=WA, CN=mTLS-CA, L=Snoqualmie",
        "validity": {
          "notBefore": "2025-09-10T23:43:10Z",
          "notAfter": "2055-09-11T00:43:02Z"
        },
        "sha256Fingerprint": "_w6bJ7aOAlGOj7NUhJxTfsfee-ONg_xop3_PTgTJpqs="
      }
    }
  },
  "clientIp": "127.0.0.1",
  "endpoint": "d3lch071jze0cb.cloudfront.net",
  "distributionId": "E1NXS4MQZH501R",
  "connectionId": "NpvTe1925xfj24a67sPQr7ae42BIq03FGhJJKfrQYWZcWZFp96SIIg=="
}
```

### 连接函数响应格式
<a name="connection-function-response-format"></a>

连接函数必须返回一个响应对象，该对象指示是允许还是拒绝连接。使用辅助方法做出连接决策：

```
function connectionHandler(connection) {
    // Helper methods to allow or deny connections
    if (/* some logic to determine if function should allow connection */) {
        connection.allow();
    } else {
        connection.deny();
    }
}
```

与查看器请求和查看器响应函数不同，连接函数无法修改 HTTP 请求或响应。它们只能允许或拒绝 TLS 连接。

## CloudFront 连接函数 JavaScript 运行时特征
<a name="connection-function-javascript-runtime"></a>

CloudFront 连接函数使用 CloudFront Functions JavaScript 运行时 2.0，它提供一个专门针对证书验证工作负载进行了优化的安全的高性能环境。该运行时设计为在亚毫秒级内启动，并可在 CloudFront 的全球边缘网络中处理数百万次并发执行。

该运行时环境包括全面的 JavaScript 语言支持：
+ **ECMAScript 2020（ES11）支持**：现代 JavaScript 特征，包括可选链操作符（?.）、空值合并操作符（??）以及用于处理大型证书序列号的 BigInt
+ **内置对象**：标准 JavaScript 对象，例如 Object、Array、JSON、Math 和 Date
+ **控制台日志记录**：使用 console.log() 调试和监控证书验证决策。测试期间可实时查看日志，并且日志有助于排查开发过程中的验证逻辑问题
+ **KeyValueStore 集成**：可原生访问 CloudFront KeyValueStore 以实施超快速数据查询操作，并且支持实时证书吊销检查、设备允许列表验证以及租户特定的配置检索

连接函数已进行优化，可在证书验证场景中实现高性能。运行时会自动处理内存管理、垃圾回收和资源清理工作，以确保在数百万个并发连接中实现一致的性能。所有操作均设计为确定性执行且具备高性能，其中 KeyValueStore 查找操作通常在微秒级内完成。

运行时环境在各个函数执行之间完全隔离，确保不同的客户端连接之间不会发生数据泄漏。每次函数执行均以全新状态开始，并且无法访问之前的执行结果或来自其他连接的客户端数据。

## CloudFront 连接函数辅助方法和 API
<a name="connection-function-helper-methods"></a>

CloudFront 连接函数提供专用辅助方法，旨在简化证书验证决策流程并提升可观测性。这些方法已针对连接验证工作流进行优化，并与 CloudFront 的连接日志记录和监控系统无缝集成。
+ **connection.allow()**：允许 TLS 连接继续工作。此方法将指示 CloudFront 完成 TLS 握手流程，并允许客户端建立连接。当证书验证通过且满足任何自定义身份验证逻辑时，使用此方法
+ **connection.deny()**：拒绝 TLS 连接并终止握手。此方法会立即关闭连接并阻止任何 HTTP 流量流动。客户端将收到 TLS 连接错误。将此方法用于证书无效、身份验证失败或违反策略的场景
+ **connection.logCustomData()**：将自定义数据添加到连接日志（最多 800 字节的 UTF-8 文本）。此方法可让您将验证结果、证书详细信息或决策依据写入 CloudFront 连接日志，以便进行安全监控、合规审计和问题排查

这些方法提供了简洁的声明式接口，可用于做出连接决策，并记录相关信息以用于监控与调试工作。允许/拒绝模式可确保函数的意图明确，并且 CloudFront 能够根据您的决策优化连接处理。自定义日志记录数据会立即在 CloudFront 连接日志中提供，并且可与日志分析工具结合使用，以实施安全监控并获得运营洞察。

在函数完成之前，始终调用 connection.allow() 或 connection.deny()。出于安全防护考虑，如果两种方法均未被调用，CloudFront 将默认拒绝连接。

## CloudFront 连接函数 KeyValueStore 集成
<a name="connection-function-kvs-integration"></a>

CloudFront 连接函数可使用 CloudFront KeyValueStore 为证书验证场景执行超快速数据查找。KeyValueStore 对于连接函数而言尤其强大，因为它能提供全局、最终一致的数据访问，并在所有 CloudFront 边缘站点实现微秒级的查找速度。这使其非常适合维护证书吊销列表、设备允许列表、租户配置和其他需要在 TLS 握手过程中可供访问的验证数据。

KeyValueStore 集成专为高性能连接验证工作流而设计：
+ **kvsHandle.exists(key)**：检查 KeyValueStore 中是否存在某个键，而无需检索其对应的值。这是处理二进制验证场景（如证书吊销检查）的最高效方法，仅需确认证书序列号是否存在于吊销列表中
+ **kvsHandle.get(key)**：从 KeyValueStore 中检索一个值以用于更复杂的验证场景。当您需要访问与证书或设备标识符关联的配置数据、验证规则或元数据时，使用此选项

KeyValueStore 操作是异步的，且必须与 async/await 语法结合使用。KeyValueStore 的总大小限制为 10 MB，并且最多支持 1000 万个键值对。KeyValueStore 数据在所有边缘站点间保持最终一致性，更新通常在数秒内完成传播。

要获得最佳性能，可构建 KeyValueStore 键以最大限度地减少查找操作数。使用证书序列号作为键来进行简单吊销检查，或者为多 CA 环境创建复合键（包含颁发者哈希和序列号）。在设计数据结构时，请考虑在键复杂性和 KeyValueStore 容量之间进行权衡。

## 使用 async 和 await
<a name="connection-function-async-await"></a>

连接函数支持使用 async/await 语法的异步操作，这在执行 KeyValueStore 操作或其他异步任务时至关重要。async/await 模式可确保您的函数等待 KeyValueStore 查找完成后再做出连接决策，同时保持 TLS 握手处理所需的高性能特征。

正确使用 async/await 对于连接函数至关重要，因为 KeyValueStore 操作的速度虽然非常快，但仍是需要跨 CloudFront 分布式基础设施进行协调的网络操作。运行时会自动处理 promise 解析，并确保您的函数在 5 毫秒的执行时限内完成。

**Example ：异步连接函数与 KeyValueStore**  

```
import cf from 'cloudfront';

async function connectionHandler(connection) {
    const kvsHandle = cf.kvs();
    
    // Async operation to check KeyValueStore for certificate revocation
    const isRevoked = await kvsHandle.exists(connection.clientCertificate.certificates.leaf.serialNumber);
    
    if (isRevoked) {
        // Log the revocation decision with certificate details
        connection.logCustomData(`REVOKED_CERT:${connection.clientCertificate.certificates.leaf.serialNumber}:${connection.clientCertificate.certificates.leaf.issuer}`);
        console.log(`Denying connection for revoked certificate: ${connection.clientCertificate.certificates.leaf.serialNumber}`);
        return connection.deny();
    }
    
    // Log successful validation for monitoring
    connection.logCustomData(`VALID_CERT:${connection.clientCertificate.certificates.leaf.serialNumber}`);
    console.log(`Allowing connection for valid certificate: ${connection.clientCertificate.certificates.leaf.serialNumber}`);
    return connection.allow();
}
```

在调用 KeyValueStore 方法或其他异步操作时，始终使用 async/await。连接函数运行时会自动处理 promise 解析，并确保在 TLS 握手处理的严格时间限制内实施正确的执行流程。避免使用 .then() 或回调模式，因为 async/await 可在连接函数环境中实现更清晰的错误处理和更佳的性能。

在设计异步连接函数时，需优化代码结构以最大限度地减少 KeyValueStore 操作的数量，并在验证逻辑中尽早执行这些操作。这可确保实现最佳性能，并降低高流量时段内发生超时问题的风险。考虑批量执行相关验证检查，并针对您的使用案例使用最高效的 KeyValueStore 方法 [exists() 与 get()]。

## 连接函数代码示例
<a name="connection-function-code-examples"></a>

以下示例演示针对不同验证场景的常见连接函数模式。使用这些示例作为您自己的连接函数实施的起点。

**Example ：设备证书验证**  
此示例演示如何针对 IoT 设备、游戏主机和其他客户端身份验证场景验证设备序列号和证书主题字段：  

```
async function connectionHandler(connection) {
    // Custom validation: check device serial number format
    const serialNumber = connection.clientCertificate.certificates.leaf.serialNumber;
    if (!serialNumber.startsWith("DEV")) {
        connection.logCustomData(`INVALID_SERIAL:${serialNumber}`);
        return connection.deny();
    }
    
    // Validate certificate subject contains required organizational unit
    const subject = connection.clientCertificate.certificates.leaf.subject;
    if (!subject.includes("OU=AuthorizedDevices")) {
        connection.logCustomData(`INVALID_OU:${subject}`);
        return connection.deny();
    }
    
    // Allow connection for valid devices
    connection.logCustomData(`VALID_DEVICE:${serialNumber}`);
    return connection.allow();
}
```
此函数执行标准证书验证之外的多项验证检查，包括设备序列号格式和组织单元验证。

**Example ：支持混合身份验证的可选 mTLS**  
此示例演示如何使用不同的身份验证策略来处理 mTLS 连接和非 mTLS 连接：  

```
async function connectionHandler(connection) {
    if (connection.clientCertificate) {
        // mTLS connection - enhanced validation for certificate holders
        const subject = connection.clientCertificate.certificates.leaf.subject;
        connection.logCustomData(`MTLS_SUCCESS:${subject}:${connection.clientIp}`);
        console.log(`mTLS connection from: ${subject}`);
        return connection.allow();
    } else {
        // Non-mTLS connection - apply IP-based restrictions
        const clientIp = connection.clientIp;
        
        // Only allow non-mTLS from specific IP ranges
        if (clientIp.startsWith("203.0.113.") || clientIp.startsWith("198.51.100.")) {
            connection.logCustomData(`NON_MTLS_ALLOWED:${clientIp}`);
            console.log(`Non-mTLS connection allowed from: ${clientIp}`);
            return connection.allow();
        }
        
        connection.logCustomData(`NON_MTLS_DENIED:${clientIp}`);
        return connection.deny();
    }
}
```
此函数可提升拥有证书的客户端的安全性，并保持与可信 IP 范围内的传统客户端的兼容性。

# 部署前测试 CloudFront 连接函数
<a name="test-connection-functions"></a>

您可以使用 TestConnectionFunction API 操作测试处于 DEVELOPMENT 阶段的 CloudFront 连接函数。通过测试，您可以在将函数发布到 LIVE 阶段之前，使用示例连接事件验证函数逻辑。

**Topics**
+ [测试过程](#connection-function-testing-process)
+ [测试结果](#connection-function-test-results)
+ [连接测试对象](#connection-test-object)

## 测试过程
<a name="connection-function-testing-process"></a>

要测试连接函数，请执行以下操作：

1. 创建处于 DEVELOPMENT 阶段的连接函数

1. 准备表示 TLS 连接事件的测试连接对象

1. 使用 TestConnectionFunction API 操作通过测试数据执行函数

1. 查看测试结果，包括函数输出、执行日志和任何错误消息

1. 根据需要更新函数代码并重复测试过程

## 测试结果
<a name="connection-function-test-results"></a>

测试连接函数时，结果将包括：
+ **函数摘要**：有关已测试的函数的元数据
+ **计算利用率**：显示资源使用情况的性能指标
+ **执行日志**：函数的控制台输出，包括任何日志记录语句
+ **函数输出**：由函数返回的结果
+ **错误消息**：执行过程中发生的任何运行时错误或异常

## 连接测试对象
<a name="connection-test-object"></a>

连接测试对象是一个二进制 blob（最多 40 KB），它表示函数将处理的 TLS 连接事件。此对象包含函数用于做出身份验证决策的证书和连接信息。

**注意**  
连接测试对象的特定结构和格式由 CloudFront 连接函数运行时定义。有关为使用案例创建适当的测试对象的详细信息，请参阅 CloudFront Functions 文档或联系 AWS 支持。

创建连接函数后，您可以：
+ **测试函数**：使用控制台或 CLI 中的测试功能，通过示例连接事件验证函数。有关更多信息，请参阅“连接函数测试”。
+ **更新函数**：根据需要修改函数代码和配置。可以随时更新处于 DEVELOPMENT 阶段的连接函数。
+ **发布函数**：准备好用于生产环境后，发布函数以将其从 DEVELOPMENT 阶段移至 LIVE 阶段。有关更多信息，请参阅“关联连接函数”。
+ **与分配关联**：将已发布的函数与已启用 mTLS 的分配关联以处理实时连接。有关更多信息，请参阅“关联连接函数”。

# 将连接函数与分配关联
<a name="associate-connection-functions"></a>

在将连接函数发布到 LIVE 阶段后，必须将其与已启用 mTLS 的分配关联以处理实时连接。不同于与缓存行为关联的查看器请求和查看器响应函数，连接函数会在分配级别进行关联。

**Topics**
+ [关联要求](#connection-function-association-requirements)
+ [使用筛选器组织函数](#connection-function-organizing-filters)
+ [部署注意事项](#connection-function-deployment-considerations)

## 关联要求
<a name="connection-function-association-requirements"></a>

要将连接函数与分配相关联，请执行以下操作：
+ 函数必须处于 LIVE 阶段
+ 分配必须已启用 mTLS
+ 分配必须已配置有效的信任存储
+ 每个分配只能关联一个连接函数

## 使用筛选器组织函数
<a name="connection-function-organizing-filters"></a>

CloudFront 可提供筛选功能来帮助组织和管理连接函数：
+ **分配 ID 筛选器**：查找与特定分配关联的函数
+ **键值存储筛选器**：查找使用特定键值存储进行数据查找的函数
+ **阶段筛选器**：列出处于 DEVELOPMENT 或 LIVE 阶段的函数

在跨不同的分配或开发环境管理多个连接函数时，可使用这些筛选器。

## 部署注意事项
<a name="connection-function-deployment-considerations"></a>

部署连接函数时，请考虑以下因素：
+ **全球部署**：连接函数部署到全球范围内的所有 CloudFront 边缘站点，这可能需要几分钟
+ **版本管理**：每个已发布版本都会创建一个新的 LIVE 函数来替代上一个版本
+ **回滚策略**：通过保留函数代码的先前工作版本来规划回滚
+ **在生产环境中进行测试**：考虑在暂存环境和生产环境中使用单独的分配

# 使用 CloudFront Functions 和 KeyValueStore 为双向 TLS（查看器）实施证书吊销
<a name="implement-certificate-revocation"></a>

您可以将 CloudFront 连接函数与 KeyValueStore 结合使用来实施证书吊销检查。这可让您维护已吊销证书序列号列表，并在 TLS 握手过程中根据此列表检查客户端证书。

要实施证书吊销，您需要以下组件：
+ 已配置查看器 mTLS 的分配
+ 包含已吊销证书序列号的 KeyValueStore
+ 一个连接函数，用于查询 KeyValueStore 以检查证书状态

当客户端连接时，CloudFront 会根据信任存储验证证书，然后运行您的连接函数。您的函数会根据 KeyValueStore 检查证书序列号，并允许或拒绝连接。

**Topics**
+ [第 1 步：为已吊销证书创建 KeyValueStore](create-kvs-revoked-certificates.md)
+ [第 2 步：创建吊销连接函数](create-revocation-connection-function.md)
+ [第 3 步：测试吊销函数](test-revocation-function.md)
+ [第 4 步：将函数关联到分配](associate-function-distribution.md)
+ [高级吊销场景](advanced-revocation-scenarios.md)

# 第 1 步：为已吊销证书创建 KeyValueStore
<a name="create-kvs-revoked-certificates"></a>

创建 KeyValueStore 来存储已吊销证书的序列号，连接函数可以在 mTLS 连接期间检查这些序列号。

首先，以 JSON 格式准备已吊销证书的序列号：

```
{
  "data": [
    {
      "key": "ABC123DEF456",
      "value": ""
    },
    {
      "key": "789XYZ012GHI", 
      "value": ""
    }
  ]
}
```

将此 JSON 文件上传到 S3 存储桶，然后创建 KeyValueStore：

```
aws s3 cp revoked-serials.json s3://your-bucket-name/revoked-serials.json
aws cloudfront create-key-value-store \
  --name revoked-serials-kvs \
  --import-source '{
    "SourceType": "S3",
    "SourceARN": "arn:aws:s3:::your-bucket-name/revoked-serials.json"
  }'
```

等待 KeyValueStore 完成预调配。通过以下方式查看状态：

```
aws cloudfront get-key-value-store --name "revoked-serials-kvs"
```

# 第 2 步：创建吊销连接函数
<a name="create-revocation-connection-function"></a>

创建一个连接函数，该函数可根据 KeyValueStore 中的数据检查证书序列号，以确定是否吊销证书。

创建一个连接函数，该函数会根据 KeyValueStore 中的数据检查证书序列号：

```
aws cloudfront create-connection-function \
  --name "revocation-control" \
  --connection-function-config file://connection-function-config.json \
  --connection-function-code file://connection-function-code.txt
```

配置文件用于指定 KeyValueStore 关联：

```
{
  "Runtime": "cloudfront-js-2.0",
  "Comment": "A function that implements revocation control via KVS",
  "KeyValueStoreAssociations": {
    "Quantity": 1,
    "Items": [
      {
        "KeyValueStoreArn": "arn:aws:cloudfront::account-id:key-value-store/kvs-id"
      }
    ]
  }
}
```

连接函数代码会检查 KeyValueStore 中是否有已吊销证书：

```
import cf from 'cloudfront';

async function connectionHandler(connection) {
    const kvsHandle = cf.kvs();
    
    // Get parsed client serial number from client certificate
    const clientSerialNumber = connection.clientCertInfo.serialNumber;
    
    // Check KVS to see if serial number exists as a key
    const serialNumberExistsInKvs = await kvsHandle.exists(clientSerialNumber);
    
    // Deny connection if serial number exists in KVS
    if (serialNumberExistsInKvs) {
        console.log("Connection denied - certificate revoked");
        return connection.deny();
    }
    
    // Allow connections that don't exist in kvs
    console.log("Connection allowed");
    return connection.allow();
}
```

# 第 3 步：测试吊销函数
<a name="test-revocation-function"></a>

使用 CloudFront 控制台，通过示例证书测试您的连接函数。在控制台中导航到连接函数，然后使用“测试”选项卡。

**使用示例证书进行测试**

1. 将 PEM 格式的示例证书粘贴到测试界面中

1. （可选）指定用于测试基于 IP 的逻辑的客户端 IP 地址

1. 选择**测试函数**以查看函数执行结果

1. 查看执行日志以验证函数逻辑

使用有效证书和已吊销证书进行测试，确保您的函数可以正确处理这两种场景。执行日志显示 console.log 输出以及函数执行期间出现的任何错误。

# 第 4 步：将函数关联到分配
<a name="associate-function-distribution"></a>

在发布连接函数后，将其与已启用 mTLS 的分配相关联，以激活证书吊销检查。

您可以从分配设置页面或连接函数的关联分配表中关联该函数。导航到您的分配设置，滚动到**查看器双向身份验证（mTLS）**部分，选择您的连接函数，然后保存更改。

# 高级吊销场景
<a name="advanced-revocation-scenarios"></a>

对于更复杂的证书吊销要求，请考虑以下其他配置：

**Topics**
+ [将证书吊销列表（CRL）转换为 KeyValueStore 格式](#convert-crl-kvs-format)
+ [处理多个证书颁发机构](#handle-multiple-cas)
+ [将自定义数据添加到连接日志](#add-custom-data-logs)
+ [管理 CRL 更新](#manage-crl-updates)
+ [规划 KeyValueStore 容量](#plan-kvs-capacity)

## 将证书吊销列表（CRL）转换为 KeyValueStore 格式
<a name="convert-crl-kvs-format"></a>

如果您拥有证书吊销列表（CRL）文件，则可以使用 OpenSSL 和 jq 将其转换为 KeyValueStore JSON 格式：

**将 CRL 转换为 KeyValueStore 格式**

从 CRL 文件中提取序列号：

```
openssl crl -text -noout -in rfc5280_CRL.crl | \
  awk '/Serial Number:/ {print $3}' | \
  cut -d'=' -f2 | \
  sed 's/../&:/g;s/:$//' >> serialnumbers.txt
```

将序列号转换为 KeyValueStore JSON 格式：

```
jq -R -s 'split("\n") | map(select(length > 0)) | {data: map({"key": ., "value": ""})}' \
  serialnumbers.txt >> serialnumbers_kvs.json
```

将格式化的文件上传到 S3，然后按照第 1 步的说明创建 KeyValueStore。

## 处理多个证书颁发机构
<a name="handle-multiple-cas"></a>

当您的 TrustStore 包含多个证书颁发机构（CA）时，请在您的 KeyValueStore 键中包含颁发者信息，以避免来自不同 CA 的序列号相同的证书之间发生冲突。

对于多 CA 场景，请使用颁发者的 SHA1 哈希和序列号的组合作为键：

```
import cf from 'cloudfront';

async function connectionHandler(connection) {
    const kvsHandle = cf.kvs();
    const clientCert = connection.clientCertInfo;
    
    // Create composite key with issuer hash and serial number
    const issuer = clientCert.issuer.replace(/[^a-zA-Z0-9]/g, '').substring(0, 20);
    const serialno = clientCert.serialNumber;
    const compositeKey = `${issuer}_${serialno}`;
    
    const cert_revoked = await kvsHandle.exists(compositeKey);
    
    if (cert_revoked) {
        console.log(`Blocking revoked cert: ${serialno} from issuer: ${issuer}`);
        connection.deny();
    } else {
        connection.allow();
    }
}
```

**注意**  
使用颁发者标识符 \$1 序列号可创建较长的键，这可能会减少可存储在 KeyValueStore 中的条目总数。

## 将自定义数据添加到连接日志
<a name="add-custom-data-logs"></a>

连接函数可以使用 logCustomData 方法将自定义数据添加到 CloudFront 连接日志。这使您能够在日志中包含吊销检查结果、证书信息或其他相关数据。

```
async function connectionHandler(connection) {
    const kvsHandle = cf.kvs();
    const clientSerialNumber = connection.clientCertInfo.serialNumber;
    const serialNumberExistsInKvs = await kvsHandle.exists(clientSerialNumber);
    
    if (serialNumberExistsInKvs) {
        // Log revocation details to connection logs
        connection.logCustomData(`REVOKED:${clientSerialNumber}:DENIED`);
        console.log("Connection denied - certificate revoked");
        return connection.deny();
    }
    
    // Log successful validation
    connection.logCustomData(`VALID:${clientSerialNumber}:ALLOWED`);
    console.log("Connection allowed");
    return connection.allow();
}
```

自定义数据的限制为 800 字节的有效 UTF-8 文本。如果超过此限制，CloudFront 会将数据截断至最近的有效 UTF-8 边界。

**注意**  
仅在为分配启用连接日志时，自定义数据日志记录才起作用。如果未配置连接日志，则 logCustomData 方法将不执行任何操作。

## 管理 CRL 更新
<a name="manage-crl-updates"></a>

证书颁发机构可颁发两种类型的 CRL：
+ **完整 CRL**：包含所有已吊销证书的完整列表
+ **Delta CRL**：仅列出自上次完整 CRL 以来吊销的证书

对于完整 CRL 更新，使用更新后的数据创建一个新的 KeyValueStore，然后将连接函数关联重定向至此新的 KeyValueStore。这种方法比计算差异并执行增量更新更加简单。

对于 delta CRL 更新，使用 update-keys 命令将新的已吊销证书添加到现有 KeyValueStore：

```
aws cloudfront update-key-value-store \
  --name "revoked-serials-kvs" \
  --if-match "current-etag" \
  --put file://delta-revoked-serials.json
```

## 规划 KeyValueStore 容量
<a name="plan-kvs-capacity"></a>

KeyValueStore 的大小限制为 5 MB，并且最多支持 1000 万个键值对。根据您的键格式和数据大小规划吊销列表容量：
+ **仅序列号**：高效存储，用于进行简单吊销检查
+ **颁发者标识符 \$1 序列号**：用于多 CA 环境的较长键

对于大型吊销列表，可以考虑实施分层方法，为不同的证书类别或时段维护单独的 KeyValueStore。

# 使用 Lambda@Edge 在边缘进行自定义
<a name="lambda-at-the-edge"></a>

Lambda@Edge 是 AWS Lambda 的扩展。Lambda@Edge 是一项计算服务，可用于执行函数以自定义 Amazon CloudFront 提供的内容。您可以在某个 AWS 区域，比如美国东部（弗吉尼亚州北部）的 Lambda 控制台中编写 Node.js 或 Python 函数。

创建函数后，您可以使用 Lambda 控制台或 CloudFront 控制台来添加触发器，使函数能够在与查看器较为接近的 AWS 位置运行，而无需预置或管理服务器。或者，您也可以使用 Lambda 和 CloudFront API 操作以编程方式设置函数和触发器。

Lambda@Edge 会自动扩展，从每天几个请求到每秒数千个请求。在与查看器较为接近的 AWS 位置（而不是源服务器）上处理请求，可显著减少延迟并改善用户体验。

**注意**  
gRPC 请求不支持 Lambda@Edge。有关更多信息，请参阅[将 gRPC 与 CloudFront 分配结合使用](distribution-using-grpc.md)。

**Topics**
+ [Lambda@Edge 如何处理请求和响应](lambda-edge-event-request-response.md)
+ [使用 Lambda@Edge 的方法](lambda-edge-ways-to-use.md)
+ [Lambda@Edge 函数入门（控制台）](lambda-edge-how-it-works.md)
+ [设置 Lambda@Edge 的 IAM 权限和角色](lambda-edge-permissions.md)
+ [编写和创建 Lambda@Edge 函数](lambda-edge-create-function.md)
+ [为 Lambda@Edge 函数添加触发器](lambda-edge-add-triggers.md)
+ [测试和调试 Lambda@Edge 函数](lambda-edge-testing-debugging.md)
+ [删除 Lambda@Edge 函数和副本](lambda-edge-delete-replicas.md)
+ [Lambda@Edge 事件结构](lambda-event-structure.md)
+ [使用请求和响应](lambda-generating-http-responses.md)
+ [Lambda@Edge 函数示例](lambda-examples.md)

# Lambda@Edge 如何处理请求和响应
<a name="lambda-edge-event-request-response"></a>

在将 CloudFront 分配与 Lambda@Edge 函数相关联时，CloudFront 在 CloudFront 边缘站点中截获请求和响应。当发生以下 CloudFront 事件时，您可以执行 Lambda 函数：
+ 在 CloudFront 收到查看器的请求时 (查看器请求)
+ 在 CloudFront 将请求转发到源之前（源请求）
+ 在 CloudFront 收到来自源的响应时（源响应）
+ 在 CloudFront 将响应返回到查看器之前（查看器响应）

如果您使用的是 AWS WAF，则会在应用任何 AWS WAF 规则后执行 Lambda@Edge 查看器请求。

有关更多信息，请参阅[使用请求和响应](lambda-generating-http-responses.md)和 [Lambda@Edge 事件结构](lambda-event-structure.md)。

# 使用 Lambda@Edge 的方法
<a name="lambda-edge-ways-to-use"></a>

Lambda@Edge 处理与 Amazon CloudFront 分配配合使用有很多用途，例如：
+ Lambda 函数可检查 Cookie 并重写 URL，以便用户可看到不同版本的站点以进行 A/B 测试。
+ CloudFront 可通过检查 `User-Agent` 标头来基于查看器使用的设备将不同的对象返回到查看器，该标头包含有关这些设备的信息。例如，CloudFront 可基于不同图像所在的设备的屏幕尺寸返回这些图像。同样，函数可考虑 `Referer` 标头的值，并使 CloudFront 将图像返回到具有最低的可用分辨率的自动程序。
+ 或者，您也可以检查 Cookie 中是否有其他条件。例如，在出售服装的零售网站上，如果您使用 Cookie 来指示用户选择了哪种颜色的夹克，Lambda 函数可更改相应请求，以便 CloudFront 返回选定颜色的夹克的图像。
+ 在发生 CloudFront 查看器请求或源请求事件时，Lambda 函数可生成 HTTP 响应。
+ 函数可检查标头或授权令牌，并在 CloudFront 将请求转发到您的源之前插入一个标头，以控制对您的内容的访问。
+ Lambda 函数还可以向外部资源发出网络调用，以确认用户凭证，或获取更多内容来自定义响应。

有关包括示例代码在内的更多信息，请参阅 [Lambda@Edge 函数示例](lambda-examples.md)。

有关在控制台中设置 Lambda@Edge 的更多信息，请参阅 [教程：创建基本 Lambda@Edge 函数（控制台）](lambda-edge-how-it-works-tutorial.md)。

# Lambda@Edge 函数入门（控制台）
<a name="lambda-edge-how-it-works"></a>

借助 Lambda@Edge，您可以使用 CloudFront 触发器调用 Lambda 函数。在将 CloudFront 分配与 Lambda 函数相关联时，CloudFront 在 CloudFront 边缘站点中[截获请求和响应](https://docs.aws.amazon.com/lambda/latest/dg/lambda-edge.html)并运行函数。Lambda 函数可以提高安全性，或者自定义靠近查看器的信息以提高性能。

下表概述了如何创建 Lambda 函数并将其用于 CloudFront。

**概览：借助 CloudFront 创建和使用 Lambda 函数**

1. 在美国东部（弗吉尼亚州北部）区域创建 Lambda 函数。

1. 保存和发布带编号的函数版本。

   如果要对函数进行更改，则必须在美国东部（弗吉尼亚州北部）区域中编辑函数的 \$1LATEST 版本。然后，在将其设置为与 CloudFront 结合使用之前，发布一个带编号的新版本。

1. 将函数与 CloudFront 分配和缓存行为进行关联。指定触发函数执行的一个或多个 CloudFront 事件（称作*触发器*）。例如，您可以创建一个在 CloudFront 收到查看器的请求时促使函数执行的触发器。

1. 创建触发器时，Lambda 会在全球各个 AWS 位置创建该函数的副本。

**提示**  
有关更多信息，请参阅[创建和更新函数](lambda-edge-create-function.md)、[事件结构](lambda-event-structure.md)和[添加 CloudFront 触发器](lambda-edge-add-triggers.md)。另外，您可以在 [Lambda@Edge 函数示例](lambda-examples.md) 中找到更多创意并获得代码示例。

有关分步教程，请参阅以下主题：

**Topics**
+ [教程：创建基本 Lambda@Edge 函数（控制台）](lambda-edge-how-it-works-tutorial.md)

# 教程：创建基本 Lambda@Edge 函数（控制台）
<a name="lambda-edge-how-it-works-tutorial"></a>

本教程演示如何通过创建和配置一个在 CloudFront 中运行的示例 Node.js 函数，来开始使用 Lambda@Edge。在该示例中，我们在 CloudFront 检索文件时将 HTTP 安全标头添加到响应中。（这可以提高网站的安全性和隐私性。）

学习本教程不需要您自己的网站。但在选择创建自己的 Lambda@Edge 解决方案时，您需要完成类似步骤并从相同的选项中进行选择。

**Topics**
+ [第 1 步：注册 AWS 账户](#lambda-edge-how-it-works-tutorial-AWS)
+ [步骤 2：创建 CloudFront 分配](#lambda-edge-how-it-works-tutorial-cloudfront)
+ [第 3 步：创建函数](#lambda-edge-how-it-works-tutorial-create-function)
+ [第 4 步：添加 CloudFront 触发器来运行函数](#lambda-edge-how-it-works-tutorial-add-trigger)
+ [第 5 步：验证函数是否正常工作](#lambda-edge-how-it-works-tutorial-verify)
+ [第 6 步：问题排查](#lambda-edge-how-it-works-tutorial-troubleshoot)
+ [第 7 步：清除示例资源](#lambda-edge-how-it-works-tutorial-cleanup-resources)
+ [相关信息](#lambda-edge-how-it-works-tutorial-resources)

## 第 1 步：注册 AWS 账户
<a name="lambda-edge-how-it-works-tutorial-AWS"></a>

如果您尚未完成此操作，请注册一个 AWS 账户。有关更多信息，请参阅 [注册 AWS 账户](setting-up-cloudfront.md#sign-up-for-aws)。

## 步骤 2：创建 CloudFront 分配
<a name="lambda-edge-how-it-works-tutorial-cloudfront"></a>

创建 Lambda@Edge 函数示例之前，您必须有一个可使用的、包含提供内容的源的 CloudFront 环境。

在本示例中，您将创建一个使用 Amazon S3 存储桶作为分配源的 CloudFront 分配。如果您已有要使用的环境，则可跳过本步骤。<a name="lambda-edge-how-it-works-tutorial-cf-proc"></a>

**创建具有 Amazon S3 源的 CloudFront 分配**

1. 创建包含一个或两个文件的 Amazon S3 存储桶，此处的文件可以是图像文件等等，并将作为示例内容。要获得帮助，请按照[将您的内容上传到 Amazon S3](https://docs.aws.amazon.com/AmazonCloudFront/latest/DeveloperGuide/GettingStarted.html#GettingStartedUploadContent) 中提供的步骤执行操作。确保设置相应权限，以授予对存储桶中对象的公共读取访问权限。

1. 按照[创建 CloudFront Web 分配](https://docs.aws.amazon.com/AmazonCloudFront/latest/DeveloperGuide/GettingStarted.html#GettingStartedCreateDistribution)中提供的步骤操作，创建 CloudFront 分配并添加您的 S3 存储桶作为源。如果您已有一个分配，可以添加该存储桶作为该分配的源。
**提示**  
请记下您的分配 ID。在本教程的后面，为函数添加 CloudFront 触发器时必须从下拉列表中为您的分配选择该 ID，例如 `E653W22221KDDL`。

## 第 3 步：创建函数
<a name="lambda-edge-how-it-works-tutorial-create-function"></a>

在此步骤中，您将从 Lambda 控制台中的蓝图模板创建一个 Lambda 函数。该函数会添加代码以更新 CloudFront 分配中的安全标头。<a name="lambda-edge-how-it-works-tutorial-create-function-blueprint-proc"></a>

**创建 Lambda 函数**

1. 通过以下网址登录 AWS 管理控制台并打开 AWS Lambda 控制台：[https://console.aws.amazon.com/lambda/](https://console.aws.amazon.com/lambda/)。
**重要**  
确保您位于 **US-East-1（弗吉尼亚州北部）**AWS 区域（**us-east-1**）。您必须位于该区域，才能创建 Lambda@Edge 函数。

1. 选择**创建函数**。

1. 在**创建函数**页面上，选择**使用蓝图**，然后通过在搜索字段中输入 **cloudfront** 来筛选 CloudFront 蓝图。
**注意**  
CloudFront 蓝图仅在 **US-East-1（弗吉尼亚州北部）**区域（**us-east-1**）可用。

1. 选择**修改 HTTP 响应标头**蓝图以用作函数的模板。

1. 输入有关函数的以下信息：
   + **函数名称** – 输入您的函数的名称。
   + **执行角色** – 选择如何设置函数的权限。要使用推荐的基本 Lambda@Edge 权限策略模板，请选择**从 AWS 策略模板创建新角色**。
   + **角色名称** – 输入策略模板所创建的角色的名称。
   + **策略模板** – Lambda 将自动添加策略模板**基本 Lambda@Edge 权限**，因为您选择了 CloudFront 蓝图作为函数的基础。此策略模板添加执行角色权限，以允许 CloudFront 在全球各地的 CloudFront 位置运行您的 Lambda 函数。有关更多信息，请参阅 [设置 Lambda@Edge 的 IAM 权限和角色](lambda-edge-permissions.md)。

1. 在页面底部，选择**创建函数**。

1. 在出现的**部署到 Lambda@Edge** 窗格中，选择**取消**。（对于本教程，在将函数部署到 Lambda@Edge 之前，您必须修改函数代码。）

1. 向下滚动到页面的**代码源**部分。

1. 将模板代码替换为一个函数，该函数修改您的源返回的安全标头。例如，可以使用如下代码：

   ```
   'use strict';
   export const handler = (event, context, callback) => {
   
       //Get contents of response
       const response = event.Records[0].cf.response;
       const headers = response.headers;
   
       //Set new headers
       headers['strict-transport-security'] = [{key: 'Strict-Transport-Security', value: 'max-age= 63072000; includeSubdomains; preload'}];
       headers['content-security-policy'] = [{key: 'Content-Security-Policy', value: "default-src 'none'; img-src 'self'; script-src 'self'; style-src 'self'; object-src 'none'"}];
       headers['x-content-type-options'] = [{key: 'X-Content-Type-Options', value: 'nosniff'}];
       headers['x-frame-options'] = [{key: 'X-Frame-Options', value: 'DENY'}];
       headers['x-xss-protection'] = [{key: 'X-XSS-Protection', value: '1; mode=block'}];
       headers['referrer-policy'] = [{key: 'Referrer-Policy', value: 'same-origin'}];
   
       //Return modified response
       callback(null, response);
   };
   ```

1. 选择**文件**、**保存**，保存已更新的代码。

1. 选择**部署**。

继续执行下一部分，添加 CloudFront 触发器以运行函数。

## 第 4 步：添加 CloudFront 触发器来运行函数
<a name="lambda-edge-how-it-works-tutorial-add-trigger"></a>

现已具有用于更新安全标头的 Lambda 函数，请配置 CloudFront 触发器以运行您的函数，从而在 CloudFront 从分配来源收到的任何响应中添加标头。<a name="lambda-edge-how-it-works-tutorial-add-trigger-proc"></a>

**为您的函数配置 CloudFront 触发器**

1. 在 Lambda 控制台中，在函数的**函数概述**页面上，选择**添加触发器**。

1. 对于**触发器配置**，请选择 **CloudFront**。

1. 选择**部署到 Lambda@Edge**。

1. 在**部署到 Lambda@Edge**窗格上的**配置 CloudFront 触发器**下，输入以下信息：
   + **分配** – 要与函数关联的 CloudFront 分配 ID。从下拉列表中选择分配 ID。
   + **缓存行为** – 要用于触发器的缓存行为。在本示例中，将该值设置为 **\$1**，这表示您的分配的默认缓存行为。有关更多信息，请参阅[所有分配设置参考](distribution-web-values-specify.md)主题中的 [缓存行为设置](DownloadDistValuesCacheBehavior.md)。
   + **CloudFront 事件** – 指定何时运行您的函数的触发器。我们希望在 CloudFront 返回来自源的响应时运行安全标头函数。在下拉列表中，选择**源响应**。有关更多信息，请参阅 [为 Lambda@Edge 函数添加触发器](lambda-edge-add-triggers.md)。

1. 选中**确认部署到 Lambda@Edge**复选框。

1. 选择**部署**以添加触发器并将该函数复制到全球各地的 AWS 位置。

1. 请等待复制函数完成。这通常需要几分钟时间。

    您可以[转到 CloudFront 控制台](https://console.aws.amazon.com/cloudfront/v4/home)并查看您的分配，确认复制是否完成。等待分配状态从**正在部署**更改为部署日期和时间，这意味着已复制函数。要验证函数是否正常工作，请执行下一节中的步骤。

## 第 5 步：验证函数是否正常工作
<a name="lambda-edge-how-it-works-tutorial-verify"></a>

现在，您已创建 Lambda 函数并已配置触发器以便为 CloudFront 分配运行它，请检查以确保该函数按照预期运行。在本示例中，我们检查 CloudFront 返回的 HTTP 标头，确保已添加了安全标头。<a name="lambda-edge-how-it-works-tutorial-verify-proc"></a>

**验证您的 Lambda@Edge 函数是否添加安全标头**

1. 在浏览器中，键入 S3 存储桶中的文件的 URL。例如，您可以使用类似以下所示的 URL：`https://d111111abcdef8.cloudfront.net/image.jpg`。

   有关要在文件 URL 中使用的 CloudFront 域名的更多信息，请参阅[在 CloudFront 中自定义文件的 URL 格式](LinkFormat.md)。

1. 打开您的浏览器的 Web Developer（Web 开发人员）工具栏。例如，在 Chrome 浏览器窗口中，打开上下文（右键单击）菜单，然后选择**检查**。

1. 选择**网络**选项卡。

1. 重新加载页面以查看您的图像，然后在左侧窗格选择 HTTP 请求。您会看到在一个单独窗格中显示 HTTP 标头。

1. 查看 HTTP 标头的列表，验证所需安全标头包含在列表中。例如，您可能会看到类似于以下屏幕截图中所示的标头。  
![\[HTTP 标头列表，并突出显示预期的安全标头。\]](http://docs.aws.amazon.com/zh_cn/AmazonCloudFront/latest/DeveloperGuide/images/lambda-at-edge-security-headers-list.png)

如果安全标头已包含在标头列表中，很棒！您已成功创建第一个 Lambda@Edge 函数。如果 CloudFront 返回错误或有其他问题，请继续执行下一步骤来解决相应问题。

## 第 6 步：问题排查
<a name="lambda-edge-how-it-works-tutorial-troubleshoot"></a>

如果 CloudFront 返回错误或没有按预期添加安全标头，您可以查看 CloudWatch Logs 来研究函数的执行。请务必使用与执行函数时的位置最接近的 AWS 位置中存储的日志。

例如，如果您查看来自伦敦的文件，请尝试在 CloudWatch 控制台中将“区域”更改为“欧洲地区（伦敦）”。<a name="lambda-edge-how-it-works-tutorial-cloudwatch-proc"></a>

**检查您的 Lambda@Edge 函数的 CloudWatch 日志**

1. 登录到 AWS 管理控制台 并通过以下网址打开 CloudWatch 控制台：[https://console.aws.amazon.com/cloudwatch/](https://console.aws.amazon.com/cloudwatch/)。

1. 将**区域**更改为您在浏览器中查看文件时所显示的位置。这是执行函数的区域。

1. 在左侧窗格中选择**日志**以查看您的分配的日志。

有关更多信息，请参阅 [使用 Amazon CloudWatch 监控 CloudFront 指标](monitoring-using-cloudwatch.md)。

## 第 7 步：清除示例资源
<a name="lambda-edge-how-it-works-tutorial-cleanup-resources"></a>

如果您仅为本教程创建了 Amazon S3 存储桶和 CloudFront 分配，请删除您分配的 AWS 资源，以免继续产生费用。删除 AWS 资源后，您添加的任何内容将不再可用。

**任务**：
+ [删除 S3 存储桶](#lambda-edge-how-it-works-tutorial-delete-bucket) 
+ [删除 Lambda 函数](#lambda-edge-how-it-works-tutorial-delete-function)
+ [删除 CloudFront 分配](#lambda-edge-how-it-works-tutorial-delete-distribution)

### 删除 S3 存储桶
<a name="lambda-edge-how-it-works-tutorial-delete-bucket"></a>

在删除您的 Amazon S3 存储桶之前，请确保已禁用该存储桶的日志记录。否则，在您删除存储桶时，AWS 会继续将日志写入其中。<a name="lambda-edge-how-it-works-tutorial-delete-bucket-proc"></a>

**禁用存储桶的日志记录**

1. 通过以下网址打开 Amazon S3 控制台：[https://console.aws.amazon.com/s3/](https://console.aws.amazon.com/s3/)。

1. 选择存储桶，然后选择 **Properties**。

1. 从**属性**中选择**日志记录**。

1. 取消选中**启用**复选框。

1. 选择**保存**。

现在您可以删除存储桶。有关更多信息，请参阅《Amazon Simple Storage Service 控制台用户指南》**中的[删除存储桶](https://docs.aws.amazon.com/AmazonS3/latest/userguide/delete-bucket.html)。

### 删除 Lambda 函数
<a name="lambda-edge-how-it-works-tutorial-delete-function"></a>

有关如何取消 Lambda 函数关联以及选择删除函数本身的说明，请参阅[删除 Lambda@Edge 函数和副本](lambda-edge-delete-replicas.md)。

### 删除 CloudFront 分配
<a name="lambda-edge-how-it-works-tutorial-delete-distribution"></a>

删除 CloudFront 分配之前，须先将其禁用。已禁用的分发不再起作用，并且不会产生费用。您可以随时启用已禁用的分发。已禁用的分配在删除后将不再可用。<a name="lambda-edge-how-it-works-tutorial-delete-distribution-proc"></a>

**禁用并删除 CloudFront 分配**

1. 通过 打开 CloudFront 控制台[https://console.aws.amazon.com/cloudfront/v4/home](https://console.aws.amazon.com/cloudfront/v4/home)

1. 选择要禁用的分配，然后选择**禁用**。

1. 当系统提示确认时，选择**是，禁用**。

1. 选择禁用的分配，然后选择**删除**。

1. 当系统提示进行确认时，选择 **Yes, Delete**。

## 相关信息
<a name="lambda-edge-how-it-works-tutorial-resources"></a>

现在，您对 Lambda@Edge 函数的工作方式已经有了基本概念，如需了解更多信息，请阅读以下内容：
+ [Lambda@Edge 函数示例](lambda-examples.md)
+ [Lambda@Edge 设计最佳实践](https://aws.amazon.com/blogs/networking-and-content-delivery/lambdaedge-design-best-practices/)
+ [使用 Lambda@Edge 减少延迟并将计算转移到边缘站点](https://aws.amazon.com/blogs/networking-and-content-delivery/reducing-latency-and-shifting-compute-to-the-edge-with-lambdaedge/)

# 设置 Lambda@Edge 的 IAM 权限和角色
<a name="lambda-edge-permissions"></a>

要配置 Lambda@Edge，您必须针对 AWS Lambda 设置以下 IAM 权限和角色：
+ [IAM 权限](#lambda-edge-permissions-required) – 这些权限允许您创建自己的 Lambda 函数并将其与您的 CloudFront 分配相关联。
+ [Lambda 函数执行角色](#lambda-edge-permissions-function-execution)（IAM 角色）– Lambda 服务主体代入此角色来执行您的函数。
+ [服务相关 Lambda@Edge 角色](#using-service-linked-roles-lambda-edge) – 服务相关角色允许特定 AWS 服务将 Lambda 函数复制到 AWS 区域，并允许 CloudWatch 使用 CloudFront 日志文件。

## 将 Lambda@Edge 函数与 CloudFront 分配关联所需的 IAM 权限
<a name="lambda-edge-permissions-required"></a>

除了配置 Lambda 所需的 IAM 权限之外，您还需要以下权限才能将 Lambda 函数与 CloudFront 分配相关联：
+ `lambda:GetFunction` – 授予相关权限，已获取 Lambda 函数的配置信息，以及一个用于下载包含该函数的 `.zip` 文件的预签名 URL。
+ `lambda:EnableReplication*` – 向资源策略授予相关权限，以便 Lambda 复制服务可以获取函数代码和配置。
+ `lambda:DisableReplication*` – 向资源策略授予相关权限，以便 Lambda 复制服务可以删除函数。
**重要**  
您必须在 `lambda:EnableReplication*` 和 `lambda:DisableReplication*` 操作的末尾添加星号（`*`）。
+ 对于资源，请指定当 CloudFront 事件发生时要执行的函数版本的 ARN，如以下示例所示：

  `arn:aws:lambda:us-east-1:123456789012:function:TestFunction:2`
+ `iam:CreateServiceLinkedRole` – 授予相关权限，以允许创建 Lambda@Edge 用于在 CloudFront 中复制 Lambda 函数所需的服务相关角色。在首次配置 Lambda@Edge 之后，将自动创建服务相关角色。您不需要将此权限添加至使用 Lambda@Edge 的其他分配中。

  
+ `cloudfront:UpdateDistribution` 或 `cloudfront:CreateDistribution` - 授予更新或创建分配的权限。

有关更多信息，请参阅以下主题：
+ [适用于 Amazon CloudFront 的 Identity and Access Management](security-iam.md)
+ 《AWS Lambda 开发人员指南》中的** [Lambda 资源访问权限](https://docs.aws.amazon.com/lambda/latest/dg/intro-permission-model.html#lambda-intro-execution-role)

## 服务主体的函数执行角色
<a name="lambda-edge-permissions-function-execution"></a>

您必须创建一个 IAM 角色，以便 `lambda.amazonaws.com` 和 `edgelambda.amazonaws.com` 服务主体在执行您的函数时可以代入该角色。

**提示**  
当您在 Lambda 控制台中创建函数时，可以选择使用 AWS 策略模板创建新的执行角色。此步骤*会自动* 添加执行函数所需的 Lambda@Edge 权限。请参阅[教程中的步骤 5：创建简单的 Lambda@Edge 函数](lambda-edge-how-it-works-tutorial.md#lambda-edge-how-it-works-tutorial-create-function)。

有关手动创建 IAM 角色的更多信息，请参阅《IAM 用户指南》**中的[创建角色并附加策略（控制台）](https://docs.aws.amazon.com/IAM/latest/UserGuide/access_policies_job-functions_create-policies.html)。

**Example 示例：角色信任策略**  
您可以在 IAM 控制台的**信任关系**选项卡下添加此角色。请勿在**权限**选项卡下添加此策略。    
****  

```
{
   "Version":"2012-10-17",		 	 	 
   "Statement": [
      {
         "Effect": "Allow",
         "Principal": {
            "Service": [
               "lambda.amazonaws.com",
               "edgelambda.amazonaws.com"
            ]
         },
         "Action": "sts:AssumeRole"
      }
   ]
}
```

有关需要向执行角色授予的权限的更多信息，请参阅《AWS Lambda 开发人员指南》**中的 [Lambda 资源访问权限](https://docs.aws.amazon.com/lambda/latest/dg/intro-permission-model.html#lambda-intro-execution-role)。

**备注**  
默认情况下，每当 CloudFront 事件触发 Lambda 函数时，数据都会写入到 CloudWatch Logs。如果要使用这些日志，执行角色需要权限来将数据写入 CloudWatch Logs。您可以使用预定义的 AWSLambdaBasicExecutionRole 向执行角色授予权限。  
有关 CloudWatch Logs 的更多信息，请参阅[边缘函数日志](edge-functions-logs.md)。
如果您的 Lambda 函数代码访问其他 AWS 资源，比如从 S3 存储桶读取对象，则执行角色需要权限来执行此操作。

## Lambda@Edge 的服务相关角色
<a name="using-service-linked-roles-lambda-edge"></a>

Lambda@Edge 使用 IAM [服务相关角色](https://docs.aws.amazon.com/IAM/latest/UserGuide/id_roles_terms-and-concepts.html#iam-term-service-linked-role)。服务相关角色是一种与服务直接关联的独特类型的 IAM 角色。服务相关角色是由服务预定义的，具有服务代表您调用其他 AWS 服务所需的所有权限。

Lambda@Edge 使用以下 IAM 服务相关角色：
+ **AWSServiceRoleForLambdaReplicator** – Lambda@Edge 使用该角色来允许 Lambda@Edge 将函数复制到 AWS 区域。

  当您首次在 CloudFront 中添加 Lambda@Edge 触发器时，会自动创建一个名为 AWSServiceRoleForLambdaReplicator 的角色，以允许 Lambda@Edge 将函数复制到 AWS 区域。使用 Lambda@Edge 函数也需要该角色。例如，AWSServiceRoleForLambdaReplicator 角色的 ARN 如下所示：

  `arn:aws:iam::123456789012:role/aws-service-role/replicator.lambda.amazonaws.com/AWSServiceRoleForLambdaReplicator`
+ **AWSServiceRoleForCloudFrontLogger** – CloudFront 使用此角色将日志文件推送到 CloudWatch。您可以使用日志文件来调试 Lambda@Edge 验证错误。

  在添加 Lambda@Edge 函数关联以允许 CloudFront 将 Lambda@Edge 错误日志文件推送到 CloudWatch 时，将自动创建 AWSServiceRoleForCloudFrontLogger 角色。AWSServiceRoleForCloudFrontLogger 角色的 ARN 如下所示：

  `arn:aws:iam::account_number:role/aws-service-role/logger.cloudfront.amazonaws.com/AWSServiceRoleForCloudFrontLogger`

通过使用服务相关角色，您可以更轻松地设置和使用 Lambda@Edge，因为您不必手动添加所需的权限。Lambda@Edge 定义其服务相关角色的权限，并且仅 Lambda@Edge 可以担任该角色。定义的权限包括信任策略和权限策略。不能将该权限策略附加到任何其他 IAM 实体。

您必须先删除任何关联的 CloudFront 或 Lambda@Edge 资源，然后才能删除服务相关角色。这有助于保护您的 Lambda@Edge 资源，使您不会删除访问活动资源时仍需要的服务相关角色。

有关服务关联角色的更多信息，请参阅[CloudFront 的服务相关角色](security_iam_service-with-iam.md#security_iam_service-with-iam-roles-service-linked)。

### Lambda@Edge 的服务相关角色权限
<a name="slr-permissions-lambda-edge"></a>

Lambda@Edge 使用两个名为 **AWSServiceRoleForLambdaReplicator** 和 **AWSServiceRoleForCloudFrontLogger** 的服务相关角色。以下部分介绍了其中的每个角色的权限。

**Contents**
+ [Lambda Replicator 的服务相关角色权限](#slr-permissions-lambda-replicator)
+ [CloudFront Logger 的服务相关角色权限](#slr-permissions-cloudfront-logger)

#### Lambda Replicator 的服务相关角色权限
<a name="slr-permissions-lambda-replicator"></a>

此服务相关角色允许 Lambda 将 Lambda@Edge 函数复制到AWS 区域。

AWSServiceRoleForLambdaReplicator 服务关联角色信任 `replicator.lambda.amazonaws.com` 服务来代入角色。

角色权限策略允许 Lambda@Edge 对指定的资源完成以下操作：
+ `lambda:CreateFunction` 上的 `arn:aws:lambda:*:*:function:*`
+ `lambda:DeleteFunction` 上的 `arn:aws:lambda:*:*:function:*`
+ `lambda:DisableReplication` 上的 `arn:aws:lambda:*:*:function:*`
+ `iam:PassRole` 上的 `all AWS resources`
+  `cloudfront:ListDistributionsByLambdaFunction` 上的 `all AWS resources`

#### CloudFront Logger 的服务相关角色权限
<a name="slr-permissions-cloudfront-logger"></a>

该服务相关角色允许 CloudFront 将日志文件推送到 CloudWatch 账户，以便您可以调试 Lambda@Edge 验证错误。

AWSServiceRoleForCloudFrontLogger 服务关联角色信任 `logger.cloudfront.amazonaws.com` 服务来代入角色。

该角色权限策略允许 Lambda@Edge 对指定的 `arn:aws:logs:*:*:log-group:/aws/cloudfront/*` 资源执行以下操作：
+ `logs:CreateLogGroup` ``
+ `logs:CreateLogStream`
+ `logs:PutLogEvents`

您必须配置权限以允许 IAM 实体（如用户、组或角色）删除 Lambda@Edge 服务相关角色。有关更多信息，请参阅《IAM 用户指南》**中的[服务相关角色权限](https://docs.aws.amazon.com/IAM/latest/UserGuide/using-service-linked-roles.html#service-linked-role-permissions)。

### 为 Lambda@Edge 创建服务相关角色
<a name="create-slr-lambda-edge"></a>

通常您不需要为 Lambda@Edge 手动创建服务相关角色。在以下情况下，该服务自动为您创建角色：
+ 在首次创建触发器时，该服务会创建一个 AWSServiceRoleForLambdaReplicator 角色（如果该角色尚不存在）。该角色允许 Lambda 将 Lambda@Edge 函数复制到 AWS 区域。

  如果您删除服务相关角色，则在分配中为 Lambda@Edge 添加新触发器时，将再次创建该角色。
+ 在更新或创建具有 Lambda@Edge 关联的 CloudFront 分配时，该服务会创建 AWSServiceRoleForCloudFrontLogger 角色（如果该角色尚不存在）。该角色允许 CloudFront 将日志文件推送到 CloudWatch。

  如果删除服务相关角色，在更新或创建具有 Lambda@Edge 关联的 CloudFront 分配时，将再次创建该角色。

要手动创建这些服务相关角色，可以运行以下 AWS Command Line Interface（AWS CLI）命令：

**创建 AWSServiceRoleForLambdaReplicator 角色**
+ 运行如下命令。

  ```
  aws iam create-service-linked-role --aws-service-name replicator.lambda.amazonaws.com
  ```

**创建 AWSServiceRoleForCloudFrontLogger 角色**
+ 运行如下命令。

  ```
  aws iam create-service-linked-role --aws-service-name logger.cloudfront.amazonaws.com
  ```

### 编辑 Lambda@Edge 服务相关角色
<a name="edit-slr-lambda-edge"></a>

Lambda@Edge 不允许您编辑 AWSServiceRoleForLambdaReplicator 或 AWSServiceRoleForCloudFrontLogger 服务相关角色。在该服务创建服务相关角色后，您无法更改该角色的名称，因为不同的实体可能会引用该角色。但是，您可以使用 IAM 编辑角色描述。有关更多信息，请参阅《IAM 用户指南》** 中的[编辑服务相关角色](https://docs.aws.amazon.com/IAM/latest/UserGuide/using-service-linked-roles.html#edit-service-linked-role)。

### 支持 Lambda@Edge 服务相关角色的 AWS 区域
<a name="slr-regions-lambda-edge"></a>

CloudFront 支持在以下AWS 区域对 Lambda@Edge 使用服务相关角色：
+ 美国东部（弗吉尼亚州北部）– `us-east-1`
+ 美国东部（俄亥俄州）– `us-east-2`
+ 美国西部（加利福尼亚北部）– `us-west-1`
+ 美国西部（俄勒冈州）– `us-west-2`
+ 亚太地区（孟买）– (`ap-south-1`)
+ 亚太地区（首尔）– (`ap-northeast-2`)
+ 亚太地区（新加坡）– (`ap-southeast-1`)
+ 亚太地区（悉尼）– `ap-southeast-2`
+ 亚太地区（东京）– (`ap-northeast-1`)
+ 欧洲地区（法兰克福）– `eu-central-1`
+ 欧洲地区（爱尔兰）– `eu-west-1`
+ 欧洲地区（伦敦）– `eu-west-2`
+ 南美洲（圣保罗）– (`sa-east-1`)

# 编写和创建 Lambda@Edge 函数
<a name="lambda-edge-create-function"></a>

要使用 Lambda@Edge，您需要为 AWS Lambda 函数*编写* 代码。为了帮助您编写 Lambda@Edge 函数，请参阅以下资源：
+  [Lambda@Edge 事件结构](lambda-event-structure.md) – 了解可用于 Lambda@Edge 的事件结构。
+ [Lambda@Edge 函数示例](lambda-examples.md) – 函数示例（例如 A/B 测试和生成 HTTP 重定向）。

将 Node.js 或 Python 用于 Lambda@Edge 的编程模型与在 AWS 区域内使用 Lambda 的编程模型相同。有关更多信息，请参阅《AWS Lambda 开发人员指南》**中的[使用 Node.js 构建 Lambda 函数](https://docs.aws.amazon.com/lambda/latest/dg/lambda-nodejs.html)或[使用 Python 构建 Lambda 函数](https://docs.aws.amazon.com/lambda/latest/dg/lambda-python.html)。

在 Lambda@Edge 函数中，包含 `callback` 参数并返回适用于请求或响应事件的对象：
+ **请求事件** – 在响应中包含 `cf.request` 对象。

  如果要生成响应，请在响应中包含 `cf.response` 对象。有关更多信息，请参阅 [在请求触发器中生成 HTTP 响应](lambda-generating-http-responses.md#lambda-generating-http-responses-in-requests)。
+ **响应事件** – 在响应中包含 `cf.response` 对象。

编写您自己的代码或使用其中一个示例后，您就可以在 Lambda 中创建函数了。要创建函数或编辑现有函数，请参阅以下主题：

**Topics**
+ [创建 Lambda@Edge 函数](lambda-edge-create-in-lambda-console.md)
+ [编辑 Lambda 函数](lambda-edge-edit-function.md)

 在 Lambda 中创建函数后，您需要设置 Lambda 以基于特定的 CloudFront 事件（称为*触发器*）运行该函数。有关更多信息，请参阅 [为 Lambda@Edge 函数添加触发器](lambda-edge-add-triggers.md)。

# 创建 Lambda@Edge 函数
<a name="lambda-edge-create-in-lambda-console"></a>

要将 AWS Lambda 设置为运行基于 CloudFront 事件的 Lambda 函数，请按照以下步骤操作。<a name="lambda-edge-create-function-procedure"></a>

**创建 Lambda@Edge 函数**

1. 登录到 AWS 管理控制台，然后通过以下网址打开 AWS Lambda 控制台：[https://console.aws.amazon.com/lambda/](https://console.aws.amazon.com/lambda/)。

1. 如果您已有一个或多个 Lambda 函数，请选择**创建函数**。

   如果您没有任何函数，请选择**立即开始使用**。

1. 在页面顶部的“区域”列表中，选择**美国东部（弗吉尼亚州北部）**。

1. 使用您自己的代码创建函数，或者以 CloudFront 蓝图为基础创建函数。
   + 要使用您自己的代码创建函数，请选择**从头开始创作**。
   + 要显示 CloudFront 蓝图列表，请在筛选条件字段中输入 **cloudfront**，然后选择 **Enter** 键。

     如果您找到了自己要使用的蓝图，请选择该蓝图的名称。

1. 在**基本信息**部分，指定以下值：

   1. **名称** – 输入函数的名称。

   1. **角色** – 要快速入门，请选择**从模板创建新角色**。您也可以选择**选择现有角色**或**创建自定义角色**，然后按照提示填写本部分的信息。

   1. **角色名称** – 输入角色的名称。

   1. **策略模板** – 选择**基本 Edge Lambda 权限**。

1. 如果您在步骤 4 中选择了**从头开始创作**，请跳至步骤 7。

   如果您在步骤 4 中选择了蓝图，则可通过 **cloudfront** 部分创建一个触发器，它可将此函数与 CloudFront 分配和 CloudFront 事件中的缓存相关联。建议您现在选择**删除**，因此在创建函数时没有函数触发器。您可以在稍后添加触发器。
**提示**  
建议您先测试和调试该函数，然后再添加触发器。如果选择立即添加触发器，则在您创建该函数，该函数完成向全球 AWS 位置的复制，并且相应的分配部署完成后，该函数将立即开始运行。

1. 选择**创建函数**。

   Lambda 将创建两个版本的函数：\$1LATEST 和 Version 1。您只能编辑 \$1LATEST 版本，但控制台最初会显示 Version 1。

1. 要编辑函数，请选择页面顶部附近、函数 ARN 下方的 **Version 1**。然后，在 **Versions** 选项卡上，选择 **\$1LATEST**。（如果您离开再返回到该函数，则按钮标签将是 **Qualifiers**。）

1. 在 **Configuration** 选项卡上，选择适用的 **Code entry type**。然后，按照提示编辑或上传您的代码。

1. 对于**运行时**，请根据函数的代码选择值。

1. 在**标签**部分中，添加任何适用的标签。

1. 选择**操作**，然后选择**发布新版本**。

1. 输入新版本函数的描述。

1. 选择 **Publish**。

1. 测试并调试函数。有关在 Lambda 控制台中进行测试的更多信息，请参阅《AWS Lambda 开发人员指南》**中的[使用控制台调用 Lambda 函数](https://docs.aws.amazon.com/lambda/latest/dg/getting-started.html#get-started-invoke-manually)。

1. 当您准备好为 CloudFront 事件执行函数时，发布另一个版本并编辑该函数以添加触发器。有关更多信息，请参阅 [为 Lambda@Edge 函数添加触发器](lambda-edge-add-triggers.md)。

# 编辑 Lambda 函数
<a name="lambda-edge-edit-function"></a>

在创建 Lambda@Edge 函数后，可以使用 Lambda 控制台对其进行编辑。

**备注**  
原始版本标记为 \$1LATEST。
您只能编辑 \$1LATEST 版本。
每次编辑 \$1LATEST 版本时，均必须发布带编号的新版本。
您无法为 \$1LATEST 创建触发器。
当您发布函数的新版本时，Lambda 不会将触发器从以前的版本自动复制到新版本中。您必须为新版本重现触发器。
当您将 CloudFront 事件的触发器添加到函数中时，如果已经有一个针对相同分配、缓存行为和同一函数早期版本的事件的触发器，则 Lambda 会从早期版本中删除该触发器。
在更新 CloudFront 分配（如添加触发器）后，您必须等待更改传播到边缘站点，您在触发器中指定的函数才能运行。<a name="lambda-edge-edit-function-procedure"></a>

**要编辑 Lambda 函数**

1. 登录到 AWS 管理控制台，然后通过以下网址打开 AWS Lambda 控制台：[https://console.aws.amazon.com/lambda/](https://console.aws.amazon.com/lambda/)。

1. 在页面顶部的“区域”列表中，选择**美国东部（弗吉尼亚州北部）**。

1. 在函数列表中，选择函数的名称。

   默认情况下，控制台会显示 \$1LATEST 版本。您可以查看早期版本（选择 **Qualifiers**），但是只能编辑 \$1LATEST。

1. 在**代码**选项卡上，对于**代码输入种类**，选择在浏览器中编辑代码、上传 .zip 文件，或从 Amazon S3 上传文件。

1. 选择**保存**或**保存并测试**。

1. 选择**操作**，然后选择**发布新版本**。

1. 在 **Publish new version from \$1LATEST** 对话框中，输入新版本的描述。此描述会与自动生成的版本号一起显示在版本列表中。

1. 选择 **Publish**。

   新版本将自动成为最新版本。版本号会显示在页面左上角的**版本**中。
**注意**  
如果您尚未为函数添加触发器，请参阅 [为 Lambda@Edge 函数添加触发器](lambda-edge-add-triggers.md)。

1. 选择**触发器**选项卡。

1. 选择 **Add trigger**。

1. 在**添加触发器**对话框中，选择虚线框，然后再选择 **CloudFront**。
**注意**  
如果您已为函数创建一个或多个触发器，则 CloudFront 为默认服务。

1. 指定以下值，以指示您希望 Lambda 函数何时执行。

   1. **分配 ID** – 选择要向其中添加触发器的分配的 ID。

   1. **缓存行为** – 选择缓存行为，该行为将指定您要对其执行函数的对象。

   1. **CloudFront 事件** – 选择促使函数执行的 CloudFront 事件。

   1. **启用触发器并复制** – 选中此复选框，以便 Lambda 将函数复制到全球各地的 AWS 区域。

1. 选择 **Submit**。

1. 要为该函数添加更多触发器，请重复步骤 10 到 13。

有关在 Lambda 控制台中测试和调试函数的更多信息，请参阅《AWS Lambda 开发人员指南》**中的[使用控制台调用 Lambda 函数](https://docs.aws.amazon.com/lambda/latest/dg/getting-started.html#get-started-invoke-manually)。

当您准备好为 CloudFront 事件执行函数时，发布另一个版本并编辑该函数以添加触发器。有关更多信息，请参阅 [为 Lambda@Edge 函数添加触发器](lambda-edge-add-triggers.md)。

# 为 Lambda@Edge 函数添加触发器
<a name="lambda-edge-add-triggers"></a>

Lambda@Edge 触发器是指 CloudFront 分配、缓存行为与使函数开始执行的事件的组合。例如，您可以创建一个触发器，在 CloudFront 收到来自为分配所设置的特定缓存行为查看器的请求时执行函数。您可以指定一个或多个 CloudFront 触发器。

**提示**  
在创建 CloudFront 分配时，您可以指定一些设置来告诉 CloudFront 在收到不同的请求时如何响应。默认设置称为分配的*默认缓存行为*。您可以设置其他缓存行为来定义 CloudFront 在特定情况下（例如，在收到特定文件类型的请求时）如何响应。有关更多信息，请参阅 [缓存行为设置](DownloadDistValuesCacheBehavior.md)。

首次创建 Lambda 函数时，可以仅指定*一个*触发器。您可以通过使用 Lambda 控制台或在 CloudFront 控制台中编辑分配，在稍后向同一函数中添加更多触发器。
+ 如果您要将更多触发器添加到同一 CloudFront 分配的函数中，那么使用 Lambda 控制台非常有效。
+ 如果要为多个分配添加触发器，那么使用 CloudFront 控制台非常有效，因为这样更方便查找您要更新的分配。您同时还可以更新其他 CloudFront 设置。

**Topics**
+ [可以触发 Lambda@Edge 函数的 CloudFront 事件](lambda-cloudfront-trigger-events.md)
+ [选择要触发函数的事件](lambda-how-to-choose-event.md)
+ [将触发器添加到 Lambda@Edge 函数中](lambda-edge-add-triggers-console.md)

# 可以触发 Lambda@Edge 函数的 CloudFront 事件
<a name="lambda-cloudfront-trigger-events"></a>

对于 Amazon CloudFront 分配中的每个缓存行为，您最多可添加四个触发器（关联），以便在发生特定 CloudFront 事件时触发 Lambda 函数执行。CloudFront 触发器可以基于四个 CloudFront 事件之一，如下图所示。

![\[显示 Lambda 函数的 CloudFront 触发器事件如何与 CloudFront 集成的概念图。\]](http://docs.aws.amazon.com/zh_cn/AmazonCloudFront/latest/DeveloperGuide/images/cloudfront-events-that-trigger-lambda-functions.png)


可用于触发 Lambda@Edge 函数的 CloudFront 事件如下所示：

**查看器请求**  
当 CloudFront 收到查看器的请求时及它检查请求的对象是否在 CloudFront 缓存中之前，该函数会执行。  
在以下情况下该函数不会执行：  
+ 当获取自定义错误页面时。
+ 当 CloudFront 自动将 HTTP 请求重定向到 HTTPS 时（当 [查看器协议策略](DownloadDistValuesCacheBehavior.md#DownloadDistValuesViewerProtocolPolicy) 的值为**将 HTTP 重定向到 HTTPS** 时）。

**源请求**  
*仅*当 CloudFront 将请求转发给您的源时，该函数才会执行。当请求的对象位于 CloudFront 缓存中时，该函数不会执行。

**源响应**  
在 CloudFront 收到来自源的响应之后及它将对象缓存在响应中之前，该函数会执行。请注意，即使从源返回了错误，该函数仍会执行。  
在以下情况下该函数不会执行：  
+ 当请求的文件位于 CloudFront 缓存中并且未过期时。
+ 当从由源请求事件触发的函数中生成响应时。

**查看器响应**  
在将请求的文件返回到查看器之前，该函数会执行。请注意，无论文件是否已在 CloudFront 缓存中，该函数都会执行。  
在以下情况下该函数不会执行：  
+ 当源返回 400 或更高的 HTTP 状态代码时。
+ 当返回自定义错误页面时。
+ 当从由查看器请求事件触发的函数中生成响应时。
+ 当 CloudFront 自动将 HTTP 请求重定向到 HTTPS 时（当 [查看器协议策略](DownloadDistValuesCacheBehavior.md#DownloadDistValuesViewerProtocolPolicy) 的值为**将 HTTP 重定向到 HTTPS** 时）。

当对同一个缓存行为添加多个触发器时，您可以使用它们运行同一个函数或对每个触发器运行不同的函数。也可以将同一个函数与多个分配关联。

**注意**  
当 CloudFront 事件触发 Lambda 函数的执行时，该函数必须完成，*然后* CloudFront 才能继续。  
例如，如果 Lambda 函数被某个 CloudFront 查看器请求事件触发，则在 Lambda 函数完成运行之前，CloudFront 不会将响应返回给查看器或将请求转发到源。  
这意味着触发 Lambda 函数的每个请求均会增加请求的延迟，因此您可能希望该函数尽快执行。

# 选择要触发函数的事件
<a name="lambda-how-to-choose-event"></a>

当您决定使用哪个 CloudFront 事件来触发 Lambda 函数时，请考虑以下因素：

**我希望 CloudFront 缓存由 Lambda 函数更改的对象**  
要缓存由 Lambda 函数修改的对象，以便下次请求该对象时 CloudFront 可以从边缘站点提供该对象，请使用*源请求*或*源响应*事件。  
这样可减少源上的负载、减少后续请求的延迟，并降低对后续请求调用 Lambda@Edge 的成本。  
例如，如果要添加、删除或更改由源返回的对象的标头，并且希望 CloudFront 缓存结果，请使用源响应事件。

**我希望该函数针对每个请求执行**  
要针对 CloudFront 为分配接收的每个请求执行该函数，请使用*查看器请求*或*查看器响应*事件。  
只有在未将请求的对象缓存在边缘站点中且 CloudFront 将请求转发到源时，才会发生源请求和源响应事件。

**我希望函数更改缓存键**  
要更改您要用作缓存基础的值，请使用*查看器请求*事件。  
例如，如果某个函数将 URL 更改为在路径中包含语言缩写 (例如，由于用户从下拉列表中选择了其语言)，请使用查看器请求事件：  
+ **查看器请求中的 URL** – https://example.com/en/index.html
+ **在请求来自德国的一个 IP 地址时的 URL** – https://example.com/de/index.html
如果您要根据 Cookie 或请求标头缓存，则也使用查看器请求事件。  
如果该函数更改 Cookie 或标头，则将 CloudFront 配置为将请求的适用部分转发到源。有关更多信息，请参阅以下主题：  
+ [根据 Cookie 缓存内容](Cookies.md)
+ [根据请求标头缓存内容](header-caching.md)

**函数影响来自源的响应**  
要以影响源响应的方式更改请求，请使用*源请求*事件。  
通常，大多数查看器请求事件都不会被转发到源。CloudFront 使用已在边缘缓存中的对象来响应请求。如果该函数基于源请求事件更改请求，则 CloudFront 会缓存对更改的源请求的响应。

# 将触发器添加到 Lambda@Edge 函数中
<a name="lambda-edge-add-triggers-console"></a>

您可以使用 AWS Lambda 控制台或 Amazon CloudFront 控制台向 Lambda@Edge 函数中添加触发器。

**重要**  
您只能为函数的编号版本（不能为 **\$1LATEST**）创建触发器。

------
#### [ Lambda console ]<a name="lambda-edge-add-triggers-procedure"></a>

**将 CloudFront 事件的触发器添加到 Lambda@Edge 函数**

1. 登录到 AWS 管理控制台，然后通过以下网址打开 AWS Lambda 控制台：[https://console.aws.amazon.com/lambda/](https://console.aws.amazon.com/lambda/)。

1. 在页面顶部的“区域”列表中，选择**美国东部（弗吉尼亚州北部）**。

1. 在**函数**页面上，选择您要为其添加触发器的函数的名称。

1. 在**函数概述**页面上，选择**版本**选项卡。

1. 选择您要为其添加触发器的版本。

   选择某个版本后，相应按钮的名称会更改为 **Version: \$1LATEST** 或 **Version:** *版本号*。

1. 选择**触发器**选项卡。

1. 选择**添加触发器**。

1. 在**触发器配置**中，选择**选择源**，输入 **cloudfront**，然后选择 **CloudFront**。
**注意**  
如果您已创建一个或多个触发器，则 CloudFront 为默认服务。

1. 指定以下值，以指示您希望 Lambda 函数何时执行。

   1. **分配** – 选择要向其中添加触发器的分配。

   1. **缓存行为** – 选择缓存行为，该行为将指定您要对其执行函数的对象。
**注意**  
如果您对缓存行为指定 `*`，则 Lambda 函数会部署到默认缓存行为。

   1. **CloudFront 事件** – 选择促使函数执行的 CloudFront 事件。

   1. **包括正文** – 如果要在函数中访问请求正文，请选中该复选框。

   1. **确认部署到 Lambda@Edge** – 选中该复选框，以便 AWS Lambda 将函数复制到全球各地的 AWS 区域。

1. 选择**添加**。

   在更新的 CloudFront 分配部署后，函数开始处理指定 CloudFront 事件的请求。要确定是否已部署分配，请在导航窗格中选择**分配**。在部署分配后，分配的**状态**列的值将从**正在部署**更改为部署日期和时间。

------
#### [ CloudFront console ]<a name="lambda-create-functions-add-triggers-cloudfront-console-procedure"></a>

**将 CloudFront 事件的触发器添加到 Lambda@Edge 函数**

1. 获取您要为其添加触发器的 Lambda 函数的 ARN：

   1. 登录到 AWS 管理控制台，然后通过以下网址打开 AWS Lambda 控制台：[https://console.aws.amazon.com/lambda/](https://console.aws.amazon.com/lambda/)。

   1. 在页面顶部的区域列表中，选择**美国东部（弗吉尼亚州北部）**。

   1. 在函数列表中，选择您要为其添加触发器的函数的名称。

   1. 在**函数概述**页面上，选择**版本**选项卡，然后再选择要为其添加触发器的带编号的版本。

   1. 选择**复制 ARN** 按钮，将 ARN 复制到剪贴板。Lambda 函数的 ARN 如下所示：

      `arn:aws:lambda:us-east-1:123456789012:function:TestFunction:2`

      末尾的号码（在本示例中为 **2**）是函数的版本号。

1. 通过 [https://console.aws.amazon.com/cloudfront/v4/home](https://console.aws.amazon.com/cloudfront/v4/home) 打开 CloudFront 控制台

1. 在分配列表中，选择要为其添加触发器的分配的 ID。

1. 选择**行为**选项卡。

1. 选择要为其添加触发器的缓存行为，然后选择**编辑**。

1. 对于**函数关联**，请在**函数类型**列表中，选择 **Lambda@Edge**，以确定您希望何时执行函数：针对查看器请求、查看器响应、源请求或源响应。

   有关更多信息，请参阅 [选择要触发函数的事件](lambda-how-to-choose-event.md)。

1. 在**函数 ARN/名称**文本框中，粘贴当所选事件发生时您要执行的 Lambda 函数的 ARN。这是您从 Lambda 控制台复制的值。

1. 如果要在函数中访问请求正文，请选择**包含正文**。

   如果您仅希望替换请求正文，则不需要选择该选项。

1. 要对更多事件类型执行同一函数，请重复步骤 6 和 7。

1. 选择**保存更改**。

1. 要针对该分配为更多缓存行为添加触发器，请重复步骤 5 到 10。

   在更新的 CloudFront 分配部署后，函数开始处理指定 CloudFront 事件的请求。要确定是否已部署分配，请在导航窗格中选择**分配**。在部署分配后，分配的**状态**列的值将从**正在部署**更改为部署时间和日期。

------

# 测试和调试 Lambda@Edge 函数
<a name="lambda-edge-testing-debugging"></a>

请务必单独测试 Lambda@Edge 函数代码以确保它完成预期的任务，并进行集成测试以确保该函数在 CloudFront 中正常工作。

在集成测试期间或在部署函数后，您可能需要调试 CloudFront 错误，例如，HTTP 5xx 错误。错误可能是从 Lambda 函数返回的无效响应、触发该函数时的执行错误，或者由于 Lambda 服务限制执行而产生的错误。本主题中的部分使用相同的策略以确定问题是哪种故障类型，并采取相同的措施以纠正该问题。

**注意**  
如果在纠正错误时查看 CloudWatch 日志文件或指标，请注意它们显示或存储在位置最接近执行函数的 AWS 区域。因此，如果您的网站或 Web 应用程序用户位于英国，并且 Lambda 函数与您的分配关联，您必须更改区域以查看伦敦 AWS 区域的 CloudWatch 指标或日志文件。有关更多信息，请参阅 [确定 Lambda@Edge 区域](#lambda-edge-testing-debugging-determine-region)。

**Topics**
+ [测试 Lambda@Edge 函数](#lambda-edge-testing-debugging-test-function)
+ [识别 CloudFront 中的 Lambda@Edge 函数错误](#lambda-edge-identifying-function-errors)
+ [排查 Lambda@Edge 函数响应无效问题（验证错误）](#lambda-edge-testing-debugging-troubleshooting-invalid-responses)
+ [排查 Lambda@Edge 函数执行错误](#lambda-edge-testing-debugging-execution-errors)
+ [确定 Lambda@Edge 区域](#lambda-edge-testing-debugging-determine-region)
+ [确定您的账户是否将日志推送到 CloudWatch](#lambda-edge-testing-debugging-cloudwatch-logs-enabled)

## 测试 Lambda@Edge 函数
<a name="lambda-edge-testing-debugging-test-function"></a>

可以使用两个步骤测试 Lambda 函数：单独测试和集成测试。

**测试单独功能**  
在将 Lambda 函数添加到 CloudFront 之前，请确保先使用 Lambda 控制台中的测试功能或其他方法测试该功能。有关在 Lambda 控制台中进行测试的更多信息，请参阅《AWS Lambda 开发人员指南》**中的[使用控制台调用 Lambda 函数](https://docs.aws.amazon.com/lambda/latest/dg/getting-started.html#get-started-invoke-manually)。

**在 CloudFront 中测试您的函数的运行情况**  
请务必完成集成测试，其中，您的函数与一个分配关联并根据 CloudFront 事件运行。确保为正确的事件触发函数，并为 CloudFront 返回有效且正确的响应。例如，确保事件结构正确无误，仅包含有效标头等等。  
在 Lambda 控制台上重复对函数进行集成测试时，请在修改代码或更改调用函数的 CloudFront 触发器时参阅 Lambda@Edge 教程中的步骤。例如，确保您使用带编号的函数版本，如本教程中的以下步骤所述：[第 4 步：添加 CloudFront 触发器来运行函数](lambda-edge-how-it-works-tutorial.md#lambda-edge-how-it-works-tutorial-add-trigger)。  
在进行更改和部署时，请注意需要几分钟的时间以在所有区域中复制更新的函数和 CloudFront 触发器。这通常需要几分钟，但最长需要 15 分钟。  
您可以转到 CloudFront 控制台并查看您的分配，确认复制是否完成。  

**检查您的复制是否已完成部署**

1. 通过以下网址打开 CloudFront 控制台：[https://console.aws.amazon.com/cloudfront/v4/home](https://console.aws.amazon.com/cloudfront/v4/home)。

1. 选择分配名称。

1. 查看分配的状态是否已从**正在进行**恢复为**已部署**，这意味着已复制函数。然后，按照下一节中的步骤验证函数是否正常工作。
请注意，控制台中的测试只会验证函数的逻辑，而不会应用特定于 Lambda@Edge 的任何服务限额（以前称为限制）。

## 识别 CloudFront 中的 Lambda@Edge 函数错误
<a name="lambda-edge-identifying-function-errors"></a>

在确认您的函数逻辑正常工作后，在 CloudFront 中运行函数时，您可能仍会看到 HTTP 5xx 错误。可能返回 HTTP 5xx 错误的原因有很多，其中包括 Lambda 函数错误或 CloudFront 中的其他问题。
+ 如果您使用 Lambda@Edge 函数，则可以在 CloudFront 控制台中使用图表帮助跟踪导致错误的原因，然后进行修复。例如，您可以查看是 CloudFront 还是 Lambda 函数导致了 HTTP 5xx 错误，然后，对于特定函数，您可以查看相关的日志文件来调查问题。
+ 要对 CloudFront 中的常规 HTTP 错误进行问题排查，请参阅以下主题中的问题排查步骤：[对 CloudFront 中的错误响应状态代码进行故障排除](troubleshooting-response-errors.md)。

### 是什么原因导致 CloudFront 中的 Lambda@Edge 函数错误
<a name="lambda-edge-testing-debugging-function-errors"></a>

Lambda 函数导致 HTTP 5xx 错误可能有很多原因，您应采取的故障排除措施取决于错误类型。错误可以归类如下：

**Lambda 函数执行错误。**  
如果由于函数中存在未处理的异常或代码中存在错误，使得 CloudFront 没有从 Lambda 中收到响应，则会导致执行错误。例如，如果代码包含回调（错误）。

**向 CloudFront 返回无效的 Lambda 函数响应**  
函数运行后，CloudFront 会收到来自 Lambda 的响应。如果响应的对象结构不符合 [Lambda@Edge 事件结构](lambda-event-structure.md)，或响应包含无效的标头或其他无效的字段，则会返回错误。

**由于 Lambda 服务配额（以前称为限制），CloudFront 中的执行受到限制**  
Lambda 服务限制每个区域中的执行次数，并在超过配额时返回错误。有关更多信息，请参阅 [有关 Lambda@Edge 的配额](cloudfront-limits.md#limits-lambda-at-edge)。

### 如何确定故障类型
<a name="lambda-edge-testing-debugging-failure-type"></a>

为了帮助您在调试和处理以解决 CloudFront 返回的错误时确定重点，确定 CloudFront 返回 HTTP 错误的原因会有所帮助。要开始使用，您可以使用 AWS 管理控制台 上 CloudFront 控制台的**监控**部分中提供的图表。有关在 CloudFront 控制台的**监控**部分中查看图表的更多信息，请参阅[使用 Amazon CloudWatch 监控 CloudFront 指标](monitoring-using-cloudwatch.md)。

在您希望跟踪错误是由源还是由 Lambda 函数返回时，以及在错误源自 Lambda 函数时需要缩小问题类型的范围时，下列图表会非常有用。

**错误率图表**  
在**概览**选项卡上，您可以看到各个分配的一个图表是**错误率**图表。此图表显示相对于传入到您分配的请求总数，错误率的百分比。图表显示总错误率、4xx 错误总数、5xx 错误总数以及来自 Lambda 函数的 5xx 错误总数。根据错误类型和卷，您可以采取措施来调查和排查造成错误的原因。  

![\[CloudFront 分配的错误率图表\]](http://docs.aws.amazon.com/zh_cn/AmazonCloudFront/latest/DeveloperGuide/images/Distribution-error-rate-pct-full.png)

+ 如果您看到 Lambda 错误，可以通过查看函数返回的特定类型错误来进一步进行调查。**Lambda@Edge 错误**选项卡包含按类型分类函数错误的图表，帮助您确定特定函数的问题。
+ 如果您看到 CloudFront 错误，可以进行故障排查和处理来修复原始错误，或者更改 CloudFront 配置。有关更多信息，请参阅 [对 CloudFront 中的错误响应状态代码进行故障排除](troubleshooting-response-errors.md)。

**执行错误和无效函数响应图表**  
**Lambda@Edge 错误**选项卡包含针对特定分配按类型对 Lambda@Edge 错误进行分类的图表。例如，一个图表按 AWS 区域显示所有执行错误。  
要更轻松地解决问题，您可以通过按区域打开并检查特定函数的日志，来查找特定问题。  

**按区域查看特定函数的日志文件**

1. 在 **Lambda@Edge 错误**选项卡上，在**关联的 Lambda@Edge 函数**下，选择函数名称，然后选择**查看指标**。

1. 接下来，在包含函数名称的页面上，在右上角选择**查看函数日志**，然后选择一个区域。

   例如，如果您在**错误**图表中看到美国西部（俄勒冈州）区域存在问题，请从下拉列表中选择该区域。这将打开 Amazon CloudWatch 控制台。

1. 在该区域的 CloudWatch 控制台中，在**日志流**下，选择一个日志流以查看该函数的事件。
此外，阅读本章中的下列部分，了解有关问题排查和修复错误的更多建议。

**限制图表**  
**Lambda@Edge 错误**选项卡还包括一个**限制**图表。有时候，在您达到了区域并发限制（以前称为限制）时，Lambda 服务会按区域限制函数调用。如果您发现超出限制错误，则您的函数已达到 Lambda 服务对在区域中执行所施加的配额。有关更多信息（包括如何请求提高配额），请参阅[有关 Lambda@Edge 的配额](cloudfront-limits.md#limits-lambda-at-edge)。  

![\[Lambda@Edge 函数执行的限制图表。\]](http://docs.aws.amazon.com/zh_cn/AmazonCloudFront/latest/DeveloperGuide/images/Lambda-throttles-page.png)


有关如何在排查 HTTP 错误时使用此信息的示例，请参阅[调试 AWS 上的内容分发的四个步骤](https://aws.amazon.com/blogs/networking-and-content-delivery/four-steps-for-debugging-your-content-delivery-on-aws/)。

## 排查 Lambda@Edge 函数响应无效问题（验证错误）
<a name="lambda-edge-testing-debugging-troubleshooting-invalid-responses"></a>

如果您发现问题是 Lambda 验证错误，则表示您的 Lambda 函数向 CloudFront 返回无效的响应。请按照此部分中的指导采取措施以检查您的函数，并确保您的响应符合 CloudFront 要求。

CloudFront 通过两种方式验证来自 Lambda 函数的响应：
+ **Lambda 响应必须符合所需的对象结构。**不正确的对象结构示例包括：无法解析 JSON，缺少必填字段以及在响应中包含无效的对象。有关更多信息，请参阅[Lambda@Edge 事件结构](lambda-event-structure.md)。
+ **响应只能包含有效的对象值。**如果响应包含有效的对象，但具有不支持的值，则会出现错误。示例包括：添加或更新列入黑名单的标头或只读标头（请参阅 [边缘函数的限制](edge-functions-restrictions.md)），超出最大正文大小（请参阅 Lambda [错误](lambda-generating-http-responses.md#lambda-generating-http-responses-errors) 主题中的*生成的响应大小的限制*）和无效的字符或值（请参阅 [Lambda@Edge 事件结构](lambda-event-structure.md)）。

在 Lambda 向 CloudFront 返回无效的响应时，将在日志文件中写入错误消息，CloudFront 将这些日志文件推送到执行 Lambda 函数所在的区域中的 CloudWatch。这是在具有无效的响应时将日志文件发送到 CloudWatch 的默认行为。不过，如果在发布该功能之前将 Lambda 函数与 CloudFront 相关联，则可能不会为您的函数启用该功能。有关更多信息，请参阅本主题后面的*确定您的账户是否将日志推送到 CloudWatch*。

CloudFront 将日志文件推送到与您函数的执行位置对应的区域（在与您的分配关联的日志组中）。日志组具有以下格式：`/aws/cloudfront/LambdaEdge/DistributionId`，其中 *DistributionId* 是您的分配的 ID。要确定可以找到 CloudWatch 日志文件的区域，请参阅本主题后面的*确定 Lambda@Edge 区域*。

如果可再现该错误，您可以创建一个导致该错误的新请求，然后在失败的 CloudFront 响应（`X-Amz-Cf-Id` 标头）中找到请求 ID 以在日志文件中找到单个故障。日志文件条目包含可帮助您确定返回错误的原因的信息，并且还会列出相应的 Lambda 请求 ID，以便您可以在单个请求的上下文中分析根本原因。

如果错误是间歇性的，您可以使用 CloudFront 访问日志查找失败请求的请求 ID，然后在 CloudWatch 日志中搜索相应的错误消息。有关更多信息，请参阅上一节*确定故障类型*。

## 排查 Lambda@Edge 函数执行错误
<a name="lambda-edge-testing-debugging-execution-errors"></a>

如果问题是 Lambda 执行错误，为 Lambda 函数创建日志记录语句以将消息写入到 CloudWatch 日志文件可能是非常有用的，从而在 CloudFront 中监视函数的执行情况并确定它是否正常工作。然后，您可以在 CloudWatch 日志文件中搜索这些语句，以验证您的函数是否正常工作。

**注意**  
即使您尚未更改 Lambda@Edge 函数，对该 Lambda 函数执行环境的更新也可能会影响它并可能返回执行错误。有关测试和迁移到更高版本的信息，请参阅[对 AWS Lambda 和 AWS Lambda@Edge 执行环境的近期更新](https://aws.amazon.com/blogs/compute/upcoming-updates-to-the-aws-lambda-execution-environment/)。

## 确定 Lambda@Edge 区域
<a name="lambda-edge-testing-debugging-determine-region"></a>

要查看 Lambda@Edge 函数接收流量的区域，请在 AWS 管理控制台上的 CloudFront 控制台中查看此函数的指标。指标针对各个 AWS 区域显示。在同一页上，您可以选择一个区域并查看该区域的日志文件，从而调查问题。您必须查看相应 AWS 区域中的 CloudWatch 日志文件，以查看在 CloudFront 执行 Lambda 函数时创建的日志文件。

有关在 CloudFront 控制台的**监控**部分中查看图表的更多信息，请参阅[使用 Amazon CloudWatch 监控 CloudFront 指标](monitoring-using-cloudwatch.md)。

## 确定您的账户是否将日志推送到 CloudWatch
<a name="lambda-edge-testing-debugging-cloudwatch-logs-enabled"></a>

默认情况下，CloudFront 为无效的 Lambda 函数响应启用日志记录，并使用[Lambda@Edge 的服务相关角色](lambda-edge-permissions.md#using-service-linked-roles-lambda-edge)之一将日志文件推送到 CloudWatch。如果在发布无效的 Lambda 函数响应日志功能之前将 Lambda@Edge 函数添加到 CloudFront，下次更新 Lambda@Edge 配置时，将启用日志记录，例如，通过添加 CloudFront 触发器。

您可以执行以下操作，以验证是否为您的账户启用将日志文件推送到 CloudWatch 的功能：
+ **检查日志是否显示在 CloudWatch 中** – 确保您查看了执行 Lambda@Edge 函数的区域。有关更多信息，请参阅 [确定 Lambda@Edge 区域](#lambda-edge-testing-debugging-determine-region)。
+ **在 IAM 中确定您的账户中是否存在相关的服务相关角色** – 您的账户中必须有 IAM 角色 `AWSServiceRoleForCloudFrontLogger`。有关该角色的更多信息，请参阅[Lambda@Edge 的服务相关角色](lambda-edge-permissions.md#using-service-linked-roles-lambda-edge)。

# 删除 Lambda@Edge 函数和副本
<a name="lambda-edge-delete-replicas"></a>

仅当 CloudFront 已创建 Lambda@Edge 函数的副本时，您才能删除该函数。在以下情况下，Lambda 函数的副本将自动删除：
+ 在您从所有 CloudFront 分配中删除该函数的上一个关联后。如果多个分配使用一个函数，则仅在从上一个分配中删除函数关联后删除副本。
+ 在您删除与函数关联的上一个分配后。

通常，将在数小时内删除副本。无法手动删除 Lambda@Edge 函数副本。这有助于防止出现删除仍在使用的副本的情况，这种情况将导致错误。

**警告**  
不要构建使用 CloudFront 外部的 Lambda@Edge 函数副本的应用程序。当删除它们与分配的关联，或者删除分配本身时，将删除这些副本。可能在不发出警告的情况下删除外部应用程序所依赖的副本，这会导致其失败。

**从 CloudFront 分配中删除 Lambda@Edge 函数关联**

1. 登录 AWS 管理控制台，并通过以下网址打开 CloudFront 控制台：[https://console.aws.amazon.com/cloudfront/v4/home](https://console.aws.amazon.com/cloudfront/v4/home)。

1. 选择具有要删除的 Lambda@Edge 函数关联的分配的 ID。

1. 选择**行为**选项卡。

1. 选择具有要删除的 Lambda@Edge 函数关联的缓存行为，然后选择**编辑**。

1. 在**函数关联**的**函数类型**下，选择**无关联**以删除 Lambda@Edge 函数关联。

1. 选择**保存更改**。

从 CloudFront 分配中删除 Lambda@Edge 函数关联后，可以选择性地从 中删除 Lambda 函数或函数版本AWS Lambda 删除函数关联后，等待几个小时，以便清理 Lambda@Edge 函数副本。之后，您可以使用 Lambda 控制台、AWS CLI、Lambda API 或 AWS SDK 删除该函数。

您还可以删除特定*版本*的 Lambda 函数，前提是该版本没有任何与之关联的 CloudFront 分配。删除某个 Lambda 函数版本的所有关联后，请等待几小时。然后，您可以删除该函数版本。

# Lambda@Edge 事件结构
<a name="lambda-event-structure"></a>

下面的主题介绍了触发 Lambda@Edge 函数时，CloudFront 传递给该函数的请求和响应事件对象。

**Topics**
+ [动态源选择](#lambda-event-content-based-routing)
+ [请求事件](#lambda-event-structure-request)
+ [响应事件](#lambda-event-structure-response)

## 动态源选择
<a name="lambda-event-content-based-routing"></a>

您可以[在缓存行为中使用路径模式](DownloadDistValuesCacheBehavior.md#DownloadDistValuesPathPattern)，根据所请求对象的路径和名称将请求路由到源，例如 `images/*.jpg`。使用 Lambda@Edge，您也可以基于其他功能将请求路由到源，例如请求标头中的值。

在多种情况下，这种动态源选择会非常有用。例如，您可以跨不同地理区域中的源分配请求，帮助实现全球负载均衡。或者，您可以选择性地将请求路由到不同的源，每个服务器提供特定功能：自动程序处理、SEO 优化、身份验证等。有关演示如何使用此功能的代码示例，请参阅 [基于内容的动态源选择 - 示例](lambda-examples.md#lambda-examples-content-based-routing-examples)。

在 CloudFront 源请求事件中，根据路径模式，事件结构中的 `origin` 对象包含有关将请求路由到的源的信息。您可以更新 `origin` 对象中的值，将请求路由到不同的源。更新 `origin` 对象时，您不需要在分配中定义源。您还可以将 Amazon S3 源对象替换为自定义源对象，或者执行相反的操作。但是，只能为每个请求指定一个源；该源可以是自定义源，也可以是 Amazon S3 源，但不能同时指定这两种源。

## 请求事件
<a name="lambda-event-structure-request"></a>

下面的主题显示了 CloudFront 传递给[查看器和源请求事件](lambda-cloudfront-trigger-events.md)的 Lambda 函数的对象结构。这些示例显示了不带正文的 `GET` 请求。下面一些示例中，列出了查看器和源请求事件中的所有的可能字段。

**Topics**
+ [示例查看器请求](#example-viewer-request)
+ [示例源请求](#example-origin-request)
+ [请求事件字段](#request-event-fields)

### 示例查看器请求
<a name="example-viewer-request"></a>

下面的示例显示查看器请求事件对象。

```
{
  "Records": [
    {
      "cf": {
        "config": {
          "distributionDomainName": "d111111abcdef8.cloudfront.net",
          "distributionId": "EDFDVBD6EXAMPLE",
          "eventType": "viewer-request",
          "requestId": "4TyzHTaYWb1GX1qTfsHhEqV6HUDd_BzoBZnwfnvQc_1oF26ClkoUSEQ=="
        },
        "request": {
          "clientIp": "203.0.113.178",
          "headers": {
            "host": [
              {
                "key": "Host",
                "value": "d111111abcdef8.cloudfront.net"
              }
            ],
            "user-agent": [
              {
                "key": "User-Agent",
                "value": "curl/7.66.0"
              }
            ],
            "accept": [
              {
                "key": "accept",
                "value": "*/*"
              }
            ]
          },
          "method": "GET",
          "querystring": "",
          "uri": "/"
        }
      }
    }
  ]
}
```

### 示例源请求
<a name="example-origin-request"></a>

下面的示例显示源请求事件对象。

```
{
  "Records": [
    {
      "cf": {
        "config": {
          "distributionDomainName": "d111111abcdef8.cloudfront.net",
          "distributionId": "EDFDVBD6EXAMPLE",
          "eventType": "origin-request",
          "requestId": "4TyzHTaYWb1GX1qTfsHhEqV6HUDd_BzoBZnwfnvQc_1oF26ClkoUSEQ=="
        },
        "request": {
          "clientIp": "203.0.113.178",
          "headers": {
            "x-forwarded-for": [
              {
                "key": "X-Forwarded-For",
                "value": "203.0.113.178"
              }
            ],
            "user-agent": [
              {
                "key": "User-Agent",
                "value": "Amazon CloudFront"
              }
            ],
            "via": [
              {
                "key": "Via",
                "value": "2.0 2afae0d44e2540f472c0635ab62c232b.cloudfront.net (CloudFront)"
              }
            ],
            "host": [
              {
                "key": "Host",
                "value": "example.org"
              }
            ],
            "cache-control": [
              {
                "key": "Cache-Control",
                "value": "no-cache"
              }
            ]
          },
          "method": "GET",
          "origin": {
            "custom": {
              "customHeaders": {},
              "domainName": "example.org",
              "keepaliveTimeout": 5,
              "path": "",
              "port": 443,
              "protocol": "https",
              "readTimeout": 30,
              "responseCompletionTimeout": 30,
              "sslProtocols": [
                "TLSv1",
                "TLSv1.1",
                "TLSv1.2"
              ]
            }
          },
          "querystring": "",
          "uri": "/"
        }
      }
    }
  ]
}
```

### 请求事件字段
<a name="request-event-fields"></a>

请求事件对象数据包含在两个子对象中：`config` (`Records.cf.config`) 和 `request` (`Records.cf.request`)。下面的列表描述了各个子对象的字段。

#### Config 对象中的字段
<a name="request-event-fields-config"></a>

下面的列表介绍了 `config` 对象 (`Records.cf.config`) 中的字段。

**`distributionDomainName`（只读）**  
与请求关联的分配的域名。

**`distributionID`（只读）**  
与请求关联的分配的 ID。

**`eventType`（只读）**  
与请求关联的触发器类型：`viewer-request` 或 `origin-request`。

**`requestId`（只读）**  
一个加密字符串，唯一地标识查看器到 CloudFront 的请求。`requestId` 值还在 CloudFront 访问日志中显示为 `x-edge-request-id`。有关更多信息，请参阅 [访问日志（标准日志）](AccessLogs.md)和 [日志文件字段](standard-logs-reference.md#BasicDistributionFileFormat)：

#### 请求对象中的字段
<a name="request-event-fields-request"></a>

下面的列表介绍了 `request` 对象 (`Records.cf.request`) 中的字段。

**`clientIp`（只读）**  
发出请求的查看器的 IP 地址。如果查看器使用 HTTP 代理或负载均衡器发送请求，则值为该代理或负载均衡器的 IP 地址。

**标头（读/写）**  
请求中的标头。请注意以下几点：  
+ `headers` 对象中的键为标准 HTTP 请求标头名称的小写版本。使用小写键可为您提供对标头值的不区分大小写的访问权限。
+ 每个标头对象（例如，`headers["accept"]` 或 `headers["host"]`）是一个键/值对数组。对于一个指定标头，数组为请求中的每个值包含一个键/值对。
+ `key` 包含 HTTP 请求中显示的标头的名称，该名称区分大小写，例如 `Host`、`User-Agent`、`X-Forwarded-For`、`Cookie` 等等。
+ `value` 包含 HTTP 请求中显示的标头值。
+ 当您的 Lambda 函数添加或修改请求标头，并且您未包含标头 `key` 字段时，Lambda@Edge 会自动使用您提供的标头名称插入标头 `key`。无论您如何格式化标头名称，自动插入的标头键都将通过对每个部分使用首字母大写方式 [用连字符 (-) 分隔] 来格式化。

  例如，您可以不带标头键添加标头 `key`，如下所示：

  ```
  "user-agent": [
    {
      "value": "ExampleCustomUserAgent/1.X.0"
    }
  ]
  ```

  在本示例中，Lambda@Edge 会自动插入 `"key": "User-Agent"`。
有关标头使用情况限制的信息，请参阅[边缘函数的限制](edge-functions-restrictions.md)。

**`method`（只读）**  
请求中的 HTTP 方法。

**`querystring`（读/写）**  
请求中的查询字符串（如果有的话）。如果请求中不包括查询字符串，则事件对象仍包括带空值的 `querystring`。有关查询字符串的更多信息，请参阅[根据查询字符串参数缓存内容](QueryStringParameters.md)。

**`uri`（读/写）**  
所请求对象的相对路径。如果您的 Lambda 函数修改了 `uri` 值，请记住以下事项：  
+ 新的 `uri` 值必须以正斜杠 (/) 开头。
+ 如果某个函数更改 `uri` 值，则这样会更改查看器请求的对象。
+ 如果某个函数更改 `uri` 值，则这样*不会* 更改该请求或该请求发送到的源的缓存行为。

**`body`（读/写）**  
HTTP 请求的正文。`body` 结构可以包含以下字段：    
**`inputTruncated`（只读）**  
一个布尔值标记，它指示 Lambda@Edge 是否截断正文。有关更多信息，请参阅 [具有 Include Body（包含正文）选项的请求正文的限制](lambda-at-edge-function-restrictions.md#lambda-at-edge-restrictions-request-body)。  
**`action`（读/写）**  
您打算对正文执行的操作。`action` 选项如下所示：  
+ `read-only:` 这是默认值。在从 Lambda 函数返回响应时，如果 `action` 是只读的，Lambda@Edge 将忽略对 `encoding` 或 `data` 的任何更改。
+ `replace:` 如果要替换发送到源的正文，请指定该选项。  
**`encoding`（读/写）**  
正文的编码。在 Lambda@Edge 向 Lambda 函数公开正文时，它先将正文转换为 base64-encoding。如果您为 `replace` 选择 `action` 以替换正文，您可以选择使用 `base64`（默认编码）或 `text` 编码。如果将 `encoding` 指定为 `base64`，但正文不是有效的 base64，CloudFront 将返回错误。  
**`data`（读/写）**  
请求正文内容。

**`origin`（读/写）（仅限原始事件）**  
要将请求发送到的源。`origin` 结构*只能包含一个源*，该源可以是自定义源或 Amazon S3 源。  
根据指定的源类型（自定义或 Amazon S3 源），您必须在请求中指定以下字段：    
**`customHeaders`（读/写）（自定义和 Amazon S3 源）**  
（可选）您可以通过为每个自定义标头指定标头名称/值对，在请求中包括自定义标头。您不能添加不允许使用的标头，并且 `Records.cf.request.headers` 中不能存在同名标头。[有关请求标头的注释](#request-event-fields-request-headers)也适用于自定义标头。有关更多信息，请参阅 [CloudFront 无法添加到源请求的自定义标头](add-origin-custom-headers.md#add-origin-custom-headers-denylist)和 [边缘函数的限制](edge-functions-restrictions.md)：  
**`domainName`（读/写）（自定义和 Amazon S3 源）**  
源的域名。域名不能为空。  
+ **对于自定义源** – 指定 DNS 域名，例如 `www.example.com`。域名不能包含冒号 (:)，也不能为 IP 地址。域名最多可以有 253 个字符。
+ **对于 Amazon S3 源** – 指定 Amazon S3 存储桶的 DNS 域名，例如 `amzn-s3-demo-bucket.s3.eu-west-1.amazonaws.com`。名称必须最多为 128 个字符，并且必须为全小写。  
**`path`（读/写）（自定义和 Amazon S3 源）**  
源上的目录路径，请求应在其中查找内容。路径应该以正斜杠 (/) 开头，但不应该以正斜杠结尾（例如，它不应该以 `example-path/` 结尾）。仅对于自定义源，路径应为 URL 编码，最大长度为 255 个字符。  
**`keepaliveTimeout`（读/写）（仅自定义源）**  
CloudFront 在接收最后一个响应数据包后应尝试与源保持连接的秒数。该值必须是 1 到 120 之间（含）的数字。  
**`port`（读/写）（仅自定义源）**  
自定义源中 CloudFront 应连接到的端口。端口必须为 80、443，或者是 1024 到 65535 之间（含）的数字。  
**`protocol`（读/写）（仅自定义源）**  
连接到您的源时 CloudFront 应使用的连接协议。该值可以是 `http` 或 `https`。  
**`readTimeout`（读/写）（自定义和 Amazon S3 源）**  
向您的源发送请求后 CloudFront 应等待响应多长时间，以秒为单位。这还指定在 CloudFront 接收响应数据包之后应等待多长时间，然后再接收下一个数据包。该值必须是 1 到 120 之间（含）的数字。  
如果您需要更高的配额，请参阅[每个源的响应超时](cloudfront-limits.md#limits-web-distributions)。  
**`responseCompletionTimeout`（读/写）（自定义和 Amazon S3 源）**  
从 CloudFront 向源发出的请求可以保持打开状态并等待响应的时间（以秒为单位）。如果此时未收到来自源的完整响应，CloudFront 将终止连接。  
`responseCompletionTimeout` 的值必须大于或等于 `readTimeout` 的值。如果将此值设置为 0，则会移除您设置的任何之前的值并返回到默认值。也可以通过从事件请求中删除 `responseCompletionTimeout` 字段来实现此目的。  
**`sslProtocols`（读/写）（仅自定义源）**  
CloudFront 在建立与您的源的 HTTPS 连接时可使用的最小 SSL/TLS 协议。可以是以下值之一：`TLSv1.2`、`TLSv1.1`、`TLSv1` 或 `SSLv3`。  
**`authMethod`（读/写）（仅限 Amazon S3 源）**  
如果您使用[源访问身份 (OAI)](private-content-restricting-access-to-s3.md#private-content-restricting-access-to-s3-oai)，请将此字段设置为 `origin-access-identity`。如果您没有使用 OAI，请将其设置为 `none`。将 `authMethod` 设置为 `origin-access-identity` 时有以下几点要求：  
+ 您必须指定 `region`（请参阅以下字段）。
+ 将请求从一个 Amazon S3 源更改为另一个源时，必须使用相同的 OAI。
+ 将请求从自定义源更改为 Amazon S3 源时，不能使用 OAI。
此字段不支持[源访问控制（OAC）](private-content-restricting-access-to-s3.md)。  
**`region`（读/写）（仅限 Amazon S3 源）**  
您的 Amazon S3 存储桶的 AWS 区域。仅当您将 `authMethod` 设置为 `origin-access-identity` 时，此项才是必需的。

## 响应事件
<a name="lambda-event-structure-response"></a>

下面的主题显示了 CloudFront 传递给[查看器和源响应事件](lambda-cloudfront-trigger-events.md)的 Lambda 函数的对象结构。下面的示例是查看器和源响应事件中所有可能字段的列表。

**Topics**
+ [示例源响应](#lambda-event-structure-response-origin)
+ [示例查看器响应](#lambda-event-structure-response-viewer)
+ [响应事件字段](#response-event-fields)

### 示例源响应
<a name="lambda-event-structure-response-origin"></a>

下面的示例显示源响应事件对象。

```
{
  "Records": [
    {
      "cf": {
        "config": {
          "distributionDomainName": "d111111abcdef8.cloudfront.net",
          "distributionId": "EDFDVBD6EXAMPLE",
          "eventType": "origin-response",
          "requestId": "4TyzHTaYWb1GX1qTfsHhEqV6HUDd_BzoBZnwfnvQc_1oF26ClkoUSEQ=="
        },
        "request": {
          "clientIp": "203.0.113.178",
          "headers": {
            "x-forwarded-for": [
              {
                "key": "X-Forwarded-For",
                "value": "203.0.113.178"
              }
            ],
            "user-agent": [
              {
                "key": "User-Agent",
                "value": "Amazon CloudFront"
              }
            ],
            "via": [
              {
                "key": "Via",
                "value": "2.0 8f22423015641505b8c857a37450d6c0.cloudfront.net (CloudFront)"
              }
            ],
            "host": [
              {
                "key": "Host",
                "value": "example.org"
              }
            ],
            "cache-control": [
              {
                "key": "Cache-Control",
                "value": "no-cache"
              }
            ]
          },
          "method": "GET",
          "origin": {
            "custom": {
              "customHeaders": {},
              "domainName": "example.org",
              "keepaliveTimeout": 5,
              "path": "",
              "port": 443,
              "protocol": "https",
              "readTimeout": 30,
              "responseCompletionTimeout": 30,
              "sslProtocols": [
                "TLSv1",
                "TLSv1.1",
                "TLSv1.2"
              ]
            }
          },
          "querystring": "",
          "uri": "/"
        },
        "response": {
          "headers": {
            "access-control-allow-credentials": [
              {
                "key": "Access-Control-Allow-Credentials",
                "value": "true"
              }
            ],
            "access-control-allow-origin": [
              {
                "key": "Access-Control-Allow-Origin",
                "value": "*"
              }
            ],
            "date": [
              {
                "key": "Date",
                "value": "Mon, 13 Jan 2020 20:12:38 GMT"
              }
            ],
            "referrer-policy": [
              {
                "key": "Referrer-Policy",
                "value": "no-referrer-when-downgrade"
              }
            ],
            "server": [
              {
                "key": "Server",
                "value": "ExampleCustomOriginServer"
              }
            ],
            "x-content-type-options": [
              {
                "key": "X-Content-Type-Options",
                "value": "nosniff"
              }
            ],
            "x-frame-options": [
              {
                "key": "X-Frame-Options",
                "value": "DENY"
              }
            ],
            "x-xss-protection": [
              {
                "key": "X-XSS-Protection",
                "value": "1; mode=block"
              }
            ],
            "content-type": [
              {
                "key": "Content-Type",
                "value": "text/html; charset=utf-8"
              }
            ],
            "content-length": [
              {
                "key": "Content-Length",
                "value": "9593"
              }
            ]
          },
          "status": "200",
          "statusDescription": "OK"
        }
      }
    }
  ]
}
```

### 示例查看器响应
<a name="lambda-event-structure-response-viewer"></a>

下面的示例显示查看器响应事件对象。

```
{
  "Records": [
    {
      "cf": {
        "config": {
          "distributionDomainName": "d111111abcdef8.cloudfront.net",
          "distributionId": "EDFDVBD6EXAMPLE",
          "eventType": "viewer-response",
          "requestId": "4TyzHTaYWb1GX1qTfsHhEqV6HUDd_BzoBZnwfnvQc_1oF26ClkoUSEQ=="
        },
        "request": {
          "clientIp": "203.0.113.178",
          "headers": {
            "host": [
              {
                "key": "Host",
                "value": "d111111abcdef8.cloudfront.net"
              }
            ],
            "user-agent": [
              {
                "key": "User-Agent",
                "value": "curl/7.66.0"
              }
            ],
            "accept": [
              {
                "key": "accept",
                "value": "*/*"
              }
            ]
          },
          "method": "GET",
          "querystring": "",
          "uri": "/"
        },
        "response": {
          "headers": {
            "access-control-allow-credentials": [
              {
                "key": "Access-Control-Allow-Credentials",
                "value": "true"
              }
            ],
            "access-control-allow-origin": [
              {
                "key": "Access-Control-Allow-Origin",
                "value": "*"
              }
            ],
            "date": [
              {
                "key": "Date",
                "value": "Mon, 13 Jan 2020 20:14:56 GMT"
              }
            ],
            "referrer-policy": [
              {
                "key": "Referrer-Policy",
                "value": "no-referrer-when-downgrade"
              }
            ],
            "server": [
              {
                "key": "Server",
                "value": "ExampleCustomOriginServer"
              }
            ],
            "x-content-type-options": [
              {
                "key": "X-Content-Type-Options",
                "value": "nosniff"
              }
            ],
            "x-frame-options": [
              {
                "key": "X-Frame-Options",
                "value": "DENY"
              }
            ],
            "x-xss-protection": [
              {
                "key": "X-XSS-Protection",
                "value": "1; mode=block"
              }
            ],
            "age": [
              {
                "key": "Age",
                "value": "2402"
              }
            ],
            "content-type": [
              {
                "key": "Content-Type",
                "value": "text/html; charset=utf-8"
              }
            ],
            "content-length": [
              {
                "key": "Content-Length",
                "value": "9593"
              }
            ]
          },
          "status": "200",
          "statusDescription": "OK"
        }
      }
    }
  ]
}
```

### 响应事件字段
<a name="response-event-fields"></a>

响应事件对象数据包含在三个子对象中：`config` (`Records.cf.config`)、`request` (`Records.cf.request`) 和 `response` (`Records.cf.response`)。有关请求对象中的字段的更多信息，请参阅[请求对象中的字段](#request-event-fields-request)。下面的列表描述了 `config` 和 `response` 子对象中的字段。

#### Config 对象中的字段
<a name="response-event-fields-config"></a>

下面的列表介绍了 `config` 对象 (`Records.cf.config`) 中的字段。

**`distributionDomainName`（只读）**  
与响应关联的分配的域名。

**`distributionID`（只读）**  
与响应关联的分配的 ID。

**`eventType`（只读）**  
与响应关联的触发器的类型：`origin-response` 或 `viewer-response`。

**`requestId`（只读）**  
一个加密字符串，唯一地标识与此响应关联的查看器到 CloudFront 的请求。`requestId` 值还在 CloudFront 访问日志中显示为 `x-edge-request-id`。有关更多信息，请参阅 [访问日志（标准日志）](AccessLogs.md)和 [日志文件字段](standard-logs-reference.md#BasicDistributionFileFormat)：

#### 响应对象中的字段
<a name="response-event-fields-response"></a>

下面的列表介绍了 `response` 对象 (`Records.cf.response`) 中的字段。有关使用 Lambda@Edge 函数生成 HTTP 响应的信息，请参阅[在请求触发器中生成 HTTP 响应](lambda-generating-http-responses.md#lambda-generating-http-responses-in-requests)。

**`headers`（读/写）**  
响应中的标头。请注意以下几点：  
+ `headers` 对象中的键为标准 HTTP 请求标头名称的小写版本。使用小写键可为您提供对标头值的不区分大小写的访问权限。
+ 每个标头对象（例如，`headers["content-type"]` 或 `headers["content-length"]`）是一个键/值对数组。对于一个指定标头，数组为响应中的每个值包含一个键/值对。
+ `key` 包含 HTTP 响应中显示的标头的名称，名称区分大小写；例如 `Content-Type`、`Content-Length`、`Cookie` 等等。
+ `value` 包含 HTTP 响应中显示的标头值。
+ 当您的 Lambda 函数添加或修改响应标头，并且您不包含标头 `key` 字段时，Lambda@Edge 会自动使用您提供的标头名称插入标头 `key`。无论您如何格式化标头名称，自动插入的标头键都将通过对每个部分使用首字母大写方式 [用连字符 (-) 分隔] 来格式化。

  例如，您可以不带标头键添加标头 `key`，如下所示：

  ```
  "content-type": [
    {
      "value": "text/html;charset=UTF-8"
    }
  ]
  ```

  在本示例中，Lambda@Edge 会自动插入 `"key": "Content-Type"`。
有关标头使用情况限制的信息，请参阅[边缘函数的限制](edge-functions-restrictions.md)。

**`status`**  
响应的 HTTP 状态代码。

**`statusDescription`**  
响应的 HTTP 状态描述。

# 使用请求和响应
<a name="lambda-generating-http-responses"></a>

要使用 Lambda @Edge 请求和响应，请参阅以下主题：

**Topics**
+ [将 Lambda@Edge 函数与源故障转移结合使用](#lambda-and-origin-failover)
+ [在请求触发器中生成 HTTP 响应](#lambda-generating-http-responses-in-requests)
+ [更新源响应触发器中的 HTTP 响应](#lambda-updating-http-responses)
+ [通过选择“包含正文”选项来访问请求正文](#lambda-include-body-access)

## 将 Lambda@Edge 函数与源故障转移结合使用
<a name="lambda-and-origin-failover"></a>

您可以将 Lambda@Edge 函数与您设置有源组的 CloudFront 分配结合使用，例如，对于您配置为帮助确保高可用性的源故障转移。要将 Lambda 函数与源组结合使用，请在创建缓存行为时，在源组的源请求或源响应触发器中指定此函数。

有关更多信息，请参阅下列内容：
+ **创建源组：**[创建源组](high_availability_origin_failover.md#concept_origin_groups.creating)
+ **源故障转移如何与 Lambda@Edge 结合使用：**[将源故障转移与 Lambda@Edge 函数结合使用](high_availability_origin_failover.md#concept_origin_groups.lambda)

## 在请求触发器中生成 HTTP 响应
<a name="lambda-generating-http-responses-in-requests"></a>

CloudFront 收到请求时，您可以使用 Lambda 函数生成 CloudFront 直接返回到查看器的 HTTP 响应，无需将响应转发到源。生成 HTTP 响应会减少源上的负载，通常也可以减少查看器的延迟。

生成 HTTP 响应的一些常见情况包括：
+ 将小网页返回到查看器
+ 返回 HTTP 301 或 302 状态代码，以便将用户重定向到其他网页
+ 用户未通过身份验证时向查看器返回 HTTP 401 状态代码

出现以下 CloudFront 事件时，Lambda@Edge 函数可以生成 HTTP 响应：

**查看器请求事件**  
当查看器请求事件触发了函数时，CloudFront 将响应返回到查看器并且不进行缓存。

**源请求事件**  
当源请求事件触发函数时，CloudFront 检查边缘缓存中以前由函数生成的响应。  
+ 如果响应在缓存中，函数不执行，CloudFront 将缓存的响应返回查看器。
+ 如果响应不在缓存中，则执行函数，CloudFront 将响应返回查看器，并且缓存它。

要查看生成 HTTP 响应的一些代码示例，请参阅 [Lambda@Edge 函数示例](lambda-examples.md)。您也可以替换响应触发器中的 HTTP 响应。有关更多信息，请参阅 [更新源响应触发器中的 HTTP 响应](#lambda-updating-http-responses)。

### 编程模型
<a name="lambda-generating-http-responses-programming-model"></a>

本节介绍了使用 Lambda@Edge 生成 HTTP 响应的编程模型的信息。

**Topics**
+ [响应对象](#lambda-generating-http-responses-object)
+ [错误](#lambda-generating-http-responses-errors)
+ [必填字段](#lambda-generating-http-responses-required-fields)

#### 响应对象
<a name="lambda-generating-http-responses-object"></a>

作为 `result` 方法的 `callback` 参数返回的响应应具有以下结构（请注意，只有 `status` 字段为必填字段）。

```
const response = {
    body: 'content',
    bodyEncoding: 'text' | 'base64',
    headers: {
        'header name in lowercase': [{
            key: 'header name in standard case',
            value: 'header value'
         }],
         ...
    },
    status: 'HTTP status code (string)',
    statusDescription: 'status description'
};
```

响应对象可包括以下值：

**`body`**  
您希望 CloudFront 在生成的响应中返回的正文（如果有）。

**`bodyEncoding`**  
您在 `body` 中指定的值的编码。有效编码仅为 `text` 和 `base64`。如果您在 `body` 对象中包含 `response` 但省略 `bodyEncoding`，CloudFront 将正文视为文本。  
如果将 `bodyEncoding` 指定为 `base64`，但正文不是有效的 base64，CloudFront 将返回错误。

**`headers`**  
您希望 CloudFront 在生成的响应中返回的标头。请注意以下几点：  
+ `headers` 对象中的键为标准 HTTP 请求标头名称的小写版本。使用小写键可为您提供对标头值的不区分大小写的访问权限。
+ 每个标头（例如，`headers["accept"]` 或 `headers["host"]`）是一个键值对数组。对于一个指定标头，数组为生成的响应中的每个值包含一个键值对。
+ `key`（可选）是显示在 HTTP 请求中的标头名称，区分大小写；例如 `accept` 或 `host`。
+ 指定 `value` 作为标头值。
+ 如果您不包括键值对的标头键部分，Lambda@Edge 会使用您提供的标头名称自动插入标头键。无论您如何格式化标头名称，所插入的标头键都会自动通过对每个部分使用首字母大写方式（用连字符 (-) 分隔）来格式化。

  例如，您可以不带标头键添加标头，如下所示：`'content-type': [{ value: 'text/html;charset=UTF-8' }]`

  在本示例中，Lambda@Edge 创建以下标头键：`Content-Type`。
有关标头使用情况限制的信息，请参阅[边缘函数的限制](edge-functions-restrictions.md)。

**`status`**  
HTTP 状态代码。以字符串形式提供状态代码。CloudFront 将提供的状态代码用于以下各项：  
+ 在响应中返回
+ 当从由源请求事件触发的函数生成响应时，缓存在 CloudFront 边缘缓存中
+ 登录 CloudFront [访问日志（标准日志）](AccessLogs.md)
如果 `status` 值并非介于 200 到 599 之间，CloudFront 向查看器返回错误。

**`statusDescription`**  
您希望 CloudFront 在响应中随 HTTP 状态代码一起返回的说明。无需使用标准说明，例如为 HTTP 状态代码 200 使用 `OK`。

#### 错误
<a name="lambda-generating-http-responses-errors"></a>

以下是所生成的 HTTP 响应可能的错误。

**响应包含正文并为状态指定了 204（无内容**  
当函数由查看器请求触发时，如果满足以下情况，则 CloudFront 向查看器返回 HTTP 502 状态代码（无效网关）：  
+ `status` 的值为 204（无内容）
+ 响应包括 `body` 的值
这是因为 Lambda@Edge 会施加 RFC 2616 中介绍的可选限制，其中指出 `HTTP 204` 响应不需要包含消息正文。

**对所生成响应的大小的限制**  
由 Lambda 函数生成的响应的最大大小取决于触发该函数的事件：  
+ **查看器请求事件** – 40 KB
+ **源请求事件** – 1 MB
如果响应大于允许的大小，则 CloudFront 会向查看器返回 HTTP 502 状态代码（无效网关）。

#### 必填字段
<a name="lambda-generating-http-responses-required-fields"></a>

`status` 字段为必填项。

其他所有字段均为可选字段。

## 更新源响应触发器中的 HTTP 响应
<a name="lambda-updating-http-responses"></a>

当 CloudFront 从源服务器接收 HTTP 响应时，如果有源响应触发器与缓存行为关联，您可以修改 HTTP 响应以覆盖从源返回的内容。

更新 HTTP 响应的一些常见情况包括：
+ 更改状态以设置 HTTP 200 状态代码并创建静态正文内容，这些内容在源服务器返回错误代码 (4xx 或 5xx) 时返回到查看器。有关代码示例，请参阅 [示例：使用源响应触发器将错误状态代码更新为 200](lambda-examples.md#lambda-examples-custom-error-static-body)。
+ 更改状态以设置 HTTP 301 或 HTTP 302 状态代码，用于在源返回错误状态代码 (4xx 或 5xx) 时将用户重定向到其他网站。有关代码示例，请参阅 [示例：使用源响应触发器将错误状态代码更新为 302](lambda-examples.md#lambda-examples-custom-error-new-site)。

**注意**  
函数必须返回值介于 `200` 到 `599` 之间（包括这两者）的状态，否则 CloudFront 向查看器返回错误。

您也可以替换查看器和源请求事件中的 HTTP 响应。有关更多信息，请参阅 [在请求触发器中生成 HTTP 响应](#lambda-generating-http-responses-in-requests)。

当您在处理 HTTP 响应时，Lambda@Edge 不会将源服务器返回的正文公开到源响应触发器。您可以通过将其设置为所需值来生成静态内容正文，或者通过将值设置为空删除函数中的正文。如果您不更新函数中的正文字段，源服务器返回的源正文将返回到查看器。

## 通过选择“包含正文”选项来访问请求正文
<a name="lambda-include-body-access"></a>

您可以选择让 Lambda@Edge 为可写的 HTTP 方法（POST、PUT、DELETE 等）公开请求中的正文，以便您可以在 Lambda 函数中访问正文。您可以选择只读访问，也可以指定将替换正文。

要启用该选项，请在为函数创建 CloudFront 触发器以用于查看器请求或源请求事件时选择**包含正文**。有关更多信息，请参阅[为 Lambda@Edge 函数添加触发器](lambda-edge-add-triggers.md)；要了解如何在函数中使用**包含正文**，请参阅[Lambda@Edge 事件结构](lambda-event-structure.md)。

您可能希望使用此功能的情况包括：
+ 处理 Web 表单（例如“联系我们”表单），而不将客户输入数据发送回源服务器。
+ 收集查看器浏览器发送的 Web 信标数据并在边缘站点中进行处理。

有关代码示例，请参阅 [Lambda@Edge 函数示例](lambda-examples.md)。

**注意**  
如果请求正文很大，Lambda@Edge 将会截断正文。有关最大大小和截断的详细信息，请参阅[具有 Include Body（包含正文）选项的请求正文的限制](lambda-at-edge-function-restrictions.md#lambda-at-edge-restrictions-request-body)。

# Lambda@Edge 函数示例
<a name="lambda-examples"></a>

请参阅以下示例，了解如何将 Lambda 函数与 Amazon CloudFront 结合使用。

**注意**  
如果您为 Lambda@Edge 函数选择运行时 Node.js 18 或更高版本，则会自动为您创建一个 `index.mjs` 文件。要使用以下代码示例，请改为将 `index.mjs` 文件重命名为 `index.js`。

**Topics**
+ [一般示例](#lambda-examples-general-examples)
+ [生成响应 - 示例](#lambda-examples-generated-response-examples)
+ [查询字符串 - 示例](#lambda-examples-query-string-examples)
+ [按国家/地区或设备类型标头个性化内容 - 示例](#lambda-examples-redirecting-examples)
+ [基于内容的动态源选择 - 示例](#lambda-examples-content-based-routing-examples)
+ [更新错误状态 - 示例](#lambda-examples-update-error-status-examples)
+ [访问请求正文 - 示例](#lambda-examples-access-request-body-examples)

## 一般示例
<a name="lambda-examples-general-examples"></a>

以下示例展示了在 CloudFront 中使用 Lambda@Edge 的常见方法。

**Topics**
+ [示例：A/B 测试](#lambda-examples-a-b-testing)
+ [示例：覆盖响应标头](#lambda-examples-overriding-response-header)

### 示例：A/B 测试
<a name="lambda-examples-a-b-testing"></a>

您可以使用以下示例测试图像的两个不同版本，不需要创建重定向或更改 URL。本示例读取查看器请求中的 Cookie，并相应地修改请求 URL。如果查看器未发送包含预期值之一的 Cookie，则示例会将查看器随机分配给其中一个 URL。

------
#### [ Node.js ]

```
'use strict';

exports.handler = (event, context, callback) => {
    const request = event.Records[0].cf.request;
    const headers = request.headers;

    if (request.uri !== '/experiment-pixel.jpg') {
        // do not process if this is not an A-B test request
        callback(null, request);
        return;
    }

    const cookieExperimentA = 'X-Experiment-Name=A';
    const cookieExperimentB = 'X-Experiment-Name=B';
    const pathExperimentA = '/experiment-group/control-pixel.jpg';
    const pathExperimentB = '/experiment-group/treatment-pixel.jpg';

    /*
     * Lambda at the Edge headers are array objects.
     *
     * Client may send multiple Cookie headers, i.e.:
     * > GET /viewerRes/test HTTP/1.1
     * > User-Agent: curl/7.18.1 (x86_64-unknown-linux-gnu) libcurl/7.18.1 OpenSSL/1.0.1u zlib/1.2.3
     * > Cookie: First=1; Second=2
     * > Cookie: ClientCode=abc
     * > Host: example.com
     *
     * You can access the first Cookie header at headers["cookie"][0].value
     * and the second at headers["cookie"][1].value.
     *
     * Header values are not parsed. In the example above,
     * headers["cookie"][0].value is equal to "First=1; Second=2"
     */
    let experimentUri;
    if (headers.cookie) {
        for (let i = 0; i < headers.cookie.length; i++) {
            if (headers.cookie[i].value.indexOf(cookieExperimentA) >= 0) {
                console.log('Experiment A cookie found');
                experimentUri = pathExperimentA;
                break;
            } else if (headers.cookie[i].value.indexOf(cookieExperimentB) >= 0) {
                console.log('Experiment B cookie found');
                experimentUri = pathExperimentB;
                break;
            }
        }
    }

    if (!experimentUri) {
        console.log('Experiment cookie has not been found. Throwing dice...');
        if (Math.random() < 0.75) {
            experimentUri = pathExperimentA;
        } else {
            experimentUri = pathExperimentB;
        }
    }

    request.uri = experimentUri;
    console.log(`Request uri set to "${request.uri}"`);
    callback(null, request);
};
```

------
#### [ Python ]

```
import json
import random

def lambda_handler(event, context):
    request = event['Records'][0]['cf']['request']
    headers = request['headers']

    if request['uri'] != '/experiment-pixel.jpg':
        # Not an A/B Test
        return request

    cookieExperimentA, cookieExperimentB = 'X-Experiment-Name=A', 'X-Experiment-Name=B'
    pathExperimentA, pathExperimentB = '/experiment-group/control-pixel.jpg', '/experiment-group/treatment-pixel.jpg'

    '''
    Lambda at the Edge headers are array objects.

    Client may send multiple cookie headers. For example:
    > GET /viewerRes/test HTTP/1.1
    > User-Agent: curl/7.18.1 (x86_64-unknown-linux-gnu) libcurl/7.18.1 OpenSSL/1.0.1u zlib/1.2.3
    > Cookie: First=1; Second=2
    > Cookie: ClientCode=abc
    > Host: example.com

    You can access the first Cookie header at headers["cookie"][0].value
    and the second at headers["cookie"][1].value.

    Header values are not parsed. In the example above,
    headers["cookie"][0].value is equal to "First=1; Second=2"
    '''

    experimentUri = ""

    for cookie in headers.get('cookie', []):
        if cookieExperimentA in cookie['value']:
            print("Experiment A cookie found")
            experimentUri = pathExperimentA
            break
        elif cookieExperimentB in cookie['value']:
            print("Experiment B cookie found")
            experimentUri = pathExperimentB
            break

    if not experimentUri:
        print("Experiment cookie has not been found. Throwing dice...")
        if random.random() < 0.75:
            experimentUri = pathExperimentA
        else:
            experimentUri = pathExperimentB

    request['uri'] = experimentUri
    print(f"Request uri set to {experimentUri}")
    return request
```

------

### 示例：覆盖响应标头
<a name="lambda-examples-overriding-response-header"></a>

以下示例演示了如何基于其他标头的值来更改响应标头的值。

------
#### [ Node.js ]

```
export const handler = async (event) => {
    const response = event.Records[0].cf.response;
    const headers = response.headers;

    const headerNameSrc = 'X-Amz-Meta-Last-Modified';
    const headerNameDst = 'Last-Modified';

    if (headers[headerNameSrc.toLowerCase()]) {
        headers[headerNameDst.toLowerCase()] = [{
            key: headerNameDst,
            value: headers[headerNameSrc.toLowerCase()][0].value,
        }];
        console.log(`Response header "${headerNameDst}" was set to ` +
                    `"${headers[headerNameDst.toLowerCase()][0].value}"`);
    }

    return response;
};
```

------
#### [ Python ]

```
import json 

def lambda_handler(event, context):
    response = event['Records'][0]['cf']['response']
    headers = response['headers']
    
    header_name_src = 'X-Amz-Meta-Last-Modified'
    header_name_dst = 'Last-Modified'
    
    if headers.get(header_name_src.lower()):
        headers[header_name_dst.lower()] = [{
            'key': header_name_dst,
            'value': headers[header_name_src.lower()][0]['value']
        }]
        print(f'Response header "{header_name_dst}" was set to '
              f'"{headers[header_name_dst.lower()][0]["value"]}"')
    
    return response
```

------

## 生成响应 - 示例
<a name="lambda-examples-generated-response-examples"></a>

以下示例展示了如何使用 Lambda@Edge 生成响应。

**Topics**
+ [示例：提供静态内容（生成的响应）](#lambda-examples-static-web-server)
+ [示例：生成 HTTP 重定向（生成的响应）](#lambda-examples-http-redirect)

### 示例：提供静态内容（生成的响应）
<a name="lambda-examples-static-web-server"></a>

以下示例演示了如何使用 Lambda 函数来提供静态网站内容，这样可减少源服务器上的负载，并减少总体延迟。

**注意**  
您可以针对查看器请求和源请求事件生成 HTTP 响应。有关更多信息，请参阅 [在请求触发器中生成 HTTP 响应](lambda-generating-http-responses.md#lambda-generating-http-responses-in-requests)。  
您也可以替换或删除源响应事件中 HTTP 响应的正文。有关更多信息，请参阅 [更新源响应触发器中的 HTTP 响应](lambda-generating-http-responses.md#lambda-updating-http-responses)。

------
#### [ Node.js ]

```
'use strict';

const content = `
<\!DOCTYPE html>
<html lang="en">
  <head>
    <meta charset="utf-8">
    <title>Simple Lambda@Edge Static Content Response</title>
  </head>
  <body>
    <p>Hello from Lambda@Edge!</p>
  </body>
</html>
`;

exports.handler = (event, context, callback) => {
    /*
     * Generate HTTP OK response using 200 status code with HTML body.
     */
    const response = {
        status: '200',
        statusDescription: 'OK',
        headers: {
            'cache-control': [{
                key: 'Cache-Control',
                value: 'max-age=100'
            }],
            'content-type': [{
                key: 'Content-Type',
                value: 'text/html'
            }]
        },
        body: content,
    };
    callback(null, response);
};
```

------
#### [ Python ]

```
import json

CONTENT = """
<\!DOCTYPE html>
<html lang="en">
<head>
    <meta charset="utf-8">
    <title>Simple Lambda@Edge Static Content Response</title>
</head>
<body>
    <p>Hello from Lambda@Edge!</p>
</body>
</html>
"""

def lambda_handler(event, context):
    # Generate HTTP OK response using 200 status code with HTML body.
    response = {
        'status': '200',
        'statusDescription': 'OK',
        'headers': {
            'cache-control': [
                {
                    'key': 'Cache-Control',
                    'value': 'max-age=100'
                }
            ],
            "content-type": [
                {
                    'key': 'Content-Type',
                    'value': 'text/html'
                }
            ]
        },
        'body': CONTENT
    }
    return response
```

------

### 示例：生成 HTTP 重定向（生成的响应）
<a name="lambda-examples-http-redirect"></a>

以下示例演示了如何生成 HTTP 重定向。

**注意**  
您可以针对查看器请求和源请求事件生成 HTTP 响应。有关更多信息，请参阅 [在请求触发器中生成 HTTP 响应](lambda-generating-http-responses.md#lambda-generating-http-responses-in-requests)。

------
#### [ Node.js ]

```
'use strict';

exports.handler = (event, context, callback) => {
    /*
     * Generate HTTP redirect response with 302 status code and Location header.
     */
    const response = {
        status: '302',
        statusDescription: 'Found',
        headers: {
            location: [{
                key: 'Location',
                value: 'https://docs.aws.amazon.com/lambda/latest/dg/lambda-edge.html',
            }],
        },
    };
    callback(null, response);
};
```

------
#### [ Python ]

```
def lambda_handler(event, context):

    # Generate HTTP redirect response with 302 status code and Location header.

    response = {
        'status': '302',
        'statusDescription': 'Found',
        'headers': {
            'location': [{
                'key': 'Location',
                'value': 'https://docs.aws.amazon.com/lambda/latest/dg/lambda-edge.html'
            }]
        }
    }

    return response
```

------

## 查询字符串 - 示例
<a name="lambda-examples-query-string-examples"></a>

以下示例展示了将 Lambda@Edge 与查询字符串结合使用的方法。

**Topics**
+ [示例：根据查询字符串参数添加标头](#lambda-examples-header-based-on-query-string)
+ [示例：标准化查询字符串参数以提高缓存命中率](#lambda-examples-normalize-query-string-parameters)
+ [示例：将未经身份验证的用户重定向到登录页面](#lambda-examples-redirect-to-signin-page)

### 示例：根据查询字符串参数添加标头
<a name="lambda-examples-header-based-on-query-string"></a>

下面的示例演示如何获取查询字符串参数的键-值对，然后根据这些值添加标头。

------
#### [ Node.js ]

```
'use strict';

const querystring = require('querystring');
exports.handler = (event, context, callback) => {
    const request = event.Records[0].cf.request;
    
    /* When a request contains a query string key-value pair but the origin server
     * expects the value in a header, you can use this Lambda function to
     * convert the key-value pair to a header. Here's what the function does:
     * 1. Parses the query string and gets the key-value pair.
     * 2. Adds a header to the request using the key-value pair that the function got in step 1.
     */

    /* Parse request querystring to get javascript object */
    const params = querystring.parse(request.querystring);

    /* Move auth param from querystring to headers */
    const headerName = 'Auth-Header';
    request.headers[headerName.toLowerCase()] = [{ key: headerName, value: params.auth }];
    delete params.auth;

    /* Update request querystring */
    request.querystring = querystring.stringify(params);

    callback(null, request);
};
```

------
#### [ Python ]

```
from urllib.parse import parse_qs, urlencode

def lambda_handler(event, context):
    request = event['Records'][0]['cf']['request']

    '''
    When a request contains a query string key-value pair but the origin server
    expects the value in a header, you can use this Lambda function to
    convert the key-value pair to a header. Here's what the function does:
        1. Parses the query string and gets the key-value pair.
        2. Adds a header to the request using the key-value pair that the function got in step 1.
    '''

    # Parse request querystring to get dictionary/json
    params = {k : v[0] for k, v in parse_qs(request['querystring']).items()}

    # Move auth param from querystring to headers
    headerName = 'Auth-Header'
    request['headers'][headerName.lower()] = [{'key': headerName, 'value': params['auth']}]
    del params['auth']

    # Update request querystring
    request['querystring'] = urlencode(params)

    return request
```

------

### 示例：标准化查询字符串参数以提高缓存命中率
<a name="lambda-examples-normalize-query-string-parameters"></a>

下面的示例演示如何在 CloudFront 将请求转发给您的源之前通过对查询字符串进行以下更改来提高缓存命中率：
+ 按参数名称的字母顺序排列键/值对
+ 将键/值对的大小写更改为小写

有关更多信息，请参阅 [根据查询字符串参数缓存内容](QueryStringParameters.md)。

------
#### [ Node.js ]

```
'use strict';

const querystring = require('querystring');

exports.handler = (event, context, callback) => {
    const request = event.Records[0].cf.request;
    /* When you configure a distribution to forward query strings to the origin and
     * to cache based on an allowlist of query string parameters, we recommend
     * the following to improve the cache-hit ratio:
     * - Always list parameters in the same order.
     * - Use the same case for parameter names and values.
     *
     * This function normalizes query strings so that parameter names and values
     * are lowercase and parameter names are in alphabetical order.
     *
     * For more information, see:
     * https://docs.aws.amazon.com/AmazonCloudFront/latest/DeveloperGuide/QueryStringParameters.html
     */

    console.log('Query String: ', request.querystring);

    /* Parse request query string to get javascript object */
    const params = querystring.parse(request.querystring.toLowerCase());
    const sortedParams = {};

    /* Sort param keys */
    Object.keys(params).sort().forEach(key => {
        sortedParams[key] = params[key];
    });

    /* Update request querystring with normalized  */
    request.querystring = querystring.stringify(sortedParams);

    callback(null, request);
};
```

------
#### [ Python ]

```
from urllib.parse import parse_qs, urlencode

def lambda_handler(event, context):
    request = event['Records'][0]['cf']['request']
    '''
    When you configure a distribution to forward query strings to the origin and
    to cache based on an allowlist of query string parameters, we recommend
    the following to improve the cache-hit ratio:
    Always list parameters in the same order.
    - Use the same case for parameter names and values.

    This function normalizes query strings so that parameter names and values
    are lowercase and parameter names are in alphabetical order.

    For more information, see:
    https://docs.aws.amazon.com/AmazonCloudFront/latest/DeveloperGuide/QueryStringParameters.html
    '''
    print("Query string: ", request["querystring"])

    # Parse request query string to get js object
    params = {k : v[0] for k, v in parse_qs(request['querystring'].lower()).items()}

    # Sort param keys
    sortedParams = sorted(params.items(), key=lambda x: x[0])

    # Update request querystring with normalized
    request['querystring'] = urlencode(sortedParams)
    
    return request
```

------

### 示例：将未经身份验证的用户重定向到登录页面
<a name="lambda-examples-redirect-to-signin-page"></a>

下面的示例演示如何将未输入其凭证的用户重定向到登录页面。

------
#### [ Node.js ]

```
'use strict';

function parseCookies(headers) {
    const parsedCookie = {};
    if (headers.cookie) {
        headers.cookie[0].value.split(';').forEach((cookie) => {
            if (cookie) {
                const parts = cookie.split('=');
                parsedCookie[parts[0].trim()] = parts[1].trim();
            }
        });
    }
    return parsedCookie;
}

exports.handler = (event, context, callback) => {
    const request = event.Records[0].cf.request;
    const headers = request.headers;

    /* Check for session-id in request cookie in viewer-request event,
     * if session-id is absent, redirect the user to sign in page with original
     * request sent as redirect_url in query params.
     */

    /* Check for session-id in cookie, if present then proceed with request */
    const parsedCookies = parseCookies(headers);
    if (parsedCookies && parsedCookies['session-id']) {
        callback(null, request);
        return;
    }

    /* URI encode the original request to be sent as redirect_url in query params */
    const encodedRedirectUrl = encodeURIComponent(`https://${headers.host[0].value}${request.uri}?${request.querystring}`);
    const response = {
        status: '302',
        statusDescription: 'Found',
        headers: {
            location: [{
                key: 'Location',
                value: `https://www.example.com/signin?redirect_url=${encodedRedirectUrl}`,
            }],
        },
    };
    callback(null, response);
};
```

------
#### [ Python ]

```
import urllib

def parseCookies(headers):
    parsedCookie = {}
    if headers.get('cookie'):
        for cookie in headers['cookie'][0]['value'].split(';'):
            if cookie:
                parts = cookie.split('=')
                parsedCookie[parts[0].strip()] = parts[1].strip()
    return parsedCookie

def lambda_handler(event, context):
    request = event['Records'][0]['cf']['request']
    headers = request['headers']

    '''
    Check for session-id in request cookie in viewer-request event,
    if session-id is absent, redirect the user to sign in page with original
    request sent as redirect_url in query params.
    '''

    # Check for session-id in cookie, if present, then proceed with request
    parsedCookies = parseCookies(headers)

    if parsedCookies and parsedCookies['session-id']:
        return request

    # URI encode the original request to be sent as redirect_url in query params
    redirectUrl = "https://%s%s?%s" % (headers['host'][0]['value'], request['uri'], request['querystring'])
    encodedRedirectUrl = urllib.parse.quote_plus(redirectUrl.encode('utf-8'))

    response = {
        'status': '302',
        'statusDescription': 'Found',
        'headers': {
            'location': [{
                'key': 'Location',
                'value': 'https://www.example.com/signin?redirect_url=%s' % encodedRedirectUrl
            }]
        }
    }
    return response
```

------

## 按国家/地区或设备类型标头个性化内容 - 示例
<a name="lambda-examples-redirecting-examples"></a>

以下示例展示了如何使用 Lambda@Edge，基于位置或查看器使用的设备类型来自定义行为。

**Topics**
+ [示例：将查看器请求重定向到国家/地区特定的 URL](#lambda-examples-redirect-based-on-country)
+ [示例：根据设备提供不同版本的对象](#lambda-examples-vary-on-device-type)

### 示例：将查看器请求重定向到国家/地区特定的 URL
<a name="lambda-examples-redirect-based-on-country"></a>

下面的示例演示如何生成包含国家/地区特定的 URL 的 HTTP 重定向响应并将该响应返回到查看器。在您希望提供国家/地区特定的响应时，这非常有用。例如：
+ 如果您有国家/地区特定的子域，例如 us.example.com 和 tw.example.com，则在查看器请求 example.com 时，您可以生成重定向响应。
+ 如果您要流式传输视频，但您在特定国家/地区中无权流式传输内容，则可以将该国家/地区中的用户重定向到说明他们为何无法观看视频的页面。

请注意以下几点：
+ 您必须将您的分配配置为基于 `CloudFront-Viewer-Country` 标头进行缓存。有关更多信息，请参阅 [基于选择的请求标头进行缓存](DownloadDistValuesCacheBehavior.md#DownloadDistValuesForwardHeaders)。
+ CloudFront 在查看器请求事件之后添加 `CloudFront-Viewer-Country` 标头。要使用此示例，您必须为源请求事件创建触发器。

------
#### [ Node.js ]

```
'use strict';

/* This is an origin request function */
exports.handler = (event, context, callback) => {
    const request = event.Records[0].cf.request;
    const headers = request.headers;

    /*
     * Based on the value of the CloudFront-Viewer-Country header, generate an
     * HTTP status code 302 (Redirect) response, and return a country-specific
     * URL in the Location header.
     * NOTE: 1. You must configure your distribution to cache based on the
     *          CloudFront-Viewer-Country header. For more information, see
     *          https://docs.aws.amazon.com/console/cloudfront/cache-on-selected-headers
     *       2. CloudFront adds the CloudFront-Viewer-Country header after the viewer
     *          request event. To use this example, you must create a trigger for the
     *          origin request event.
     */

    let url = 'https://example.com/';
    if (headers['cloudfront-viewer-country']) {
        const countryCode = headers['cloudfront-viewer-country'][0].value;
        if (countryCode === 'TW') {
            url = 'https://tw.example.com/';
        } else if (countryCode === 'US') {
            url = 'https://us.example.com/';
        }
    }

    const response = {
        status: '302',
        statusDescription: 'Found',
        headers: {
            location: [{
                key: 'Location',
                value: url,
            }],
        },
    };
    callback(null, response);
};
```

------
#### [ Python ]

```
# This is an origin request function

def lambda_handler(event, context):
    request = event['Records'][0]['cf']['request']
    headers = request['headers']

    '''
    Based on the value of the CloudFront-Viewer-Country header, generate an
    HTTP status code 302 (Redirect) response, and return a country-specific
    URL in the Location header.
    NOTE: 1. You must configure your distribution to cache based on the
            CloudFront-Viewer-Country header. For more information, see
            https://docs.aws.amazon.com/console/cloudfront/cache-on-selected-headers
          2. CloudFront adds the CloudFront-Viewer-Country header after the viewer
            request event. To use this example, you must create a trigger for the
            origin request event.
    '''

    url = 'https://example.com/'
    viewerCountry = headers.get('cloudfront-viewer-country')
    if viewerCountry:
        countryCode = viewerCountry[0]['value']
        if countryCode == 'TW':
            url = 'https://tw.example.com/'
        elif countryCode == 'US':
            url = 'https://us.example.com/'

    response = {
        'status': '302',
        'statusDescription': 'Found',
        'headers': {
            'location': [{
                'key': 'Location',
                'value': url
            }]
        }
    }

    return response
```

------

### 示例：根据设备提供不同版本的对象
<a name="lambda-examples-vary-on-device-type"></a>

下面的示例演示如何根据用户使用的设备的类型 (例如，移动设备或平板电脑) 提供不同版本的对象。请注意以下几点：
+ 您必须将您的分配配置为基于 `CloudFront-Is-*-Viewer` 标头进行缓存。有关更多信息，请参阅 [基于选择的请求标头进行缓存](DownloadDistValuesCacheBehavior.md#DownloadDistValuesForwardHeaders)。
+ CloudFront 在查看器请求事件之后添加 `CloudFront-Is-*-Viewer` 标头。要使用此示例，您必须为源请求事件创建触发器。

------
#### [ Node.js ]

```
'use strict';

/* This is an origin request function */
exports.handler = (event, context, callback) => {
    const request = event.Records[0].cf.request;
    const headers = request.headers;

    /*
     * Serve different versions of an object based on the device type.
     * NOTE: 1. You must configure your distribution to cache based on the
     *          CloudFront-Is-*-Viewer headers. For more information, see
     *          the following documentation:
     *          https://docs.aws.amazon.com/console/cloudfront/cache-on-selected-headers
     *          https://docs.aws.amazon.com/console/cloudfront/cache-on-device-type
     *       2. CloudFront adds the CloudFront-Is-*-Viewer headers after the viewer
     *          request event. To use this example, you must create a trigger for the
     *          origin request event.
     */

    const desktopPath = '/desktop';
    const mobilePath = '/mobile';
    const tabletPath = '/tablet';
    const smarttvPath = '/smarttv';

    if (headers['cloudfront-is-desktop-viewer']
        && headers['cloudfront-is-desktop-viewer'][0].value === 'true') {
        request.uri = desktopPath + request.uri;
    } else if (headers['cloudfront-is-mobile-viewer']
               && headers['cloudfront-is-mobile-viewer'][0].value === 'true') {
        request.uri = mobilePath + request.uri;
    } else if (headers['cloudfront-is-tablet-viewer']
               && headers['cloudfront-is-tablet-viewer'][0].value === 'true') {
        request.uri = tabletPath + request.uri;
    } else if (headers['cloudfront-is-smarttv-viewer']
               && headers['cloudfront-is-smarttv-viewer'][0].value === 'true') {
        request.uri = smarttvPath + request.uri;
    }
    console.log(`Request uri set to "${request.uri}"`);

    callback(null, request);
};
```

------
#### [ Python ]

```
# This is an origin request function
def lambda_handler(event, context):
    request = event['Records'][0]['cf']['request']
    headers = request['headers']

    '''
    Serve different versions of an object based on the device type.
    NOTE: 1. You must configure your distribution to cache based on the
            CloudFront-Is-*-Viewer headers. For more information, see
            the following documentation:
            https://docs.aws.amazon.com/console/cloudfront/cache-on-selected-headers
            https://docs.aws.amazon.com/console/cloudfront/cache-on-device-type
          2. CloudFront adds the CloudFront-Is-*-Viewer headers after the viewer
            request event. To use this example, you must create a trigger for the
            origin request event.
    '''

    desktopPath = '/desktop';
    mobilePath = '/mobile';
    tabletPath = '/tablet';
    smarttvPath = '/smarttv';

    if 'cloudfront-is-desktop-viewer' in headers and headers['cloudfront-is-desktop-viewer'][0]['value'] == 'true':
        request['uri'] = desktopPath + request['uri']
    elif 'cloudfront-is-mobile-viewer' in headers and headers['cloudfront-is-mobile-viewer'][0]['value'] == 'true':
        request['uri'] = mobilePath + request['uri']
    elif 'cloudfront-is-tablet-viewer' in headers and headers['cloudfront-is-tablet-viewer'][0]['value'] == 'true':
        request['uri'] = tabletPath + request['uri']
    elif 'cloudfront-is-smarttv-viewer' in headers and headers['cloudfront-is-smarttv-viewer'][0]['value'] == 'true':
        request['uri'] = smarttvPath + request['uri']

    print("Request uri set to %s" % request['uri'])

    return request
```

------

## 基于内容的动态源选择 - 示例
<a name="lambda-examples-content-based-routing-examples"></a>

以下示例展示了如何使用 Lambda@Edge，基于请求中的信息路由到不同的源。

**Topics**
+ [示例：使用源请求触发器从自定义源更改为 Amazon S3 源](#lambda-examples-content-based-S3-origin-based-on-query)
+ [示例：使用源请求触发器更改 Amazon S3 源区域](#lambda-examples-content-based-S3-origin-request-trigger)
+ [示例：使用源请求触发器从 Amazon S3 源更改为自定义源](#lambda-examples-content-based-custom-origin-request-trigger)
+ [示例：使用源请求触发器将流量从一个 Amazon S3 存储桶逐步转移到另一个存储桶](#lambda-examples-content-based-gradual-traffic-transfer)
+ [示例：使用源请求触发器根据国家/地区标头更改源域名](#lambda-examples-content-based-geo-header)

### 示例：使用源请求触发器从自定义源更改为 Amazon S3 源
<a name="lambda-examples-content-based-S3-origin-based-on-query"></a>

此函数演示如何根据请求属性，使用源请求触发器将从中提取内容的自定义源更改为 Amazon S3 源。

------
#### [ Node.js ]

```
'use strict';

 const querystring = require('querystring');
 
 exports.handler = (event, context, callback) => {
     const request = event.Records[0].cf.request;
 
     /**
      * Reads query string to check if S3 origin should be used, and
      * if true, sets S3 origin properties.
      */
 
     const params = querystring.parse(request.querystring);
 
     if (params['useS3Origin']) {
         if (params['useS3Origin'] === 'true') {
             const s3DomainName = 'amzn-s3-demo-bucket.s3.amazonaws.com';
 
             /* Set S3 origin fields */
             request.origin = {
                 s3: {
                     domainName: s3DomainName,
                     region: '',
                     authMethod: 'origin-access-identity',
                     path: '',
                     customHeaders: {}
                 }
             };
             request.headers['host'] = [{ key: 'host', value: s3DomainName}];
         }
     }
     
    callback(null, request);
};
```

------
#### [ Python ]

```
from urllib.parse import parse_qs

def lambda_handler(event, context):
    request = event['Records'][0]['cf']['request']
    '''
    Reads query string to check if S3 origin should be used, and
    if true, sets S3 origin properties
    '''
    params = {k: v[0] for k, v in parse_qs(request['querystring']).items()}
    if params.get('useS3Origin') == 'true':
        s3DomainName = 'amzn-s3-demo-bucket.s3.amazonaws.com'

        # Set S3 origin fields
        request['origin'] = {
            's3': {
                'domainName': s3DomainName,
                'region': '',
                'authMethod': 'origin-access-identity',
                'path': '',
                'customHeaders': {}
            }
        }
        request['headers']['host'] = [{'key': 'host', 'value': s3DomainName}]
    return request
```

------

### 示例：使用源请求触发器更改 Amazon S3 源区域
<a name="lambda-examples-content-based-S3-origin-request-trigger"></a>

此函数演示如何根据请求属性，使用源请求触发器更改从中提取内容的 Amazon S3 源。

在此例中，我们使用 `CloudFront-Viewer-Country` 标头的值将 S3 存储桶域名更新为更接近查看器的区域中的存储桶。这在多种情况下非常有用：
+ 当指定的区域接近查看器所在的国家/地区时，这可以减少延迟。
+ 通过确保由与发起请求所在位置的相同国家/地区的源提供数据，实现数据主权。

要使用本示例，您必须执行以下操作：
+ 将您的分配配置为基于 `CloudFront-Viewer-Country` 标头进行缓存。有关更多信息，请参阅 [基于选择的请求标头进行缓存](DownloadDistValuesCacheBehavior.md#DownloadDistValuesForwardHeaders)。
+ 在源请求事件中为此函数创建一个触发器。CloudFront 在查看器请求事件后添加了 `CloudFront-Viewer-Country` 标头，因此，要使用此示例，您必须确保函数对源请求执行。

**注意**  
以下示例代码对您用于源的所有 S3 存储桶使用相同的来源访问身份（OAI）。有关更多信息，请参阅[来源访问身份](private-content-restricting-access-to-s3.md#private-content-restricting-access-to-s3-oai)。

------
#### [ Node.js ]

```
'use strict';

exports.handler = (event, context, callback) => {
    const request = event.Records[0].cf.request;

    /**
     * This blueprint demonstrates how an origin-request trigger can be used to
     * change the origin from which the content is fetched, based on request properties.
     * In this example, we use the value of the CloudFront-Viewer-Country header
     * to update the S3 bucket domain name to a bucket in a Region that is closer to
     * the viewer.
     * 
     * This can be useful in several ways:
     *      1) Reduces latencies when the Region specified is nearer to the viewer's
     *         country.
     *      2) Provides data sovereignty by making sure that data is served from an
     *         origin that's in the same country that the request came from.
     * 
     * NOTE: 1. You must configure your distribution to cache based on the
     *          CloudFront-Viewer-Country header. For more information, see
     *          https://docs.aws.amazon.com/console/cloudfront/cache-on-selected-headers
     *       2. CloudFront adds the CloudFront-Viewer-Country header after the viewer
     *          request event. To use this example, you must create a trigger for the
     *          origin request event.
     */

    const countryToRegion = {
        'DE': 'eu-central-1',
        'IE': 'eu-west-1',
        'GB': 'eu-west-2',
        'FR': 'eu-west-3',
        'JP': 'ap-northeast-1',
        'IN': 'ap-south-1'
    };

    if (request.headers['cloudfront-viewer-country']) {
        const countryCode = request.headers['cloudfront-viewer-country'][0].value;
        const region = countryToRegion[countryCode];
        
        /**
         * If the viewer's country is not in the list you specify, the request
         * goes to the default S3 bucket you've configured.
         */  
        if (region) {
            /**
             * If you've set up OAI, the bucket policy in the destination bucket
             * should allow the OAI GetObject operation, as configured by default
             * for an S3 origin with OAI. Another requirement with OAI is to provide
             * the Region so it can be used for the SIGV4 signature. Otherwise, the
             * Region is not required.
             */
            request.origin.s3.region = region;
            const domainName = `amzn-s3-demo-bucket-in-${region}.s3.${region}.amazonaws.com`;
            request.origin.s3.domainName = domainName;
            request.headers['host'] = [{ key: 'host', value: domainName }];
        }
    }

    callback(null, request);
};
```

------
#### [ Python ]

```
def lambda_handler(event, context):
    request = event['Records'][0]['cf']['request']

    '''
    This blueprint demonstrates how an origin-request trigger can be used to
    change the origin from which the content is fetched, based on request properties.
    In this example, we use the value of the CloudFront-Viewer-Country header
    to update the S3 bucket domain name to a bucket in a Region that is closer to
    the viewer.
    
    This can be useful in several ways:
        1) Reduces latencies when the Region specified is nearer to the viewer's
            country.
        2) Provides data sovereignty by making sure that data is served from an
            origin that's in the same country that the request came from.
    
    NOTE: 1. You must configure your distribution to cache based on the
            CloudFront-Viewer-Country header. For more information, see
            https://docs.aws.amazon.com/console/cloudfront/cache-on-selected-headers
          2. CloudFront adds the CloudFront-Viewer-Country header after the viewer
            request event. To use this example, you must create a trigger for the
            origin request event.
    '''

    countryToRegion = {
        'DE': 'eu-central-1',
        'IE': 'eu-west-1',
        'GB': 'eu-west-2',
        'FR': 'eu-west-3',
        'JP': 'ap-northeast-1',
        'IN': 'ap-south-1'
    }

    viewerCountry = request['headers'].get('cloudfront-viewer-country')
    if viewerCountry:
        countryCode = viewerCountry[0]['value']
        region = countryToRegion.get(countryCode)

        # If the viewer's country in not in the list you specify, the request
        # goes to the default S3 bucket you've configured
        if region:
            '''
            If you've set up OAI, the bucket policy in the destination bucket
            should allow the OAI GetObject operation, as configured by default
            for an S3 origin with OAI. Another requirement with OAI is to provide
            the Region so it can be used for the SIGV4 signature. Otherwise, the
            Region is not required.
            '''
            request['origin']['s3']['region'] = region
            domainName = 'amzn-s3-demo-bucket-in-{0}.s3.{0}.amazonaws.com'.format(region)
            request['origin']['s3']['domainName'] = domainName
            request['headers']['host'] = [{'key': 'host', 'value': domainName}]

    return request
```

------

### 示例：使用源请求触发器从 Amazon S3 源更改为自定义源
<a name="lambda-examples-content-based-custom-origin-request-trigger"></a>

该函数演示如何根据请求属性，使用源请求触发器更改从中提取内容的自定义源。

------
#### [ Node.js ]

```
'use strict';

const querystring = require('querystring');
 
 exports.handler = (event, context, callback) => {
     const request = event.Records[0].cf.request;
 
     /**
      * Reads query string to check if custom origin should be used, and
      * if true, sets custom origin properties.
      */
 
     const params = querystring.parse(request.querystring);
 
     if (params['useCustomOrigin']) {
         if (params['useCustomOrigin'] === 'true') {
 
             /* Set custom origin fields*/
             request.origin = {
                 custom: {
                     domainName: 'www.example.com',
                     port: 443,
                     protocol: 'https',
                     path: '',
                     sslProtocols: ['TLSv1', 'TLSv1.1'],
                     readTimeout: 5,
                     keepaliveTimeout: 5,
                     customHeaders: {}
                 }
             };
             request.headers['host'] = [{ key: 'host', value: 'www.example.com'}];
         }
     }
    callback(null, request);
};
```

------
#### [ Python ]

```
from urllib.parse import parse_qs

def lambda_handler(event, context):
    request = event['Records'][0]['cf']['request']

    # Reads query string to check if custom origin should be used, and
    # if true, sets custom origin properties

    params = {k: v[0] for k, v in parse_qs(request['querystring']).items()}

    if params.get('useCustomOrigin') == 'true':
            # Set custom origin fields
            request['origin'] = {
                'custom': {
                    'domainName': 'www.example.com',
                    'port': 443,
                    'protocol': 'https',
                    'path': '',
                    'sslProtocols': ['TLSv1', 'TLSv1.1'],
                    'readTimeout': 5,
                    'keepaliveTimeout': 5,
                    'customHeaders': {}
                }
            }
            request['headers']['host'] = [{'key': 'host', 'value': 'www.example.com'}]

    return request
```

------

### 示例：使用源请求触发器将流量从一个 Amazon S3 存储桶逐步转移到另一个存储桶
<a name="lambda-examples-content-based-gradual-traffic-transfer"></a>

此函数演示如何以可控的方式将流量从一个 Amazon S3 存储桶逐步转移到另一个存储桶。

------
#### [ Node.js ]

```
'use strict';

    function getRandomInt(min, max) {
        /* Random number is inclusive of min and max*/
        return Math.floor(Math.random() * (max - min + 1)) + min;
 }

exports.handler = (event, context, callback) => {
    const request = event.Records[0].cf.request;
    const BLUE_TRAFFIC_PERCENTAGE = 80;

    /**
      * This Lambda function demonstrates how to gradually transfer traffic from
      * one S3 bucket to another in a controlled way.
      * We define a variable BLUE_TRAFFIC_PERCENTAGE which can take values from
      * 1 to 100. If the generated randomNumber less than or equal to BLUE_TRAFFIC_PERCENTAGE, traffic
      * is re-directed to blue-bucket. If not, the default bucket that we've configured
      * is used.
      */

    const randomNumber = getRandomInt(1, 100);

if (randomNumber <= BLUE_TRAFFIC_PERCENTAGE) {
         const domainName = 'blue-bucket.s3.amazonaws.com';
         request.origin.s3.domainName = domainName;
         request.headers['host'] = [{ key: 'host', value: domainName}];
     }
    callback(null, request);
};
```

------
#### [ Python ]

```
import math
import random

def getRandomInt(min, max):
    # Random number is inclusive of min and max
    return math.floor(random.random() * (max - min + 1)) + min

def lambda_handler(event, context):
    request = event['Records'][0]['cf']['request']
    BLUE_TRAFFIC_PERCENTAGE = 80

    '''
    This Lambda function demonstrates how to gradually transfer traffic from
    one S3 bucket to another in a controlled way.
    We define a variable BLUE_TRAFFIC_PERCENTAGE which can take values from
    1 to 100. If the generated randomNumber less than or equal to BLUE_TRAFFIC_PERCENTAGE, traffic
    is re-directed to blue-bucket. If not, the default bucket that we've configured
    is used.
    '''

    randomNumber = getRandomInt(1, 100)

    if randomNumber <= BLUE_TRAFFIC_PERCENTAGE:
        domainName = 'blue-bucket.s3.amazonaws.com'
        request['origin']['s3']['domainName'] = domainName
        request['headers']['host'] = [{'key': 'host', 'value': domainName}]

    return request
```

------

### 示例：使用源请求触发器根据国家/地区标头更改源域名
<a name="lambda-examples-content-based-geo-header"></a>

此函数演示了如何根据 `CloudFront-Viewer-Country` 标头更改源域名，这样可以从接近查看器所在的国家/地区的源提供内容。

为您的分配实施此功能可能有类似于下面的好处：
+ 在指定的区域接近查看器所在的国家/地区时减少延迟。
+ 确保由请求发起位置所在的同一国家/地区内的源提供数据，从而实现数据主权。

请注意，要启用此功能，您必须配置分配以根据 `CloudFront-Viewer-Country` 标头进行缓存。有关更多信息，请参阅 [基于选择的请求标头进行缓存](DownloadDistValuesCacheBehavior.md#DownloadDistValuesForwardHeaders)。

------
#### [ Node.js ]

```
'use strict';

exports.handler = (event, context, callback) => {
     const request = event.Records[0].cf.request;
     
  if (request.headers['cloudfront-viewer-country']) {
         const countryCode = request.headers['cloudfront-viewer-country'][0].value;
         if (countryCode === 'GB' || countryCode === 'DE' || countryCode === 'IE' ) {
             const domainName = 'eu.example.com';
             request.origin.custom.domainName = domainName;
             request.headers['host'] = [{key: 'host', value: domainName}];
         } 
     }
     
    callback(null, request);
};
```

------
#### [ Python ]

```
def lambda_handler(event, context):
    request = event['Records'][0]['cf']['request']

    viewerCountry = request['headers'].get('cloudfront-viewer-country')
    if viewerCountry:
        countryCode = viewerCountry[0]['value']
        if countryCode == 'GB' or countryCode == 'DE' or countryCode == 'IE':
            domainName = 'eu.example.com'
            request['origin']['custom']['domainName'] = domainName
            request['headers']['host'] = [{'key': 'host', 'value': domainName}]
    return request
```

------

## 更新错误状态 - 示例
<a name="lambda-examples-update-error-status-examples"></a>

以下示例指导您使用 Lambda@Edge 来更改返回给用户的错误状态。

**Topics**
+ [示例：使用源响应触发器将错误状态代码更新为 200](#lambda-examples-custom-error-static-body)
+ [示例：使用源响应触发器将错误状态代码更新为 302](#lambda-examples-custom-error-new-site)

### 示例：使用源响应触发器将错误状态代码更新为 200
<a name="lambda-examples-custom-error-static-body"></a>

该函数演示了在下列情况中，如何将响应状态更新为 200 并生成静态正文内容以返回到查看器：
+ 函数在源响应中触发。
+ 来自源服务器的响应状态是错误状态代码（4xx 或 5xx）。

------
#### [ Node.js ]

```
'use strict';

exports.handler = (event, context, callback) => {
    const response = event.Records[0].cf.response;

    /**
     * This function updates the response status to 200 and generates static
     * body content to return to the viewer in the following scenario:
     * 1. The function is triggered in an origin response
     * 2. The response status from the origin server is an error status code (4xx or 5xx)
     */

    if (response.status >= 400 && response.status <= 599) {
        response.status = 200;
        response.statusDescription = 'OK';
        response.body = 'Body generation example';
    }

    callback(null, response);
};
```

------
#### [ Python ]

```
def lambda_handler(event, context):
    response = event['Records'][0]['cf']['response']

    '''
    This function updates the response status to 200 and generates static
    body content to return to the viewer in the following scenario:
    1. The function is triggered in an origin response
    2. The response status from the origin server is an error status code (4xx or 5xx)
    '''

    if int(response['status']) >= 400 and int(response['status']) <= 599:
        response['status'] = 200
        response['statusDescription'] = 'OK'
        response['body'] = 'Body generation example'
    return response
```

------

### 示例：使用源响应触发器将错误状态代码更新为 302
<a name="lambda-examples-custom-error-new-site"></a>

该函数演示了如何将 HTTP 状态代码更新为 302，以重定向到配置了不同源的其他路径（缓存行为）。请注意以下几点：
+ 函数在源响应中触发。
+ 来自源服务器的响应状态是错误状态代码（4xx 或 5xx）。

------
#### [ Node.js ]

```
'use strict';

exports.handler = (event, context, callback) => {
    const response = event.Records[0].cf.response;
    const request = event.Records[0].cf.request;

    /**
     * This function updates the HTTP status code in the response to 302, to redirect to another
     * path (cache behavior) that has a different origin configured. Note the following:
     * 1. The function is triggered in an origin response
     * 2. The response status from the origin server is an error status code (4xx or 5xx)
     */

    if (response.status >= 400 && response.status <= 599) {
        const redirect_path = `/plan-b/path?${request.querystring}`;

        response.status = 302;
        response.statusDescription = 'Found';

        /* Drop the body, as it is not required for redirects */
        response.body = '';
        response.headers['location'] = [{ key: 'Location', value: redirect_path }];
    }

    callback(null, response);
};
```

------
#### [ Python ]

```
def lambda_handler(event, context):
    response = event['Records'][0]['cf']['response']
    request = event['Records'][0]['cf']['request']

    '''
    This function updates the HTTP status code in the response to 302, to redirect to another
    path (cache behavior) that has a different origin configured. Note the following:
    1. The function is triggered in an origin response
    2. The response status from the origin server is an error status code (4xx or 5xx)
    '''

    if int(response['status']) >= 400 and int(response['status']) <= 599:
        redirect_path = '/plan-b/path?%s' % request['querystring']

        response['status'] = 302
        response['statusDescription'] = 'Found'

        # Drop the body as it is not required for redirects
        response['body'] = ''
        response['headers']['location'] = [{'key': 'Location', 'value': redirect_path}]

    return response
```

------

## 访问请求正文 - 示例
<a name="lambda-examples-access-request-body-examples"></a>

以下示例展示了如何使用 Lambda@Edge 处理 POST 请求。

**注意**  
要使用这些示例，必须在分配的 Lambda 函数关联中启用 *include body*（包含正文）选项。默认情况下，将不会启用此选项。  
要在 CloudFront 控制台中启用此设置，请选中 **Lambda 函数关联**中的**包含正文**复选框。
要在 CloudFront API 中或使用 CloudFormation 启用此设置，请在 `LambdaFunctionAssociation` 中将 `IncludeBody` 字段设置为 `true`。

**Topics**
+ [示例：使用请求触发器读取 HTML 表单](#lambda-examples-access-request-body-examples-read)
+ [示例：使用请求触发器修改 HTML 表单](#lambda-examples-access-request-body-examples-replace)

### 示例：使用请求触发器读取 HTML 表单
<a name="lambda-examples-access-request-body-examples-read"></a>

该函数说明了如何处理 HTML 表单（Web 表单）生成的 POST 请求的正文，例如“联系我们”表单。例如，您可能具有如下所示的 HTML 表单：

```
<html>
  <form action="https://example.com" method="post">
    Param 1: <input type="text" name="name1"><br>
    Param 2: <input type="text" name="name2"><br>
    input type="submit" value="Submit">
  </form>
</html>
```

对于后面的函数示例，必须在 CloudFront 查看器请求或源请求中触发该函数。

------
#### [ Node.js ]

```
'use strict';

const querystring = require('querystring');

/**
 * This function demonstrates how you can read the body of a POST request 
 * generated by an HTML form (web form). The function is triggered in a
 * CloudFront viewer request or origin request event type.
 */

exports.handler = (event, context, callback) => {
    const request = event.Records[0].cf.request;

    if (request.method === 'POST') {
        /* HTTP body is always passed as base64-encoded string. Decode it. */
        const body = Buffer.from(request.body.data, 'base64').toString();
 
        /* HTML forms send the data in query string format. Parse it. */
        const params = querystring.parse(body);
 
        /* For demonstration purposes, we only log the form fields here.
         * You can put your custom logic here. For example, you can store the 
         * fields in a database, such as Amazon DynamoDB, and generate a response
         * right from your Lambda@Edge function.
         */
        for (let param in params) {
            console.log(`For "${param}" user submitted "${params[param]}".\n`);
        }
    }
    return callback(null, request);
};
```

------
#### [ Python ]

```
import base64
from urllib.parse import parse_qs

'''
Say there is a POST request body generated by an HTML such as:

<html>
<form action="https://example.com" method="post">
    Param 1: <input type="text" name="name1"><br>
    Param 2: <input type="text" name="name2"><br>
    input type="submit" value="Submit">
</form>
</html>

'''

'''
This function demonstrates how you can read the body of a POST request 
generated by an HTML form (web form). The function is triggered in a
CloudFront viewer request or origin request event type.
'''

def lambda_handler(event, context):
    request = event['Records'][0]['cf']['request']

    if request['method'] == 'POST':
        # HTTP body is always passed as base64-encoded string. Decode it
        body = base64.b64decode(request['body']['data'])

        # HTML forms send the data in query string format. Parse it
        params = {k: v[0] for k, v in parse_qs(body).items()}

        '''
        For demonstration purposes, we only log the form fields here.
        You can put your custom logic here. For example, you can store the
        fields in a database, such as Amazon DynamoDB, and generate a response
        right from your Lambda@Edge function.
        '''
        for key, value in params.items():
            print("For %s use submitted %s" % (key, value))
            
    return request
```

------

### 示例：使用请求触发器修改 HTML 表单
<a name="lambda-examples-access-request-body-examples-replace"></a>

该函数说明了如何修改 HTML 表单（Web 表单）生成的 POST 请求的正文。在 CloudFront 查看器请求或源请求中触发该函数。

------
#### [ Node.js ]

```
'use strict';
				
const querystring = require('querystring');

exports.handler = (event, context, callback) => {
    var request = event.Records[0].cf.request;
    if (request.method === 'POST') {
        /* Request body is being replaced. To do this, update the following
        /* three fields:
         *    1) body.action to 'replace'
         *    2) body.encoding to the encoding of the new data.
         *
         *       Set to one of the following values:
         *
         *           text - denotes that the generated body is in text format.
         *               Lambda@Edge will propagate this as is.
         *           base64 - denotes that the generated body is base64 encoded.
         *               Lambda@Edge will base64 decode the data before sending
         *               it to the origin.
         *    3) body.data to the new body.
         */
        request.body.action = 'replace';
        request.body.encoding = 'text';
        request.body.data = getUpdatedBody(request);
    }
    callback(null, request);
};

function getUpdatedBody(request) {
    /* HTTP body is always passed as base64-encoded string. Decode it. */
    const body = Buffer.from(request.body.data, 'base64').toString();

    /* HTML forms send data in query string format. Parse it. */
    const params = querystring.parse(body);

    /* For demonstration purposes, we're adding one more param.
     *
     * You can put your custom logic here. For example, you can truncate long
     * bodies from malicious requests.
     */
    params['new-param-name'] = 'new-param-value';
    return querystring.stringify(params);
}
```

------
#### [ Python ]

```
import base64
from urllib.parse import parse_qs, urlencode

def lambda_handler(event, context):
    request = event['Records'][0]['cf']['request']
    if request['method'] == 'POST':
        '''
        Request body is being replaced. To do this, update the following
        three fields:
            1) body.action to 'replace'
            2) body.encoding to the encoding of the new data.
        
            Set to one of the following values:
        
                text - denotes that the generated body is in text format.
                    Lambda@Edge will propagate this as is.
                base64 - denotes that the generated body is base64 encoded.
                    Lambda@Edge will base64 decode the data before sending
                    it to the origin.
            3) body.data to the new body.
        '''
        request['body']['action'] = 'replace'
        request['body']['encoding'] = 'text'
        request['body']['data'] = getUpdatedBody(request)
    return request

def getUpdatedBody(request):
    # HTTP body is always passed as base64-encoded string. Decode it
    body = base64.b64decode(request['body']['data'])

    # HTML forms send data in query string format. Parse it
    params = {k: v[0] for k, v in parse_qs(body).items()}

    # For demonstration purposes, we're adding one more param

    # You can put your custom logic here. For example, you can truncate long
    # bodies from malicious requests
    params['new-param-name'] = 'new-param-value'
    return urlencode(params)
```

------

# 边缘函数的限制
<a name="edge-functions-restrictions"></a>

以下主题描述了适用于 CloudFront Functions 和 Lambda@Edge 的限制。一些限制适用于所有边缘函数，而另一些限制仅适用于 CloudFront Functions 或 Lambda@Edge。

每个主题都提供了有关您在使用 CloudFront 开发和部署边缘函数时应考虑的限制和约束的详细信息。

了解这些限制有助于确保边缘函数按预期运行并符合支持的功能。

**Topics**
+ [所有边缘函数的限制](edge-function-restrictions-all.md)
+ [对 CloudFront Functions 的限制](cloudfront-function-restrictions.md)
+ [对 Lambda@Edge 的限制](lambda-at-edge-function-restrictions.md)

有关配额（以前被称为限制）的更多信息，请参阅 [CloudFront Functions 的配额](cloudfront-limits.md#limits-functions) 和 [有关 Lambda@Edge 的配额](cloudfront-limits.md#limits-lambda-at-edge)。

# 所有边缘函数的限制
<a name="edge-function-restrictions-all"></a>

以下限制适用于所有边缘函数，包括 CloudFront Functions 和 Lambda@Edge。

**Topics**
+ [AWS 账户所有权](#function-restrictions-account-ownership)
+ [组合 CloudFront Functions 与 Lambda@Edge](#function-restrictions-combining-functions)
+ [HTTP 状态代码](#function-restrictions-status-codes)
+ [HTTP 标头](#function-restrictions-headers)
+ [查询字符串](#function-restrictions-query-strings)
+ [URI](#function-restrictions-uri)
+ [URI、查询字符串和标头编码](#function-restrictions-encoding)
+ [Microsoft Smooth Streaming](#function-restrictions-microsoft-smooth-streaming)
+ [标签](#function-restrictions-tagging)

## AWS 账户所有权
<a name="function-restrictions-account-ownership"></a>

要将边缘函数与 CloudFront 分配相关联，该函数和分配必须属于相同的 AWS 账户。

## 组合 CloudFront Functions 与 Lambda@Edge
<a name="function-restrictions-combining-functions"></a>

对于给定缓存行为，将适用以下限制将适用：
+ 每个事件类型（查看器请求、源请求、源响应和查看器响应）只能有一个边缘函数关联。
+ 您不能在查看器事件（查看器请求和查看器响应）中组合 CloudFront Functions 和 Lambda@Edge。

允许使用边缘函数的所有其他组合。下表介绍了允许的组合。

[\[See the AWS documentation website for more details\]](http://docs.aws.amazon.com/zh_cn/AmazonCloudFront/latest/DeveloperGuide/edge-function-restrictions-all.html)

## HTTP 状态代码
<a name="function-restrictions-status-codes"></a>

如果源返回 400 或更高的 HTTP 状态代码，则 CloudFront 不会对查看器响应事件调用边缘函数。

用于源响应事件的 Lambda@Edge 函数被调用于*全部*源响应，包括源返回 400 或更高的 HTTP 状态代码。有关更多信息，请参阅 [更新源响应触发器中的 HTTP 响应](lambda-generating-http-responses.md#lambda-updating-http-responses)。

## HTTP 标头
<a name="function-restrictions-headers"></a>

不允许使用某些 HTTP 标头，这意味着这些标头不会向边缘函数公开，并且函数无法添加它们。其他标头是只读的，这意味着函数可以读取它们，但不能添加、修改或删除它们。

**Topics**
+ [不允许使用的标头](#function-restrictions-disallowed-headers)
+ [只读标头](#function-restrictions-read-only-headers)

### 不允许使用的标头
<a name="function-restrictions-disallowed-headers"></a>

以下 HTTP 标头不会向边缘函数公开，并且函数无法添加它们。如果您的函数添加这些标头的其中之一，则请求将无法通过 CloudFront 验证，并且 CloudFront 会将 HTTP 状态代码 502（无效网关）返回给查看器。
+ `Connection` 
+ `Expect`
+ `Keep-Alive`
+ `Proxy-Authenticate`
+ `Proxy-Authorization`
+ `Proxy-Connection`
+ `Trailer`
+ `Upgrade`
+ `X-Accel-Buffering`
+ `X-Accel-Charset`
+ `X-Accel-Limit-Rate`
+ `X-Accel-Redirect`
+ `X-Amz-Cf-*`
+ `X-Amzn-Auth`
+ `X-Amzn-Cf-Billing`
+ `X-Amzn-Cf-Id`
+ `X-Amzn-Cf-Xff`
+ `X-Amzn-Errortype`
+ `X-Amzn-Fle-Profile`
+ `X-Amzn-Header-Count`
+ `X-Amzn-Header-Order`
+ `X-Amzn-Lambda-Integration-Tag`
+ `X-Amzn-RequestId`
+ `X-Cache`
+ `X-Edge-*`
+ `X-Forwarded-Proto`
+ `X-Real-IP`

### 只读标头
<a name="function-restrictions-read-only-headers"></a>

以下标头是只读的。您的函数可以读取这些标头，并将其用作函数逻辑输入，但无法更改这些值。如果您的函数添加或编辑一个只读标头，请求将无法通过 CloudFront 验证，并且 CloudFront 将 HTTP 状态代码 502（无效网关）返回到查看器。

#### 查看器请求事件中的只读标头
<a name="function-restrictions-read-only-headers-viewer-request"></a>

以下标头在查看器请求事件中为只读标头。
+ `Content-Length`
+ `Host`
+ `Transfer-Encoding`
+ `Via`

#### 源请求事件中的只读标头（仅限 Lambda@Edge）
<a name="function-restrictions-read-only-headers-origin-request"></a>

以下标头在源请求事件中是只读的，它们仅存在于 Lambda@Edge 中。
+ `Accept-Encoding`
+ `Content-Length`
+ `If-Modified-Since`
+ `If-None-Match`
+ `If-Range`
+ `If-Unmodified-Since`
+ `Transfer-Encoding`
+ `Via`

#### 源响应事件中的只读标头（仅限 Lambda@Edge）
<a name="function-restrictions-read-only-headers-origin-response"></a>

以下标头在源响应事件中是只读的，它们仅存在于 Lambda@Edge 中。
+ `Transfer-Encoding`
+ `Via`

#### 查看器响应事件中的只读标头
<a name="function-restrictions-read-only-headers-viewer-response"></a>

以下标头在查看器响应事件中为只读标头（对于 CloudFront Functions 和 Lambda@Edge）
+ `Warning`
+ `Via`

以下标头在查看器响应事件中为只读标头（对于 Lambda@Edge）。
+ `Content-Length`
+ `Content-Encoding`
+ `Transfer-Encoding`

## 查询字符串
<a name="function-restrictions-query-strings"></a>

以下限制适用于在请求 URI 中读取、更新或创建查询字符串的函数。
+ （仅限 Lambda@Edge）要访问源请求或源响应函数中的查询字符串，您的缓存策略或源请求策略必须针对**查询字符串**设置为 **All**。
+ 函数可以为查看器请求和源请求事件创建或更新查询字符串（源请求事件仅在 Lambda@Edge 中存在）。
+ 函数可以为源响应和查看器响应事件读取查询字符串，但不能创建或更新查询字符串（源响应事件仅在 Lambda@Edge 中存在）。
+ 如果函数创建或更新查询字符串，则具有以下限制：
  + 查询字符串不能包含空格、控制字符或片段标识符 (`#`)。
  + URI（包括查询字符串）的总大小必须小于 8192 个字符。
  + 建议您对 URI 和查询字符串使用百分号编码。有关更多信息，请参阅 [URI、查询字符串和标头编码](#function-restrictions-encoding)。

## URI
<a name="function-restrictions-uri"></a>

如果某个函数更改请求的 URI，则这样不会更改该请求或该请求转发到的源的缓存行为。

URI（包括查询字符串）的总大小必须小于 8192 个字符。

## URI、查询字符串和标头编码
<a name="function-restrictions-encoding"></a>

传递给边缘函数的 URI、查询字符串和标头的值是使用 UTF-8 编码的。函数应该对其返回的 URI、查询字符串和标头值使用 UTF-8 编码。百分号编码与 UTF-8 编码兼容。

下面的列表解释了 CloudFront 如何处理 URI、查询字符串和标头的编码：
+ 如果请求中的值是 UTF-8 编码，则 CloudFront 会将值转发给您的函数，而不会更改它们。
+ 如果请求中的值为 [ISO-8859-1 编码](https://en.wikipedia.org/wiki/ISO/IEC_8859-1)，CloudFront 会将值转换为 UTF-8 编码，然后再将值转发给您的函数。
+ 如果请求中的值使用任何其他字符编码方式进行编码，则 CloudFront 会假定它们是 ISO-8859-1 编码，并尝试从 ISO-8859-1 编码转换为 UTF-8 编码。
**重要**  
转换后字符可能是原始请求中的值的不准确解释。这可能会导致您的函数或源生成意外结果。

CloudFront 转发到源的 URI、查询字符串和标头的值取决于函数是否更改了值：
+ 如果函数没有更改 URI、查询字符串或标头，CloudFront 会将它在请求中收到的值转发到源。
+ 如果函数更改了 URI、查询字符串或标头，则 CloudFront 转发 UTF-8 编码的值。

## Microsoft Smooth Streaming
<a name="function-restrictions-microsoft-smooth-streaming"></a>

您无法将边缘函数与用于流式传输媒体文件（已转码为 Microsoft Smooth Streaming 格式）的 CloudFront 分配一起使用。

## 标签
<a name="function-restrictions-tagging"></a>

您无法将标签添加到边缘函数。有关在 CloudFront 中进行标记的更多信息，请参阅[标记分配](tagging.md)。

# 对 CloudFront Functions 的限制
<a name="cloudfront-function-restrictions"></a>

以下限制仅适用于 CloudFront Functions。

**Contents**
+ [日志](#cloudfront-function-restrictions-logs)
+ [请求正文](#cloudfront-function-restrictions-request-body)
+ [将临时凭证与 CloudFront KeyValueStore API 结合使用](#regional-endpoint-for-key-value-store)
+ [运行时](#cloudfront-function-runtime-restrictions)
+ [计算利用率](#cloudfront-function-restrictions-compute-utilization)

有关配额（以前称为限制）的更多信息，请参阅[CloudFront Functions 的配额](cloudfront-limits.md#limits-functions)。

## 日志
<a name="cloudfront-function-restrictions-logs"></a>

CloudFront Functions 中的函数日志被截断为 10 KB。

## 请求正文
<a name="cloudfront-function-restrictions-request-body"></a>

CloudFront Functions 无法访问 HTTP 请求的正文。

## 将临时凭证与 CloudFront KeyValueStore API 结合使用
<a name="regional-endpoint-for-key-value-store"></a>

您可以使用 AWS Security Token Service（AWS STS）生成临时安全凭证（也称为*会话令牌*）。会话令牌支持您临时代入 AWS Identity and Access Management（IAM）角色，以便您可以访问 AWS 服务。

要调用 [CloudFront KeyValueStore API](https://docs.aws.amazon.com/cloudfront/latest/APIReference/API_Operations_Amazon_CloudFront_KeyValueStore.html)，请使用 AWS STS 中的*区域* 端点来返回*版本 2* 会话令牌。如果您将*全局* 端点用于 AWS STS（`sts.amazonaws.com`），AWS STS 将生成*版本 1* 会话令牌，而签名版本 4A（SigV4A）不支持该令牌。因此，您将会收到身份验证错误。

要调用 CloudFront KeyValueStore API，可以使用以下选项：

**AWS CLI 和 AWS SDK**  
可以将 AWS CLI 或 AWS SDK 配置为使用区域 AWS STS 端点。有关更多信息，请参阅《AWS SDK 和工具参考指南》**中的 [AWS STS Regionalized endpoints](https://docs.aws.amazon.com/sdkref/latest/guide/feature-sts-regionalized-endpoints.html)。  
有关可用 AWS STS 端点的更多信息，请参阅《IAM 用户指南》**中的[区域和端点](https://docs.aws.amazon.com/IAM/latest/UserGuide/id_credentials_temp_enable-regions.html#id_credentials_region-endpoints)。

**SAML**  
可以将 SAML 配置为使用区域 AWS STS 端点。有关更多信息，请参阅 [How to use regional SAML endpoints for failover](https://aws.amazon.com/blogs/security/how-to-use-regional-saml-endpoints-for-failover/) 博客文章。

**`SetSecurityTokenServicePreferences` API**  
您可以为 AWS STS 配置全局端点来返回版本 2 会话令牌，而不是使用区域 AWS STS 端点。为此，请使用 [SetSecurityTokenServicePreferences](https://docs.aws.amazon.com/IAM/latest/APIReference/API_SetSecurityTokenServicePreferences.html) API 操作来配置您的 AWS 账户。  

**Example 示例：IAM CLI 命令**  

```
aws iam set-security-token-service-preferences --global-endpoint-token-version v2Token
```
我们建议您使用 AWS STS 区域端点来代替此选项。区域端点可提供更高的可用性和失效转移方案。

**自定义身份提供者**  
如果您使用的是执行联合身份验证并代入角色的自定义身份提供者，请使用负责生成会话令牌的父身份提供者系统的先前选项之一。

## 运行时
<a name="cloudfront-function-runtime-restrictions"></a>

CloudFront Functions 运行时环境不支持动态代码评估，并且该环境限制了对网络、文件系统、环境变量和计时器的访问。有关更多信息，请参阅 [受限功能](functions-javascript-runtime-10.md#writing-functions-javascript-features-restricted-features)。

**注意**  
要使用 CloudFront keyValueStore，您的 CloudFront 函数必须使用 [JavaScript 运行时 2.0](functions-javascript-runtime-20.md)。

## 计算利用率
<a name="cloudfront-function-restrictions-compute-utilization"></a>

CloudFront Functions 对运行时间有限制，测量方式为*计算利用率*。计算利用率是介于 0 到 100 之间的数字，表示函数运行所花费的时间占最大允许时间的百分比。例如，计算利用率为 35 表示函数在最大允许时间的 35% 内完成。

当您[测试函数](test-function.md)时，您可以在测试事件的输出中看到计算利用率值。对于生产函数，您可以在 [CloudFront 控制台中的监控页面](https://console.aws.amazon.com/cloudfront/v4/home?#/monitoring)或在 CloudWatch 中查看[计算利用率指标](viewing-cloudfront-metrics.md#monitoring-console.cloudfront-functions)。

# 对 Lambda@Edge 的限制
<a name="lambda-at-edge-function-restrictions"></a>

以下限制仅适用于 Lambda@Edge。

**Contents**
+ [DNS 解析](#lambda-at-edge-restrictions-dns)
+ [HTTP 状态代码](#lambda-at-edge-restrictions-status-codes)
+ [Lambda 函数版本](#lambda-at-edge-restrictions-version)
+ [Lambda 区域](#lambda-at-edge-restrictions-region)
+ [Lambda 角色权限](#lambda-at-edge-restrictions-role-permissions)
+ [Lambda 功能](#lambda-at-edge-restrictions-features)
+ [支持的运行时](#lambda-at-edge-restrictions-runtime)
+ [CloudFront 标头](#lambda-at-edge-restrictions-cloudfront-headers)
+ [具有 Include Body（包含正文）选项的请求正文的限制](#lambda-at-edge-restrictions-request-body)
+ [响应超时和保持连接超时（仅自定义源）](#timeout-for-lambda-edge-functions)

有关 配额的信息，请参阅 [有关 Lambda@Edge 的配额](cloudfront-limits.md#limits-lambda-at-edge)。

## DNS 解析
<a name="lambda-at-edge-restrictions-dns"></a>

CloudFront 会先对源域名执行 DNS 解析，*然后* 再执行源请求 Lambda@Edge 函数。如果您的域的 DNS 服务出现问题，并且 CloudFront 无法解析域名以获取 IP 地址，将不会调用您的 Lambda@Edge 函数。CloudFront 会将 [HTTP 状态代码 502（无效网关）](http-502-bad-gateway.md)返回到客户端。有关更多信息，请参阅 [DNS 错误（`NonS3OriginDnsError`）](http-502-bad-gateway.md#http-502-dns-error)。

如果您的函数逻辑修改了源域名，则在函数执行完毕后，CloudFront 将对已更新的域名执行另一次 DNS 解析。

有关管理 DNS 故障转移的更多信息，请参阅《Amazon Route 53 开发人员指南》**中的[配置 DNS 故障转移](https://docs.aws.amazon.com/Route53/latest/DeveloperGuide/dns-failover-configuring.html)。

## HTTP 状态代码
<a name="lambda-at-edge-restrictions-status-codes"></a>

查看器响应事件的 Lambda@Edge 函数无法修改响应的 HTTP 状态代码，无论响应是来自源还是 CloudFront 缓存。

## Lambda 函数版本
<a name="lambda-at-edge-restrictions-version"></a>

您必须使用带编号的 Lambda 函数，而不是 `$LATEST` 或别名。

## Lambda 区域
<a name="lambda-at-edge-restrictions-region"></a>

Lambda 函数必须位于美国东部（弗吉尼亚州北部）区域。

## Lambda 角色权限
<a name="lambda-at-edge-restrictions-role-permissions"></a>

与 Lambda 函数关联的 IAM 执行角色必须由服务委托人 `lambda.amazonaws.com` 和 `edgelambda.amazonaws.com` 担任。有关更多信息，请参阅 [设置 Lambda@Edge 的 IAM 权限和角色](lambda-edge-permissions.md)。

## Lambda 功能
<a name="lambda-at-edge-restrictions-features"></a>

Lambda@Edge 不支持以下 Lambda 功能：
+ **自动**（默认设置）以外的 [Lambda 运行时管理配置](https://docs.aws.amazon.com/lambda/latest/dg/runtimes-update.html#runtime-management-controls)。
+ 将您的 Lambda 函数配置为访问 VPC 内的资源
+ [Lambda 函数死信队列](https://docs.aws.amazon.com/lambda/latest/dg/invocation-async.html#dlq)
+ [Lambda 环境变量](https://docs.aws.amazon.com/lambda/latest/dg/configuration-envvars.html)（自动支持的预留环境变量除外）
+ Lambda 函数与[管理具有层级的 AWS Lambda 依赖关系](https://docs.aws.amazon.com/lambda/latest/dg/chapter-layers.html)
+ [使用 AWS X-Ray](https://docs.aws.amazon.com/lambda/latest/dg/lambda-x-ray.html)
+ Lambda 预配置并发
**注意**  
Lambda@Edge 函数与所有 Lambda 函数共享相同的[区域并发](https://docs.aws.amazon.com/lambda/latest/dg/configuration-concurrency.html)功能。有关更多信息，请参阅 [有关 Lambda@Edge 的配额](cloudfront-limits.md#limits-lambda-at-edge)。
+ [使用容器映像创建 Lambda 函数](https://docs.aws.amazon.com/lambda/latest/dg/images-create.html)
+ [使用 arm64 架构的 Lambda 函数](https://docs.aws.amazon.com/lambda/latest/dg/foundation-arch.html)
+ 短暂存储超过 512 MB 的 Lambda 函数。
+ 使用[客户自主管理型密钥来加密 .zip 部署包](https://docs.aws.amazon.com/lambda/latest/dg/encrypt-zip-package.html)

## 支持的运行时
<a name="lambda-at-edge-restrictions-runtime"></a>

Lambda@Edge 支持最新版本的 Node.js 和 Python 运行时。有关支持的版本及其未来弃用日期的列表，请参阅《AWS Lambda 开发人员指南》**中的[支持的运行时](https://docs.aws.amazon.com/lambda/latest/dg/lambda-runtimes.html#runtimes-supported)。

**提示**  
作为最佳实践，请使用所提供的运行时的最新版本来改进性能和新增功能。
您无法使用已弃用的 Node.js 版本创建或更新函数。您只能将现有函数与这些版本及 CloudFront 分配相关联。使用与分配关联的这些版本的函数将继续运行。但是，建议将您的函数转移到更新版本的 Node.js。有关更多信息，请参阅《AWS Lambda 开发人员指南》**中的[运行时弃用策略](https://docs.aws.amazon.com/lambda/latest/dg/runtime-support-policy.html)和 GitHub 上的 [Node.js 发布计划](https://github.com/nodejs/Release#release-schedule)。

## CloudFront 标头
<a name="lambda-at-edge-restrictions-cloudfront-headers"></a>

Lambda@Edge 函数可以读取、编辑、删除或添加 [添加 CloudFront 请求标头](adding-cloudfront-headers.md) 中列出的任何 CloudFront 标头。

**备注**  
如果您希望 CloudFront 添加这些标头，则必须将 CloudFront 配置为使用[缓存策略](controlling-the-cache-key.md)或[源请求策略](controlling-origin-requests.md)来添加它们。
CloudFront 在查看器请求事件*之后* 添加标题，这意味着这些标头在查看器请求函数中不可用于 Lambda@Edge 函数。这些标头仅适用于源请求和源响应中的 Lambda@Edge 函数。
如果查看器请求包含具有这些名称的标头，并且您将 CloudFront 配置为使用[缓存策略](controlling-the-cache-key.md)或者[源请求策略](controlling-origin-requests.md)添加这些标头，则 CloudFront 会覆盖查看器请求中的标头值。面向查看器的函数可以看到查看器请求中的标头值，而面向源的函数则看到 CloudFront 添加的标头值。
如果查看器请求函数添加 `CloudFront-Viewer-Country` 标头，它将无法通过验证，并且 CloudFront 会将 HTTP 状态代码 502（无效网关）返回到查看器。

## 具有 Include Body（包含正文）选项的请求正文的限制
<a name="lambda-at-edge-restrictions-request-body"></a>

在选择**包含正文**选项以向您的 Lambda@Edge 函数公开请求正文时，以下信息和大小限制适用于公开或替换的正文部分。
+ CloudFront 总是对请求正文进行 base64 编码，然后再将其公开给 Lambda@Edge。
+ 如果请求正文很大，则 CloudFront 在将正文公开给 Lambda@Edge 之前将其截断，如下所示：
  + 对于查看器请求事件，正文将截断为 40 KB。
  + 对于源请求事件，正文将截断为 1 MB。
+ 如果以只读方式访问请求正文，则 CloudFront 将完整的原始请求正文发送到源。
+ 如果您的 Lambda@Edge 函数替换请求正文，则以下大小限制适用于该函数返回的正文：
  + 如果 Lambda@Edge 函数以纯文本形式返回正文：
    + 对于查看器请求事件，正文限制为 40KB。
    + 对于源请求事件，正文限制为 1MB。
  + 如果 Lambda@Edge 函数以 base64 编码文本形式返回正文：
    + 对于查看器请求事件，正文限制为 53.2KB。
    + 对于源请求事件，正文限制为 1.33MB。

**注意**  
如果 Lambda @Edge 函数返回的正文超出了这些限制，请求将失败，系统将显示 HTTP 502 状态代码 ([Lambda 验证错误](http-502-bad-gateway.md#http-502-lambda-validation-error))。建议您更新 Lambda @Edge 函数，使正文不超出这些限制。

## 响应超时和保持连接超时（仅自定义源）
<a name="timeout-for-lambda-edge-functions"></a>

如果您使用 Lambda@Edge 函数为分配源设置响应超时或保持连接超时，请确认您指定了您的源可以支持的值。有关更多信息，请参阅 [响应和保持连接超时限额](DownloadDistValuesOrigin.md#response-keep-alive-timeout-quota)。