Lambda@Edge 関数の例
Amazon CloudFront で Lambda 関数を使用するには、以下の例を参照してください。
Lambda@Edge 関数にランタイム Node.js 18 以降を選択すると、index.mjs
ファイルが自動的に作成されます。次のコード例を使用するには、index.mjs
ファイルの名前を index.js
に変更します。
一般的な例
以下の例は、CloudFront で Lambda@Edge を使用する一般的な方法を示しています。
例: A/B テスト
次の例を使用すると、リダイレクトを作成したり URL を変更したりすることなく、2 つの異なるバージョンのイメージをテストできます。この例では、ビューワーリクエスト内の 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
以下の例は、レスポンスヘッダーの値を別のヘッダーの値に基づいて変更する方法を示しています。
- Node.js
-
'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);
};
- Python
-
import json
def lambda_handler(event, context):
response = event["Records"][0]["cf"]["response"]
headers = response["headers"]
headerNameSrc = "X-Amz-Meta-Last-Modified"
headerNameDst = "Last-Modified"
if headers.get(headerNameSrc.lower(), None):
headers[headerNameDst.lower()] = [headers[headerNameSrc.lower()][0]]
print(f"Response header {headerNameDst.lower()} was set to {headers[headerNameSrc.lower()][0]}")
return response
レスポンスを生成する - 例
以下の例は、Lambda@Edge を使用してレスポンスを生成する方法を示しています。
例: 静的コンテンツを提供する (生成されたレスポンス)
次の例は、Lambda 関数を使用して静的ウェブサイトコンテンツを提供する方法を示しています。これにより、オリジンサーバーの負荷と全体的なレイテンシーが軽減されます。
- 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 リダイレクトを生成する (生成されたレスポンス)
次の例は、HTTP リダイレクトを生成する方法を示しています。
- 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
クエリ文字列 - 例
以下の例は、クエリ文字列で Lambda@Edge を使用する方法を示しています。
以下の例では、クエリ文字列パラメータのキーと値のペアを取得してから、それらの値に基づいてヘッダーを追加する方法を示します。
- 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
例: キャッシュヒット率を向上させるためにクエリ文字列パラメータを正規化する
次の例では、CloudFront がリクエストをオリジンに転送する前にクエリ文字列に以下の変更を行うことで、キャッシュヒット率を向上させる方法を示します。
詳細については、「クエリ文字列パラメータに基づいてコンテンツをキャッシュする」を参照してください。
- 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
例: 認証されていないユーザーをサインインページにリダイレクトする
次の例では、ユーザーが認証情報を入力していない場合にサインインページにリダイレクトする方法を示します。
- 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
国またはデバイスタイプヘッダー別のコンテンツのパーソナライズ - 例
以下の例は、Lambda@Edge により、ビューワーが使用しているデバイスの場所やタイプに基づいて動作をカスタマイズする方法を示しています。
例: ビューワーリクエストを国に固有の URL にリダイレクトする
次の例では、HTTP リダイレクト応答を国に固有の URL で生成し、ビューワーにレスポンスを返す方法を示します。これは、国ごとに異なる応答を提供する場合に便利です。次に例を示します。
次の点に注意してください。
-
CloudFront-Viewer-Country
ヘッダーに基づいてキャッシュするようにディストリビューションを設定する必要があります。詳細については、「選択されたリクエストヘッダーに基づいたキャッシュ」を参照してください。
-
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
例: デバイスに基づいて異なるバージョンのオブジェクトを供給する
次の例では、ユーザーが使用しているモバイルデバイスまたはタブレットのようなデバイスのタイプに基づいてオブジェクトの異なるバージョンを提供する方法を示します。次の点に注意してください。
-
CloudFront-Is-*-Viewer
ヘッダーに基づいてキャッシュするようにディストリビューションを設定する必要があります。詳細については、「選択されたリクエストヘッダーに基づいたキャッシュ」を参照してください。
-
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
コンテンツベースの動的オリジンの選択 - 例
以下の例は、Lambda@Edge を使用し、リクエスト内の情報に基づいて複数の異なるオリジンにルーティングする方法を示しています。
例: オリジンリクエストトリガーを使用してカスタムオリジンを Amazon S3 オリジンに変更する
この関数では、origin-request トリガーを使用して、リクエストのプロパティに基づいて、カスタムオリジンから、コンテンツがフェッチされる 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 = '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);
};
- 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 = '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}]
return request
例: オリジンリクエストトリガーを使用して Amazon S3 オリジンのリージョンを変更する
この関数では、origin-request トリガーを使用して、リクエストのプロパティに基づいて、コンテンツがフェッチされる Amazon S3 オリジンを変更する方法を示しています。
この例では、CloudFront-Viewer-Country
ヘッダーの値を使用して、S3 バケットのドメイン名を、ビューワーに近いリージョンのバケットに更新します。これは、以下のように役立ちます。
この例を使用するには、以下を実行する必要があります。
-
CloudFront-Viewer-Country
ヘッダーに基づいてキャッシュするようにディストリビューションを設定します。詳細については、「選択されたリクエストヘッダーに基づいたキャッシュ」を参照してください。
-
オリジンリクエストイベントでこの関数のトリガーを作成します。CloudFront はビューワーリクエストイベントの後に CloudFront-Viewer-Country
ヘッダーを追加するため、この例を使用するには、オリジンリクエストに対して関数が実行されることを確認する必要があります。
- 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 = `my-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 = 'my-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 オリジンからカスタムオリジンに変更する
この関数では、origin-request トリガーを使用して、リクエストのプロパティに基づいて、コンテンツがフェッチされるカスタムオリジンを変更する方法を示しています。
- 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 バケットから別のバケットにトラフィックを徐々に転送する
この関数では、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
この関数では、CloudFront-Viewer-Country
ヘッダーに基づいてオリジンのドメイン名を変更する方法を示しています。これにより、コンテンツはビューワーの国に近いオリジンから配信されます。
ディストリビューションに対してこの機能を実装すると、次のような利点があります。
この機能を有効にするには、CloudFront-Viewer-Country
ヘッダーに基づいてキャッシュするようにディストリビューションを設定する必要があります。詳細については、「選択されたリクエストヘッダーに基づいたキャッシュ」を参照してください。
- 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
エラーステータスを更新する - 例
以下の例は、Lambda@Edge を使用して、ユーザーに返すエラーステータスを変更する方法を示しています。
例: オリジンレスポンストリガーを使用してエラーステータスコードを 200 に更新する
この関数では、レスポンスステータスを 200 に更新し、以下のシナリオでビューワーに返す静的な本文コンテンツを生成する方法を示しています。
- 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 に更新する
この関数では、HTTP ステータスコードを 302 に更新して、異なるオリジンを設定した別のパス (キャッシュ動作) にリダイレクトする方法を示しています。次の点に注意してください。
- 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
リクエストボディにアクセスする - 例
以下の例は、Lambda@Edge を使用して POST リクエストを操作する方法を示しています。
これらの例を使用するには、ディストリビューションの Lambda 関数の関連付けで [Include body] (ボディを含める) オプションを有効にする必要があります。デフォルトでは、有効になっていません。
例: リクエストトリガーを使用して HTML フォームを読み込む
この関数では、「お問い合わせ」フォームなど HTML フォーム (ウェブフォーム) によって生成された 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 フォームを変更する
この関数では、HTML フォーム (ウェブフォーム) によって生成された 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)