Amazon CloudFront
开发员指南 (API 版本 2016-09-29)

Lambda@Edge 示例函数

有关将 Lambda 函数用于 CloudFront 的示例,请参阅以下各节。

请注意,每个 Lambda@Edge 函数都必须包含 callback 参数,以成功处理请求或返回响应。有关更多信息,请参阅 编写和创建 Lambda@Edge 函数

一般示例

示例:A/B 测试

如果要测试主页的两种不同版本,但是不想创建重定向或更改 URL,则可使用以下示例。此示例会在 CloudFront 接收到请求时设置 Cookie,随机将用户分配至版本 A 或 B,然后将相应的版本返回给查看器。

'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); };

示例:覆盖响应标头

以下示例演示了如何基于其他标头的值来更改响应标头的值:

'use strict'; exports.handler = (event, context, callback) => { 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()] = [ headers[headerNameSrc.toLowerCase()][0], ]; console.log(`Response header "${headerNameDst}" was set to ` + `"${headers[headerNameDst.toLowerCase()][0].value}"`); } callback(null, response); };

生成响应 - 示例

示例:提供静态内容 (生成的响应)

以下示例演示了如何使用 Lambda 函数来提供静态网站内容,这样可减少源服务器上的负载,并减少总体延迟。

注意

您可以针对查看器请求和源请求事件生成 HTTP 响应。有关更多信息,请参阅 在请求触发器中生成 HTTP 响应。您也可以替换源和查看器响应事件中的 HTTP 响应。有关更多信息,请参阅 更新源响应触发器中的 HTTP 响应

'use strict'; let 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' }], 'content-encoding': [{ key: 'Content-Encoding', value: 'UTF-8' }], }, body: content, }; callback(null, response); };

示例:以 Gzip 压缩内容 (生成的响应) 的形式提供静态网站内容

此函数演示了如何使用 Lambda 函数以 gzip 压缩内容格式来提供静态网站内容,这样可减少源服务器上的负载,并减少总体延迟。

您可以针对查看器请求和源请求事件生成 HTTP 响应。有关更多信息,请参阅 在请求触发器中生成 HTTP 响应

'use strict'; const zlib = require('zlib'); let 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 a gzip compressed content HTML body. */ const buffer = zlib.gzipSync(content); const base64EncodedBody = buffer.toString('base64'); var response = { headers: { 'content-type': [{key:'Content-Type', value: 'text/html; charset=utf-8'}], 'content-encoding' : [{key:'Content-Encoding', value: 'gzip'}] }, body: base64EncodedBody, bodyEncoding: 'base64', status: '200', statusDescription: "OK" } callback(null, response); };

示例:生成 HTTP 重定向 (生成的响应)

以下示例演示了如何生成 HTTP 重定向。

注意

您可以针对查看器请求和源请求事件生成 HTTP 响应。有关更多信息,请参阅 在请求触发器中生成 HTTP 响应

'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: 'http://docs.aws.amazon.com/lambda/latest/dg/lambda-edge.html', }], }, }; callback(null, response); };

使用查询字符串 - 示例

示例:基于查询字符串参数添加标头

下面的示例演示如何获取查询字符串参数的键-值对,然后根据这些值添加标头。

'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); } ;

示例:标准化查询字符串参数以提高缓存命中率

下面的示例演示如何在 CloudFront 将请求转发给您的源之前通过对查询字符串进行以下更改来提高缓存命中率:

  • 按参数名称的字母顺序排列键-值对

  • 将键-值对的大小写更改为小写

有关更多信息,请参阅 基于查询字符串参数缓存内容

'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 a whitelist 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: * http://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); };

示例:将未经身份验证的用户重定向到登录页面

下面的示例演示如何将未输入其凭证的用户重定向到登录页面。

'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); } /* 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: `http://www.example.com/signin?redirect_url=${encodedRedirectUrl}`, }], }, }; callback(null, response); };

按国家/地区或设备类型标头个性化内容 - 示例

示例:将查看器请求重定向到国家/地区特定的 URL

下面的示例演示如何生成包含国家/地区特定的 URL 的 HTTP 重定向响应并将该响应返回给查看器。在您希望提供国家/地区特定的响应时,这非常有用。例如:

  • 如果您有国家/地区特定的子域,例如 us.example.com 和 tw.example.com,则在查看器请求 example.com 时,您可以生成重定向响应。

  • 如果您要流式传输视频,但您在特定国家/地区中无权流式传输内容,则可以将该国家/地区中的用户重定向到说明他们为何无法观看视频的页面。

请注意以下几点:

  • 您必须将您的分配配置为基于 CloudFront-Viewer-Country 标头进行缓存。有关更多信息,请参阅 基于选择的请求标头进行缓存

  • CloudFront 在查看器请求事件之后添加 CloudFront-Viewer-Country 标头。要使用此示例,您必须为源请求事件创建触发器。

'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 * http://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); };

示例:根据设备提供不同版本的对象

下面的示例演示如何根据用户使用的设备的类型 (例如,移动设备或平板电脑) 提供不同版本的对象。请注意以下几点:

  • 您必须将您的分配配置为基于 CloudFront-Is-*-Viewer 标头进行缓存。有关更多信息,请参阅 基于选择的请求标头进行缓存

  • CloudFront 在查看器请求事件之后添加 CloudFront-Is-*-Viewer 标头。要使用此示例,您必须为源请求事件创建触发器。

'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: * http://docs.aws.amazon.com/console/cloudfront/cache-on-selected-headers * http://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); };

基于内容的动态源选择 - 示例

示例:使用源请求触发器从自定义源更改为 Amazon S3 源

此函数演示如何根据请求属性,使用源请求触发器将从中提取内容的自定义源更改为 Amazon S3 源。

'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 = 'my-bucket.s3.amazonaws.com'; /* Set S3 origin fields */ request.origin = { s3: { domainName: s3DomainName, region: '', authMethod: 'none', path: '', customHeaders: {} } }; request.headers['host'] = [{ key: 'host', value: s3DomainName}]; } } callback(null, request); };

示例:使用源请求触发器更改 Amazon S3 源区域

此函数演示如何根据请求属性,使用源请求触发器更改从中提取内容的 Amazon S3 源。

在本示例中,我们使用 CloudFront-Viewer-Country 标头的值将 S3 存储桶域名更新为更接近查看器的区域中的存储桶。这在多种情况下非常有用:

  • 当指定的区域接近查看器所在的国家/地区时,这可以减少延迟。

  • 通过确保由与发起请求所在位置的相同国家/地区的源提供数据,实现数据主权。

要使用本示例,您必须执行以下操作:

  • 将您的分配配置为基于 CloudFront-Viewer-Country 标头进行缓存。有关更多信息,请参阅 基于选择的请求标头进行缓存

  • 在源请求事件中为此函数创建一个触发器。CloudFront 在查看器请求事件后添加了 CloudFront-Viewer-Country 标头,因此,要使用本示例,您必须确保函数对源请求执行。

'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 * http://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 = `my-bucket-in-${region}.s3.amazonaws.com`; request.origin.s3.domainName = domainName; request.headers['host'] = [{ key: 'host', value: domainName }]; } } callback(null, request); };

示例:使用源请求触发器从 Amazon S3 源更改为自定义源

此函数演示如何根据请求属性,使用源请求触发器更改从中提取内容的自定义源。

'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); };

示例:使用源请求触发器将流量从一个 Amazon S3 存储桶逐步转移到另一个

此函数演示如何以可控的方式将流量从一个 Amazon S3 存储桶逐步转移到另一个。

'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); };

示例:使用源请求触发器根据国家/地区标头更改源域名

此函数演示如何根据 CloudFront-Viewer-Country 标头更改源域名,这样可以从接近查看器所在的国家/地区的源提供内容。

为您的分配实施此功能可能有类似于下面的好处:

  • 在指定的区域接近查看器所在的国家/地区时减少延迟。

  • 确保由请求发起位置所在的同一国家/地区内的源提供数据,从而实现数据主权。

请注意,要启用此功能,您必须配置分配以根据 CloudFront-Viewer-Country 标头进行缓存。有关更多信息,请参阅 基于选择的请求标头进行缓存

'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 === 'UK' || countryCode === 'DE' || countryCode === 'IE' ) { const domainName = 'eu.example.com'; request.origin.custom.domainName = domainName; request.headers['host'] = [{key: 'host', value: domainName}]; } } callback(null, request); };

更新错误状态 - 示例

示例:使用源响应触发器将错误状态代码更新为 200-OK

此函数演示了在下列情况中,如何将响应状态更新为 200 并生成静态正文内容以返回到查看器:

  • 函数在源响应中触发

  • 来自源服务器的响应状态是错误状态代码 (4xx 或 5xx)

'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); };

示例:使用源响应触发器将错误状态代码更新为 302-Found

此函数演示了如何将 HTTP 状态代码更新为 302 以重定向到配置了不同源的其他路径 (缓存行为)。请注意以下几点:

  • 函数在源响应中触发

  • 来自源服务器的响应状态是错误状态代码 (4xx 或 5xx)

'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); };