亚马逊CloudFront是一项服务,它通过分布在数百个地点的全球机器网络(也称为边缘位置)加快了静态和动态网络内容的分发和交付。CloudFront功能是FaaS(功能即服务)的化身,允许你将JavaScript功能部署到AWS的边缘位置网络上,尽可能靠近终端用户执行。
这项新功能允许你为你的应用程序用户定制或个性化的内容,更接近他们所处的位置,从而最大限度地减少网络延迟。例如,你可以转换HTTP头或API响应,为每个访问者个性化你的应用程序,实施认证或加密逻辑(如JWT认证)以允许或拒绝请求,或在边缘设置URL重写和重定向。
在这篇文章中,我们将详细探讨CloudFront函数,包括其目的、用例,以及如何开始编写和部署你的第一个函数。
CloudFront函数与AWS Lambda@Edge的比较
AWS Lambda@Edge于2017年7月推出,是AWS Lambda的扩展,其功能与CloudFront Functions类似,因为它允许你利用Amazon CloudFront向全球交付函数结果。虽然Lambda@Edge相当强大,但在许多情况下,尤其是那些在请求由CloudFront基础设施提供之前或在将此类请求的响应派发给最终用户之前需要少量计算的情况下,它并不是最好的选择,主要是因为Lambda@Edge函数是在区域边缘缓存(通常在离客户端到达的CloudFront边缘位置最近的AWS区域)而不是边缘位置本身执行。
CloudFront函数的创建是为了提供一个更适合于更大流量和更低延迟的解决方案,因为它们是在最接近终端用户的边缘位置而不是AWS区域执行的。这使它们成为轻量级CloudFront CDN转换和操作的理想选择,可以在每个请求上运行,以便在更大的容量下实现延迟敏感的操作。以下是CloudFront功能与Lambda@Edge的比较摘要。
- CloudFront函数和Lambda@Edge是针对CloudFront产生的事件而执行的。
- CloudFront函数只响应查看器触发器(当CloudFront收到查看器的请求或向查看器发送响应)。然而,Lambda@Edge可以与查看器触发器和原点触发器(当CloudFront将请求转发给原点或从原点接收响应)一起工作。
- Lambda@Edge函数在大约13个区域边缘缓存中执行(在撰写本文时),而CloudFront函数则在218多个边缘位置执行。
- CloudFront函数只支持JavaScript,而Lambda@Edge对Node.js和Python都有运行时支持。
- CloudFront函数只能操作HTTP头。如果您需要删除或替换 HTTP 请求或响应的主体,请使用 Lambda@Edge。
- CloudFront函数不能访问网络或文件系统,但Lambda@Edge可以。
- CloudFront函数的运行时间不到一毫秒,而Lambda@Edge对于查看器触发器来说可能需要5秒,对于起源触发器来说可能需要30秒。
- 分配给CloudFront Functions的最大内存是2MB,而Lambda@Edge的最大内存是128MB(查看器触发器)和10GB(起源触发器)。
- 如果你想在内容被缓存之前和之后对其进行操作,CloudFront Functions和Lambda@Edge可以一起使用。
- 与Lambda@Edge不同,CloudFront Functions有一个免费层级。前者也是按请求收费(每百万次调用0.1美元),而后者是按请求(每百万次调用0.6美元)和功能持续时间(每128MB-秒0.00000625125美元)收费。
CloudFront功能如何工作
CloudFront功能是在CloudFront基础设施中原生构建的,有超过218个存在点,分布在90个城市和47个国家。这些地点中的每一个都承载着一个函数运行时的实例,它是一个符合ECMAScript 5.1标准的JavaScript引擎,每个运行时都能够每秒处理数千万个请求,同时提供亚毫秒级的延迟。

出于安全考虑,每个功能脚本都被很好地隔离在自己的进程中,每个进程周围都有几个虚拟的保护墙。这种模式消除了AWS Lambda和Lambda@Edge使用的基于虚拟机(VM)的隔离模式的冷启动,进一步降低了延迟。单个功能脚本的寿命也很短,因为它们的运行时间不到1毫秒,对CloudFront CDN的性能没有任何可感知的影响。
CloudFront功能是由特定的CloudFront分布上的事件触发的,例如当CloudFront收到观众的请求(观众请求)和在CloudFront即将向观众提供响应(观众响应)之前。你可以从CloudFront控制台使用IDE或通过CloudFront CLI创建新函数。可以直接针对CloudFront分发版测试你的函数,以确保它们在部署后能正确运行。
CloudFront 函数的使用情况
CloudFront功能是一种很好的方式,可以通过在CDN层而不是在源服务器上执行代码来扩展你的产品功能或彻底改变其执行某些任务的方式。通过选择使用功能,你将能够建立各种解决方案,例如以下内容:
- 通过根据你关心的条件重写请求的URL,根据用于提出请求的设备提供不同的内容。例如,你可以根据用户的设备向他们发送不同分辨率的视频内容。
- 实施地理定位,以确保根据终端用户的来源国提供正确的内容。例如,你可以用它来给予购买力平价(PPP)折扣。
- 在转发到来源国或客户端之前,检查或修改任何请求头。
- 保护你的网络属性上的内容不被其他网站热链接。
- 添加安全规则和过滤器,阻止不需要的访问者和机器人。
- 通过控制基于cookies的响应来设置A/B测试。这有助于在不改变URL或重定向的情况下测试一个网站的不同版本。
- 从边缘直接(和快速)做出反应,而不影响原点。
- 对通过CloudFront交付的内容实施访问控制和授权,并将未经认证的用户重定向到登录页面。
- 分析和跟踪用户在你的网站和移动应用程序上的活动。
- 在所有响应中添加HTTP安全头(如内容安全策略),而无需修改你的应用程序代码。
CloudFront函数能做什么
每个CloudFront函数都有一个名为handler 的入口点。它需要一个名为event 的参数,这是一个HTTP请求和响应的JSON表示。这个事件对象的基本结构如下所示:
{
"version": "1.0",
"context": {
<context object>
},
"viewer": {
<viewer object>
},
"request": {
<request object>
},
"response": {
<response object>
}
}
一个函数可以做三件事。
1.修改一个HTTP请求
你可以写一个脚本,在返回到CloudFront继续处理之前修改一个客户端请求。例如,你可以修改现有的请求头或设置新的请求头。
function handler(event) {
var request = event.request;
// Modify the request object here.
request.headers['x-custom-header'] = {value: 'example value'};
// return modified request to CloudFront for further processing
return request;
}
2.修改一个HTTP响应
通过CloudFront功能,你可以在交付给客户端之前修改HTTP响应头。
function handler(event) {
var response = event.response;
// Modify the response object here.
response.statusDescription = "a description";
response.headers['x-custom-header'] = {value: 'example value'};
// return modified response
return response;
}
3.创建一个新的HTTP响应
您可以在边缘对 HTTP 请求作出响应,而无需 CloudFront 进行任何进一步处理。注意,你不能用CloudFront函数包含一个响应体。如果你需要这样做,请使用Lambda@Edge代替:
function handler(event) {
var request = event.request;
// Create the response object here
var response = {
statusCode: 200,
"headers": {
"some-header": {
"value": "some-value",
},
},
};
// return response
return response;
}
开始使用CloudFront函数
让我们继续使用CloudFront控制台创建我们的第一个函数。这个函数将在每个响应传递给客户端之前,为其添加一些安全头信息。在你继续之前,确保你有一个现有的CloudFront分布,或者按照本文的步骤创建一个。
创建函数
使用 CloudFront 控制台
在CloudFront控制台,选择侧面导航中的功能,然后点击创建功能按钮。

输入一个函数名称(如security-headers ),然后点击继续。在这一点上,你将能够为函数的主体编写代码。在开发阶段下的编辑器中输入以下内容,然后点击保存按钮。
function handler(event) {
var response = event.response;
response.headers["content-security-policy"] = {
value: "default-src 'self'; script-src 'self' 'unsafe-inline' https://cdn.jsdelivr.net;"
};
response.headers["x-xss-protection"] = {
value: "1; mode=block"
};
response.headers["feature-policy"] = {
value: "accelerometer 'none'; camera 'none'; geolocation 'none'; gyroscope 'none'; magnetometer 'none'; microphone 'none'; payment 'none'; usb 'none'"
};
response.headers["x-frame-options"] = {
value: "DENY"
};
response.headers["referrer-policy"] = {
value: "strict-origin-when-cross-origin"
};
response.headers["x-content-type-options"] = {
value: "nosniff"
};
return response;
}

使用AWS CLI
在继续之前,请确保你已经安装并配置了AWS CLI,并为你的账户提供了正确的凭据。
在你的文件系统中的某个地方创建的function.js 文件中写下上面列出的函数代码,然后使用下面的命令在CloudFront上创建该函数。
$ aws cloudfront create-function \
--name security-headers \
--function-config Comment="Security headers function",Runtime="cloudfront-js-1.0" \
--function-code fileb://function.js
如果一切顺利,你会得到以下输出,描述你刚刚创建的函数。注意并复制ETag ,因为它将在随后的章节中用来识别这个函数:
{
"Location": "https://cloudfront.amazonaws.com/2020-05-31/function/arn:aws:cloudfront::121663830981:function/security-headers",
"ETag": "ETVPDKIKX0DER",
"FunctionSummary": {
"Name": "security-headers",
"Status": "UNPUBLISHED",
"FunctionConfig": {
"Comment": "Security headers function",
"Runtime": "cloudfront-js-1.0"
},
"FunctionMetadata": {
"FunctionARN": "arn:aws:cloudfront::121663830981:function/security-headers",
"Stage": "DEVELOPMENT",
"CreatedTime": "2021-06-06T14:40:49.261000+00:00",
"LastModifiedTime": "2021-06-06T14:40:49.261000+00:00"
}
}
}

测试该函数
使用CloudFront控制台
一旦你创建了这个函数,你就可以通过提供一个事件对象来测试它,该对象代表了你的CloudFront分发在生产中会收到的HTTP请求或响应的类型。
在函数页面上,单击 "测试"选项卡。选择事件类型(查看器响应)、阶段(开发)和样本事件(查看器响应与头文件)。你可以通过输入部分的选项修改这个样本事件(或从头开始创建一个)。JSON选项卡对于通过AWS CLI或CloudFront API复制事件对象的JSON表示法进行测试特别方便。你不需要在这里改变任何东西,所以继续点击测试按钮来运行测试。

一旦测试运行,你将在屏幕顶部看到一个成功信息(如果测试失败,则是失败信息)。在输出部分,在计算利用率下,你会看到一个数字,表示该函数运行的时间长度占最大允许时间的百分比。例如,计算利用率为34意味着该函数在最大允许时间的34%内完成。
使用AWS CLI
要使用AWS CLI测试函数,你需要创建一个JSON文件(如event-object.json )并将其传递给test-function 子命令。这里是反映CloudFront控制台上带有头文件的Viewer响应样本事件的JSON对象。
{
"version": "1.0",
"context": {
"eventType": "viewer-response"
},
"viewer": {
"ip": "1.2.3.4"
},
"request": {
"method": "GET",
"uri": "/index.html",
"querystring": {
"test": {
"value": "true"
},
"fruit": {
"value": "apple",
"multiValue": [
{
"value": "apple"
},
{
"value": "banana"
}
]
}
},
"headers": {
"host": {
"value": "www.example.com"
},
"accept": {
"value": "text/html",
"multiValue": [
{
"value": "text/html"
},
{
"value": "application/xhtml+xml"
}
]
}
},
"cookies": {
"id": {
"value": "CookieIdValue"
},
"loggedIn": {
"value": "false"
}
}
},
"response": {
"statusDescription": "OK",
"headers": {
"server": {
"value": "CustomOriginServer"
},
"content-type": {
"value": "text/html; charset=UTF-8"
},
"content-length": {
"value": "9593"
}
},
"cookies": {},
"statusCode": 200
}
}
保存后,将JSON文件传递给test-function 子命令,如下所示。确保你将--if-match 标志的值替换为你在上一节复制的ETag值。
$ aws cloudfront test-function \
--name security-headers \
--if-match ETVPDKIKX0DER \
--event-object fileb://event-object.json \
--stage DEVELOPMENT
如果命令成功,你将看到与下图类似的输出,它显示了测试该功能的结果。
{
"TestResult": {
"FunctionSummary": {
"Name": "security-headers",
"Status": "UNPUBLISHED",
"FunctionConfig": {
"Comment": "Security headers function",
"Runtime": "cloudfront-js-1.0"
},
"FunctionMetadata": {
"FunctionARN": "arn:aws:cloudfront::121663830981:function/security-headers",
"Stage": "DEVELOPMENT",
"CreatedTime": "2021-06-06T14:40:49.261000+00:00",
"LastModifiedTime": "2021-06-06T14:40:49.333000+00:00"
}
},
"ComputeUtilization": "27",
"FunctionExecutionLogs": [],
"FunctionErrorMessage": "",
"FunctionOutput": "{\"response\":{\"headers\":{\"server\":{\"value\":\"CustomOriginServer\"},\"content-length\":{\"value\":\"9593\"},\"content-security-policy\":{\"value\":\"default-src 'self'; script-src 'self' 'unsafe-inline' https://cdn.jsdelivr.net;\"},\"x-content-type-options\":{\"value\":\"nosniff\"},\"x-xss-protection\":{\"value\":\"1; mode=block\"},\"x-frame-options\":{\"value\":\"DENY\"},\"referrer-policy\":{\"value\":\"strict-origin-when-cross-origin\"},\"content-type\":{\"value\":\"text/html; charset=UTF-8\"},\"feature-policy\":{\"value\":\"accelerometer 'none'; camera 'none'; geolocation 'none'; gyroscope 'none'; magnetometer 'none'; microphone 'none'; payment 'none'; usb 'none'\"}},\"statusDescription\":\"OK\",\"cookies\":{},\"statusCode\":200}}"
}
}
注意以下关于输出的内容:
FunctionSummary描述了被测试的函数。ComputeUtilization表示该函数运行的时间长度,占最大允许时间的百分比。FunctionOutput是函数返回的对象。正如你所看到的,输出对象包括在函数代码中设置的安全头,这证明了函数是按预期工作的。FunctionErrorMessage将包含一个错误信息,如果测试不成功。

发布函数
使用CloudFront控制台
在彻底测试你的函数后,你可以移到 "发布"选项卡,将函数从开发阶段复制到上线阶段。你所需要做的就是点击 "发布"按钮(如果更新一个函数,则点击 "发布并更新")。

使用AWS CLI
aws cloudfront publish-function 命令将发布与分别传递给--name 和--if-match 选项的名称和 ETag 值相匹配的函数。
$ aws cloudfront publish-function \
--name security-headers \
--if-match ETVPDKIKX0DER
下面是如果发布成功,你可以期待得到的输出:
{
"FunctionSummary": {
"Name": "security-headers",
"Status": "UNASSOCIATED",
"FunctionConfig": {
"Comment": "Security headers function",
"Runtime": "cloudfront-js-1.0"
},
"FunctionMetadata": {
"FunctionARN": "arn:aws:cloudfront::121663830981:function/security-headers",
"Stage": "LIVE",
"CreatedTime": "2021-06-06T15:15:00.413000+00:00",
"LastModifiedTime": "2021-06-06T15:15:00.413000+00:00"
}
}
}

将函数与CloudFront发布相关联
使用CloudFront控制台
单击 "关联"选项卡并选择分布、事件类型(本例中为Viewer Response)和缓存行为。然后,点击添加关联,并在对话框中确认。

页面顶部应该出现一个横幅,确认与分布的成功关联。你也可以在关联的CloudFront分布下看到功能关联。

使用 AWS CLI
要使用 AWS CLI 将 CloudFront 功能与现有的分发版关联起来,请从控制台获得分发版 ID,并将其传递给aws cloudfront get-distribution-config 命令的--id 标志,如下所示。
$ aws cloudfront get-distribution-config \
--id E3GA5OOQ5INAXA \
--output yaml > dist-config.yaml
如果成功,上述命令将不显示任何输出。然而,你应该在当前目录中看到一个新创建的dist-config.yaml 文件,该文件应在你最喜欢的文本编辑器中打开。按照下面的描述编辑该文件。
- 将
Etag字段改为IfMatch,但保留其值不变。 - 找到
FunctionAssociations字段并更新它,如下所示:
# dist-config.yaml
FunctionAssociations:
Items:
- EventType: viewer-response
FunctionARN: arn:aws:cloudfront::121663830981:function/security-headers
Quantity: 1
用通过在终端运行aws cloudfront list-functions 检索到的适当函数的FunctionARN 字段替换上面的FunctionARN 的值。你也可以把viewer-response 改为viewer-request ,如果这是你的函数需要被触发的话。对于security-headers函数,viewer-response 是合适的。一旦你完成了编辑,保存文件。

最后,使用aws cloudfront update-distribution 命令,用dist-config.yaml 文件的内容更新指定的分布,如下图所示:
$ aws cloudfront update-distribution \
--id E3GA5OOQ5INAXA \
--cli-input-yaml file://dist-config.yaml
运行该命令后,一些描述刚刚被更新的分布的输出将被打印到控制台。在重新部署发行版的过程中,发行版的Status 将变为InProgress ,这通常需要几分钟的时间。

验证函数
现在,函数已经发布并与 CloudFront 分发版相关联,现在是时候确认它是否能正常工作。你可以使用curl 或浏览器向存在于你的CloudFront分布中的资源提出请求,如下所示:
$ curl --head https://d2sbyrn254rio7.cloudfront.net/doc.html
HTTP/2 200
content-type: text/html
content-length: 0
date: Tue, 01 Jun 2021 13:43:26 GMT
last-modified: Tue, 01 Jun 2021 13:42:40 GMT
etag: "d41d8cd98f00b204e9800998ecf8427e"
accept-ranges: bytes
server: AmazonS3
via: 1.1 e792582e94d051796ee83e4a94038f8e.cloudfront.net (CloudFront)
content-security-policy: default-src 'self'; script-src 'self' 'unsafe-inline' https://cdn.jsdelivr.net;
x-xss-protection: 1; mode=block
x-frame-options: DENY
referrer-policy: strict-origin-when-cross-origin
feature-policy: accelerometer 'none'; camera 'none'; geolocation 'none'; gyroscope 'none'; magnetometer 'none'; microphone 'none'; payment 'none'; usb 'none'
x-content-type-options: nosniff
x-cache: Hit from cloudfront
x-amz-cf-pop: JFK51-C1
x-amz-cf-id: 84P8wPkvE7TjGl_ssjryL-6vmkW1dhaeH4gaoHZv7A6BPzk4lbVlWg==
注意,所有由函数代码添加的响应头都包含在响应中。这证明了该函数工作正常。
总结
CloudFront函数是实现大批量CDN定制的好方法,可以在每个请求上运行,使你能够以低延迟向你的最终用户提供更丰富、更个性化的内容。我希望这个介绍能帮助你弄清楚如何在你的应用程序中利用它们。
谢谢你的阅读,并祝你编码愉快