Skip to content

Commit d3e5533

Browse files
authored
feat(cloudfront): support Lambda@Edge for behaviors (#9220)
This change adds support for support for defining function associations when adding both default and additional behaviors to the Distribution. Fixes #9108 ---- *By submitting this pull request, I confirm that my contribution is made under the terms of the Apache-2.0 license*
1 parent e6dca52 commit d3e5533

File tree

6 files changed

+222
-25
lines changed

6 files changed

+222
-25
lines changed

packages/@aws-cdk/aws-cloudfront/README.md

Lines changed: 61 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -161,6 +161,67 @@ new cloudfront.Distribution(this, 'myDist', {
161161
});
162162
```
163163

164+
### Lambda@Edge
165+
166+
Lambda@Edge is an extension of AWS Lambda, a compute service that lets you execute functions that customize the content that CloudFront delivers.
167+
You can author Node.js or Python functions in the US East (N. Virginia) region,
168+
and then execute them in AWS locations globally that are closer to the viewer,
169+
without provisioning or managing servers.
170+
Lambda@Edge functions are associated with a specific behavior and event type.
171+
Lambda@Edge can be used rewrite URLs,
172+
alter responses based on headers or cookies,
173+
or authorize requests based on headers or authorization tokens.
174+
175+
The following shows a Lambda@Edge function added to the default behavior and triggered on every request:
176+
177+
```typescript
178+
const myFunc = new lambda.Function(...);
179+
new cloudfront.Distribution(this, 'myDist', {
180+
defaultBehavior: {
181+
origin: new origins.S3Origin(myBucket),
182+
edgeLambdas: [
183+
{
184+
functionVersion: myFunc.currentVersion,
185+
eventType: cloudfront.LambdaEdgeEventType.VIEWER_REQUEST,
186+
}
187+
],
188+
},
189+
});
190+
```
191+
192+
Lambda@Edge functions can also be associated with additional behaviors,
193+
either at Distribution creation time,
194+
or after.
195+
196+
```typescript
197+
// assigning at Distribution creation
198+
const myOrigin = new origins.S3Origin(myBucket);
199+
new cloudfront.Distribution(this, 'myDist', {
200+
defaultBehavior: { origin: myOrigin },
201+
additionalBehaviors: {
202+
'images/*': {
203+
origin: myOrigin,
204+
edgeLambdas: [
205+
{
206+
functionVersion: myFunc.currentVersion,
207+
eventType: cloudfront.LambdaEdgeEventType.ORIGIN_REQUEST,
208+
},
209+
],
210+
},
211+
},
212+
});
213+
214+
// assigning after creation
215+
myDistribution.addBehavior('images/*', myOrigin, {
216+
edgeLambdas: [
217+
{
218+
functionVersion: myFunc.currentVersion,
219+
eventType: cloudfront.LambdaEdgeEventType.VIEWER_RESPONSE,
220+
},
221+
],
222+
});
223+
```
224+
164225
## CloudFrontWebDistribution API - Stable
165226

166227
![cdk-constructs: Stable](https://img.shields.io/badge/cdk--constructs-stable-success.svg?style=for-the-badge)

packages/@aws-cdk/aws-cloudfront/lib/distribution.ts

Lines changed: 52 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -1,4 +1,5 @@
11
import * as acm from '@aws-cdk/aws-certificatemanager';
2+
import * as lambda from '@aws-cdk/aws-lambda';
23
import { Construct, IResource, Lazy, Resource, Stack, Token, Duration } from '@aws-cdk/core';
34
import { CfnDistribution } from './cloudfront.generated';
45
import { Origin } from './origin';
@@ -351,6 +352,49 @@ export interface ErrorResponse {
351352
readonly responsePagePath?: string;
352353
}
353354

355+
/**
356+
* The type of events that a Lambda@Edge function can be invoked in response to.
357+
*/
358+
export enum LambdaEdgeEventType {
359+
/**
360+
* The origin-request specifies the request to the
361+
* origin location (e.g. S3)
362+
*/
363+
ORIGIN_REQUEST = 'origin-request',
364+
365+
/**
366+
* The origin-response specifies the response from the
367+
* origin location (e.g. S3)
368+
*/
369+
ORIGIN_RESPONSE = 'origin-response',
370+
371+
/**
372+
* The viewer-request specifies the incoming request
373+
*/
374+
VIEWER_REQUEST = 'viewer-request',
375+
376+
/**
377+
* The viewer-response specifies the outgoing reponse
378+
*/
379+
VIEWER_RESPONSE = 'viewer-response',
380+
}
381+
382+
/**
383+
* Represents a Lambda function version and event type when using Lambda@Edge.
384+
* The type of the {@link AddBehaviorOptions.edgeLambdas} property.
385+
*/
386+
export interface EdgeLambda {
387+
/**
388+
* The version of the Lambda function that will be invoked.
389+
*
390+
* **Note**: it's not possible to use the '$LATEST' function version for Lambda@Edge!
391+
*/
392+
readonly functionVersion: lambda.IVersion;
393+
394+
/** The type of event in response to which should the function be invoked. */
395+
readonly eventType: LambdaEdgeEventType;
396+
}
397+
354398
/**
355399
* Options for adding a new behavior to a Distribution.
356400
*
@@ -380,6 +424,14 @@ export interface AddBehaviorOptions {
380424
* @default []
381425
*/
382426
readonly forwardQueryStringCacheKeys?: string[];
427+
428+
/**
429+
* The Lambda@Edge functions to invoke before serving the contents.
430+
*
431+
* @default - no Lambda functions will be invoked
432+
* @see https://aws.amazon.com/lambda/edge
433+
*/
434+
readonly edgeLambdas?: EdgeLambda[];
383435
}
384436

385437
/**

packages/@aws-cdk/aws-cloudfront/lib/private/cache-behavior.ts

Lines changed: 11 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -49,7 +49,17 @@ export class CacheBehavior {
4949
queryStringCacheKeys: this.props.forwardQueryStringCacheKeys,
5050
},
5151
viewerProtocolPolicy: ViewerProtocolPolicy.ALLOW_ALL,
52+
lambdaFunctionAssociations: this.props.edgeLambdas
53+
? this.props.edgeLambdas.map(edgeLambda => {
54+
if (edgeLambda.functionVersion.version === '$LATEST') {
55+
throw new Error('$LATEST function version cannot be used for Lambda@Edge');
56+
}
57+
return {
58+
lambdaFunctionArn: edgeLambda.functionVersion.functionArn,
59+
eventType: edgeLambda.eventType.toString(),
60+
};
61+
})
62+
: undefined,
5263
};
5364
}
54-
5565
}

packages/@aws-cdk/aws-cloudfront/lib/web_distribution.ts

Lines changed: 1 addition & 22 deletions
Original file line numberDiff line numberDiff line change
@@ -4,7 +4,7 @@ import * as lambda from '@aws-cdk/aws-lambda';
44
import * as s3 from '@aws-cdk/aws-s3';
55
import * as cdk from '@aws-cdk/core';
66
import { CfnDistribution } from './cloudfront.generated';
7-
import { IDistribution, OriginProtocolPolicy, PriceClass, ViewerProtocolPolicy, SSLMethod, SecurityPolicyProtocol } from './distribution';
7+
import { IDistribution, LambdaEdgeEventType, OriginProtocolPolicy, PriceClass, ViewerProtocolPolicy, SSLMethod, SecurityPolicyProtocol } from './distribution';
88
import { IOriginAccessIdentity } from './origin_access_identity';
99

1010
export enum HttpVersion {
@@ -430,27 +430,6 @@ export interface LambdaFunctionAssociation {
430430
readonly lambdaFunction: lambda.IVersion;
431431
}
432432

433-
export enum LambdaEdgeEventType {
434-
/**
435-
* The origin-request specifies the request to the
436-
* origin location (e.g. S3)
437-
*/
438-
ORIGIN_REQUEST = 'origin-request',
439-
/**
440-
* The origin-response specifies the response from the
441-
* origin location (e.g. S3)
442-
*/
443-
ORIGIN_RESPONSE = 'origin-response',
444-
/**
445-
* The viewer-request specifies the incoming request
446-
*/
447-
VIEWER_REQUEST = 'viewer-request',
448-
/**
449-
* The viewer-response specifies the outgoing reponse
450-
*/
451-
VIEWER_RESPONSE = 'viewer-response',
452-
}
453-
454433
export interface ViewerCertificateOptions {
455434
/**
456435
* How CloudFront should serve HTTPS requests.

packages/@aws-cdk/aws-cloudfront/package.json

Lines changed: 0 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -129,7 +129,6 @@
129129
"docs-public-apis:@aws-cdk/aws-cloudfront.HttpVersion",
130130
"docs-public-apis:@aws-cdk/aws-cloudfront.HttpVersion.HTTP1_1",
131131
"docs-public-apis:@aws-cdk/aws-cloudfront.HttpVersion.HTTP2",
132-
"docs-public-apis:@aws-cdk/aws-cloudfront.LambdaEdgeEventType",
133132
"docs-public-apis:@aws-cdk/aws-cloudfront.OriginSslPolicy",
134133
"docs-public-apis:@aws-cdk/aws-cloudfront.OriginSslPolicy.SSL_V3",
135134
"docs-public-apis:@aws-cdk/aws-cloudfront.OriginSslPolicy.TLS_V1",

packages/@aws-cdk/aws-cloudfront/test/distribution.test.ts

Lines changed: 97 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -1,8 +1,9 @@
11
import '@aws-cdk/assert/jest';
22
import * as acm from '@aws-cdk/aws-certificatemanager';
3+
import * as lambda from '@aws-cdk/aws-lambda';
34
import * as s3 from '@aws-cdk/aws-s3';
45
import { App, Duration, Stack } from '@aws-cdk/core';
5-
import { Distribution, Origin, PriceClass, S3Origin } from '../lib';
6+
import { Distribution, LambdaEdgeEventType, Origin, PriceClass, S3Origin } from '../lib';
67

78
let app: App;
89
let stack: Stack;
@@ -288,6 +289,101 @@ describe('custom error responses', () => {
288289

289290
});
290291

292+
describe('with Lambda@Edge functions', () => {
293+
let lambdaFunction: lambda.Function;
294+
let origin: Origin;
295+
296+
beforeEach(() => {
297+
lambdaFunction = new lambda.Function(stack, 'Function', {
298+
runtime: lambda.Runtime.NODEJS,
299+
code: lambda.Code.fromInline('whatever'),
300+
handler: 'index.handler',
301+
});
302+
303+
origin = defaultS3Origin();
304+
});
305+
306+
test('can add an edge lambdas to the default behavior', () => {
307+
new Distribution(stack, 'MyDist', {
308+
defaultBehavior: {
309+
origin,
310+
edgeLambdas: [
311+
{
312+
functionVersion: lambdaFunction.currentVersion,
313+
eventType: LambdaEdgeEventType.ORIGIN_REQUEST,
314+
},
315+
],
316+
},
317+
});
318+
319+
expect(stack).toHaveResourceLike('AWS::CloudFront::Distribution', {
320+
DistributionConfig: {
321+
DefaultCacheBehavior: {
322+
LambdaFunctionAssociations: [
323+
{
324+
EventType: 'origin-request',
325+
LambdaFunctionARN: {
326+
Ref: 'FunctionCurrentVersion4E2B22619c0305f954e58f25575548280c0a3629',
327+
},
328+
},
329+
],
330+
},
331+
},
332+
});
333+
});
334+
335+
test('can add an edge lambdas to additional behaviors', () => {
336+
new Distribution(stack, 'MyDist', {
337+
defaultBehavior: { origin },
338+
additionalBehaviors: {
339+
'images/*': {
340+
origin,
341+
edgeLambdas: [
342+
{
343+
functionVersion: lambdaFunction.currentVersion,
344+
eventType: LambdaEdgeEventType.VIEWER_REQUEST,
345+
},
346+
],
347+
},
348+
},
349+
});
350+
351+
expect(stack).toHaveResourceLike('AWS::CloudFront::Distribution', {
352+
DistributionConfig: {
353+
CacheBehaviors: [
354+
{
355+
PathPattern: 'images/*',
356+
LambdaFunctionAssociations: [
357+
{
358+
EventType: 'viewer-request',
359+
LambdaFunctionARN: {
360+
Ref: 'FunctionCurrentVersion4E2B22619c0305f954e58f25575548280c0a3629',
361+
},
362+
},
363+
],
364+
},
365+
],
366+
},
367+
});
368+
});
369+
370+
test('fails creation when attempting to add the $LATEST function version as an edge Lambda to the default behavior', () => {
371+
expect(() => {
372+
new Distribution(stack, 'MyDist', {
373+
defaultBehavior: {
374+
origin,
375+
edgeLambdas: [
376+
{
377+
functionVersion: lambdaFunction.latestVersion,
378+
eventType: LambdaEdgeEventType.ORIGIN_RESPONSE,
379+
},
380+
],
381+
},
382+
});
383+
}).toThrow(/\$LATEST function version cannot be used for Lambda@Edge/);
384+
});
385+
});
386+
291387
test('price class is included if provided', () => {
292388
const origin = defaultS3Origin();
293389
new Distribution(stack, 'Dist', {

0 commit comments

Comments
 (0)