

# 使用 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)
```

------