

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

**Example 示例**  

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

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

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

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

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

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

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

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

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

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