亚马逊AWS官方博客

利用 Amazon CloudFront Functions 实现影片试播

视频网站在提供流媒体点播服务时,针对付费的观看内容,通常都会提供试播功能。试播时长可以是几分钟到十几分钟不等,主要目的是为了让观众能够获得足够的内容来评估影片质量,以决定是否要对观看内容进行下一步的付费购买。

随着视频付费点播的快速发展和普及,影片试播功能对于视频平台愈发重要,已经变成了非常普遍的一个需求。该功能的实现方式,通常需要平台方在服务端或在播放器对视频文件进行分段处理。本文介绍了利用 Amazon CloudFront Functions 功能,在不改变原始点播文件的前提下,在 CDN 层面实现影片试播的一种方法。

在开始介绍之前,为了让读者更好理解 Amazon CloudFront 及其 Functions 功能,有一些相关重要概念需先做前置介绍,包括:

  • HTTP Range 请求
  • 分段缓存
  • Amazon CloudFront Functions 功能简介

什么是 HTTP Range 请求

在 HTTP/1.1 之后,HTTP 协议开始支持 Range 请求。对于一个体积较大的文件,用户可以通过在 HTTP 头部指定 Range 范围的方式,去请求一个目标文件的其中一部分。其使用场景主要有:

  • 断点续传:当下载大文件时,如果下载过程中出现中断或网络故障,HTTP Range 请求可以用于实现断点续传。客户端可以记录已下载的文件范围,并在继续下载时,发送 Range 请求头以获取剩余部分。
  • 视频流媒体:对于流媒体服务,如视频或音频,HTTP 分段请求可以用于实现逐段加载。客户端可以按需请求文件的各个片段,从而实现边下载边播放,提高用户体验。
  • 多线程下载:在多线程下载场景中,HTTP Range 请求可以被用来同时请求文件的多个片段,以加快下载速度。每个线程可以请求不同的文件范围,然后将片段合并成完整的文件。
  • 部分内容获取:有时候只需要获取文件的某个片段而不是整个文件。通过 HTTP 分段请求,可以定位到所需的片段,并只获取特定的内容,减少数据传输和处理的开销。

什么是分段缓存

分段缓存是一种内容分发网络(CDN)的缓存策略,它将大文件划分为多个段,并分别对这些段进行缓存。当用户请求文件时,CDN 只需获取和传输所需的段,而不必获取整个文件。对于大文件的请求,CDN 分段缓存可为用户带来如下收益:

  • 减少网络延迟:由于只需获取所需的文件段,CDN 分段缓存可以显著降低网络延迟。用户只需等待获取所需的段,而不需要等待整个文件的传输完成。
  • 提高缓存命中率:CDN 分段缓存使得多个用户可以共享相同的文件段,从而提高缓存命中率。当多个用户请求相同的文件时,CDN 可以根据已缓存的段直接提供,而无需重新获取整个文件。
  • 节省用户/源站流量成本:CDN 分段缓存可以减少不必要的数据传输。只有请求的文件段需要传输,而不需要传输整个文件,从而节省了流量费用。
  • 支持动态更新:当文件的某个段发生变化时,CDN 只需更新该段的缓存,而不必重新缓存整个文件。这样可以更快地将更新的内容传递给用户。

针对客户端的 Range GET,CloudFront 原生支持对其进行分段缓存,无需做额外配置。具体缓存逻辑请见该文档 https://docs.aws.amazon.com/zh_cn/AmazonCloudFront/latest/DeveloperGuide/RangeGETs.html,具体说明如下:

  • 若边缘站点中的缓存已经包含整个对象,或请求的对象分段,CloudFront 会直接从缓存中响应所请求的分段。
  • 若缓存不包含请求的范围,CloudFront 会将请求转发到源,若源站支持 Range GET 请求(比如 Amazon S3),源站将返回被请求的分段,CloudFront 会将该分段响应给用户并做缓存。

我们可以用 curl -i -H “Range: bytes=100-199” https://d29ccpxyxnf2fp.cloudfront.net/1MB 命令,对 S3 上的 1MB 文件进行请求,来测试 CloudFront 和 S3 对与分段请求的响应模式。如下是当 CloudFront 和 S3 接收到 Range GET 请求时,对应的日志示例:

CloudFront 日志:

S3 日志:

我们可以看到,当 CloudFront 或 S3 接收到 Range GET 请求时,会遵从 RFC 7233 规范,向客户端响应 206 状态码;S3 会将对应的 100 Bytes 内容分段返回给 CloudFront,而不是整个文件。

CloudFront Functions 简介

CloudFront Functions 是 CloudFront 的一个拓展功能,其主要目的是在 CDN 边缘节点上对请求和响应进行处理,从而提供更快的响应时间和更高的灵活性,实现“可编程 CDN”的目的。用户可以使用 JavaScrpit 便携函数,这些函数在 CloudFront 边缘节点上执行,并且可以在 HTTP 请求和响应的不同阶段进行操作。该功能的主要使用场景有:

  • 动态内容处理:用户可以在边缘节点上执行动态操作,例如根据用户的地理位置或设备类型提供不同的内容。
  • 安全性增强:用户可以使用 CloudFront Functions 来检测和阻止恶意请求,进行访问控制,或者实施其他安全性策略。
  • 缓存控制:通过在请求和响应中修改标头,用户可以自定义缓存策略,从而提高缓存命中率并优化用户体验。
  • 重写请求和响应:用户可以在边缘节点上修改请求和响应的内容,以满足特定的需求,例如重定向请求或者更改响应中的内容。

CloudFront Functions 提供了一个灵活且高性能的方式来自定义和扩展您的内容分发网络。通过在边缘节点上运行自定义 JavaScript 代码,您可以实现更多的功能,并优化用户体验,更多详细内容请参考文档:https://docs.aws.amazon.com/zh_cn/AmazonCloudFront/latest/DeveloperGuide/edge-functions.html

方案实现逻辑及具体配置

对于视频文件,本篇博客测试的文件格式为 MP4,但该方法针对单一文件格式,比如 FLV,TS 格式均适用。对于其他比较常见的基于索引文件的流媒体协议,比如 HLS 或 DASH,也可以考虑利用 CloudFront Functions 修改 TS 片的请求范围,或更进一步,利用 CloudFront+Lambda@edge 修改 CloudFront 返回的 M3U8 文件 Body,具体方法在本篇博客中不做展开讨论。Lambda@edge 是基于边缘节点的 AWS Lamda 服务的拓展,可以将您的 Lambda 运行时部署在全球的 CloudFront 边缘节点上。关于 Lambda@edge 与 Cloudfront Functions 的功能和应用场景区别,请参见该文档:https://docs.aws.amazon.com/zh_cn/AmazonCloudFront/latest/DeveloperGuide/edge-functions.html

对于试播文件的请求流程如下:

  1. 客户需将试播的视频文件 URL,添加相关的 Range 查询字符串参数。其中,Range 字段即规定了用户可请求的文件 Range 范围,比如 https://d3rvqep1efif7v.cloudfront.net/demo-video.mp4?range=0-5242880,即意为允许用户请求视频文件的前 5MB 部分。
  2. CloudFront 收到该请求后,会通过 CloudFront Functions 函数,将查询字符串中的 range 参数读取出来,并且将该参数写入到该请求的 Range Header 中,比如:range: bytes=0-5242880。
  3. 同时,CloudFront Functions 中可以添加额外的鉴权逻辑,若用户查询字符串中的 range 参数超过了特定范围,则返回 403 拒绝本次请求。
  4. CloudFront 收到改写过的请求后,若对应分段在边缘节点有缓存,则会根据请求中的 Range Header 参数,返回对应的文件分段;若没有缓存,则回源 S3,获取对应分段后,响应给用户。

准备测试 S3 存储桶/视频文件

新建 S3 存储桶,并将要测试的视频文件上传到 S3 桶中。该部分内容在此篇博客中不做详细介绍,相关操作请参考 S3 文档:https://docs.aws.amazon.com/zh_cn/AmazonS3/latest/userguide/Welcome.html

本文准备的测试环境如下:

  • 存储桶名称:cloudfront-functions-demo-bucket
  • 视频文件名称:demo-video.mp4
  • 视频大小:23 MB
  • 视频时长:33 秒
  • Region:ap-northeast-1

创建 CloudFront 分配

  • 源域”选择如上创建的 S3 Bucket 域名
  • 来源访问”,选择“来源访问控制设置” – “创建控制设置” – “创建”。该设置将生成一条新的 Bucket Policy,允许 CloudFront 访问 S3 Bucket
  • Web 应用程序防火墙”,选择“不要启用安全防护
  • 标准日志记录”,选择“打开”,选择如上创建的存储桶,日志前缀输入“CloudFront-Logs”。此设置将开启 CloudFront 访问日志并存储在对应的存储桶中,方便我们后续观察 CloudFront 的请求和响应情况
  • 其他配置保持默认即可
  • 选择“创建分配

等待几分钟后,分配即可创建完成。

创建函数

  • 在 CloudFront 控制台界面,左侧菜单选择“函数”,然后点击“创建函数
  • 函数名称可自定义,比如:range_request_demo
  • 在“构建”界面,在“开发”窗口,输入您的 JavaScript 代码,指定 CloudFront 对 Range 请求的判断逻辑,然后点击“保存更改”:

  • 参考代码如下:
function handler(event) {
    var request = event.request;
    var headers = request.headers;
    
    //获取查询字符串中的 range 起止范围
    if (request.querystring.hasOwnProperty('range')){
        var range = request.querystring.range.value;
            
        //将 range 范围添加到 HTTP 请求 Header 中
        headers.range = {value:'bytes='+range};
        var rangeValues = range.split('-');
        var rangeStart = parseInt(rangeValues[0]);
        var rangeEnd = parseInt(rangeValues[1]);
            
        //判断 Range 范围条件,禁止不合规请求
        if (rangeStart > 5242880 || rangeEnd > 5242880 || isNaN(rangeEnd) ) {
            var response = {
                statusCode: 403,
                statusDescription: 'Forbidden',
                headers: {
                    'content-type': {value: 'text/plain'}
                },
                body: 'Invalid range request.'
            }
            return response;
        }
    }else{
            var response = {
                statusCode: 403,
                statusDescription: 'Forbidden',
                headers: {
                    'content-type': {value: 'text/plain'}
                },
                body: 'Please add range parameter in your query string.'
            }
            return response;
        }
    
    return request;
}
  • 在“实时”窗口,我们可以查看目前线上已经发布的代码版本,与当前正在开发的代码版本的差异
  • 在“发布”界面,选择“发布函数
  • 函数被发布后,则可继续将该函数关联到指定的分配。点击“添加关联
    • 指定对应关联的分配
    • 事件类型”选择“Viewer request”,则该函数会对客户端的请求生效
    • 缓存行为”选择您想指定该函数生效的行为,此处选择 Default(*),可根据实际情况酌情调整

  • 添加关联后,即可在 CloudFront 控制台观察到对应的分配的状态变为“部署中”。等待几分钟后,即可生效

效果测试

对于效果的测试,我们可以采用 Amazon IVS 的 HTML5 播放器,网址如下:https://debug.ivsdemos.com/。在播放器页面填入请求的 URL:https://d3rvqep1efif7v.cloudfront.net/demo-video.mp4?range=0-5242880,并点击播放后,我们可以看到,当我们发起对应的带有 range 查询字符串的请求时,CloudFront 响应了 206 状态码,并且响应的 Content-Range 为 0-5242880:

当内容播放至 7s 左右的时候,视频就停止了继续播放,因为已经到了 5MB 的 Range 上限。

查看对应的 CloudFront 日志,我们可以发现,CloudFront 收到了 Range 范围为 0-5242880 的 Range 请求。

而当我们更改请求 URL 或 Range,比如,修改 URL 为 https://d3rvqep1efif7v.cloudfront.net/demo-video.mp4?range=0-10485760,以试图请求 10MB 内容时,会获得 403 状态码响应而无法播放:

总结

如上我们介绍了利用 CloudFront Functions 实现点播试看的一种方法。由于 CloudFront 可编程的特性,在 CloudFront 上可以实现的功能十分丰富,用户可根据自己的场景进行多样化的拓展和探索。

本篇作者

孟祥智

亚马逊云科技解决方案架构师