Skip to content

Commit 96f8a2a

Browse files
committed
Update sample app
1 parent 760d336 commit 96f8a2a

File tree

11 files changed

+4323
-226
lines changed

11 files changed

+4323
-226
lines changed

contrib/sample-lambda-app/cdk.ts

Lines changed: 192 additions & 128 deletions
Original file line numberDiff line numberDiff line change
@@ -8,6 +8,7 @@ import {AuthorizationType} from "aws-cdk-lib/aws-apigateway"
88
import * as apiGWv2 from "aws-cdk-lib/aws-apigatewayv2"
99
import {HttpLambdaIntegration, WebSocketLambdaIntegration} from "aws-cdk-lib/aws-apigatewayv2-integrations"
1010
import {LogGroup, RetentionDays} from "aws-cdk-lib/aws-logs"
11+
import {Construct} from "constructs";
1112

1213
const app = new core.App()
1314

@@ -16,136 +17,199 @@ const env = {
1617
account: app.node.tryGetContext('account') || process.env['CDK_DEFAULT_ACCOUNT'] || process.env['AWS_ACCOUNT'],
1718
}
1819

19-
const prefix = "SampleApp"
20-
21-
const stack = new core.Stack(app, "SampleLambdaApp", {env})
22-
23-
const handler = new lambda.DockerImageFunction(stack, `${prefix}Container`, {
24-
code: lambda.DockerImageCode.fromImageAsset("src"),
25-
memorySize: 128,
26-
timeout: core.Duration.minutes(1),
27-
architecture: lambda.Architecture.X86_64,
28-
logGroup: new LogGroup(stack, `${prefix}LogGroup`, {
29-
logGroupName: `/aws/lambda/${prefix}Container`,
30-
retention: RetentionDays.THREE_MONTHS,
31-
removalPolicy: core.RemovalPolicy.DESTROY,
32-
})
33-
})
34-
35-
handler.addToRolePolicy(new iam.PolicyStatement({
36-
effect: Effect.ALLOW,
37-
actions: [
38-
"execute-api:ManageConnections",
39-
],
40-
resources: [
41-
`arn:${core.Aws.PARTITION}:execute-api:*:${core.Aws.ACCOUNT_ID}:*/*/*/*`
42-
],
43-
}))
44-
45-
const integrationV1 = new apiGWv1.LambdaIntegration(handler)
46-
47-
const integrationV2 = new HttpLambdaIntegration("HTTPAPI", handler)
48-
49-
const restAPI = new apiGWv1.RestApi(stack, `${prefix}API-REST`, {
50-
restApiName: "sample-app-rest",
51-
cloudWatchRole: false,
52-
endpointTypes: [apiGWv1.EndpointType.REGIONAL],
53-
minCompressionSize: Size.kibibytes(100),
54-
policy: new iam.PolicyDocument({
55-
statements: [
56-
new iam.PolicyStatement({
57-
effect: Effect.ALLOW,
58-
principals: [
59-
new iam.AnyPrincipal()
60-
],
61-
actions: [
62-
"execute-api:Invoke"
63-
],
64-
resources: [
65-
"execute-api:/*"
66-
],
67-
conditions: {
68-
StringEquals: {
69-
"aws:PrincipalOrgID": [
70-
"o-aq4agy4d07" // dmgw
20+
class SampleLambdaApp extends core.Stack {
21+
constructor(scope: Construct, id: string, props?: core.StackProps) {
22+
super(scope, id, props)
23+
24+
const code = lambda.DockerImageCode.fromImageAsset("src")
25+
const logGroup = new LogGroup(this, "LogGroup", {
26+
logGroupName: `/aws/lambda/${id}`,
27+
retention: RetentionDays.THREE_MONTHS,
28+
removalPolicy: core.RemovalPolicy.DESTROY,
29+
})
30+
31+
const handler = new lambda.DockerImageFunction(this, "Container", {
32+
code,
33+
memorySize: 128,
34+
timeout: core.Duration.minutes(1),
35+
architecture: lambda.Architecture.X86_64,
36+
logGroup,
37+
environment: {
38+
DEBUG_DUMP_PAYLOAD: "1",
39+
WEBSOCKET_RESPONSE_MODE: "return",
40+
},
41+
})
42+
43+
const bufferedUrl = handler.addFunctionUrl({
44+
invokeMode: lambda.InvokeMode.BUFFERED,
45+
})
46+
47+
const streamHandler = new lambda.DockerImageFunction(this, "StreamHandler", {
48+
code,
49+
memorySize: 128,
50+
timeout: core.Duration.minutes(1),
51+
architecture: lambda.Architecture.X86_64,
52+
logGroup,
53+
environment: {
54+
DEBUG_DUMP_PAYLOAD: "1",
55+
LAMBDA_INVOKE_MODE: "response_stream",
56+
WEBSOCKET_RESPONSE_MODE: "post_to_connection",
57+
},
58+
})
59+
60+
const streamUrl = streamHandler.addFunctionUrl({
61+
invokeMode: lambda.InvokeMode.RESPONSE_STREAM,
62+
})
63+
64+
const integrationV1 = new apiGWv1.LambdaIntegration(handler)
65+
66+
const integrationV2 = new HttpLambdaIntegration("HTTPAPI", handler)
67+
68+
const restAPI = new apiGWv1.RestApi(this, "RESTAPI", {
69+
cloudWatchRole: false,
70+
endpointTypes: [apiGWv1.EndpointType.REGIONAL],
71+
minCompressionSize: Size.kibibytes(100),
72+
policy: new iam.PolicyDocument({
73+
statements: [
74+
new iam.PolicyStatement({
75+
effect: Effect.ALLOW,
76+
principals: [
77+
new iam.AnyPrincipal()
7178
],
72-
}
73-
},
79+
actions: [
80+
"execute-api:Invoke"
81+
],
82+
resources: [
83+
"execute-api:/*"
84+
],
85+
conditions: {
86+
StringEquals: {
87+
"aws:PrincipalOrgID": [
88+
"o-aq4agy4d07" // dmgw
89+
],
90+
}
91+
},
92+
})
93+
]
94+
})
95+
})
96+
97+
restAPI.root.addProxy({
98+
anyMethod: true,
99+
defaultIntegration: integrationV1,
100+
defaultMethodOptions: {
101+
authorizationType: AuthorizationType.IAM,
102+
}
103+
})
104+
105+
const deploy = new apiGWv1.Deployment(this, `RESTAPIDeployment`, {
106+
api: restAPI,
107+
})
108+
109+
const stages = ["dev"].map(stageName => {
110+
const stage = new apiGWv1.Stage(this, `RESTAPIStage-${stageName}`, {
111+
stageName,
112+
deployment: deploy,
74113
})
75-
]
76-
})
77-
})
78-
79-
restAPI.root.addProxy({
80-
anyMethod: true,
81-
defaultIntegration: integrationV1,
82-
defaultMethodOptions: {
83-
authorizationType: AuthorizationType.IAM,
114+
handler.addPermission(`FuncPolicyAPIRESTStage-${stageName}`, {
115+
principal: new iam.ServicePrincipal("apigateway.amazonaws.com"),
116+
action: "lambda:InvokeFunction",
117+
sourceArn: restAPI.arnForExecuteApi("*", "/*", stageName)
118+
})
119+
return stage
120+
})
121+
122+
handler.addPermission(`Func-Policy-API-REST`, {
123+
principal: new iam.ServicePrincipal("apigateway.amazonaws.com"),
124+
action: "lambda:InvokeFunction",
125+
sourceArn: restAPI.arnForExecuteApi()
126+
})
127+
128+
const httpAPI = new apiGWv2.HttpApi(this, "HTTPAPI", {
129+
createDefaultStage: true,
130+
})
131+
132+
httpAPI.addRoutes({
133+
path: "/{proxy+}",
134+
methods: [
135+
apiGWv2.HttpMethod.ANY,
136+
],
137+
integration: integrationV2,
138+
})
139+
140+
new apiGWv2.HttpStage(this, "APIStage", {
141+
httpApi: httpAPI,
142+
stageName: "test",
143+
autoDeploy: true,
144+
})
145+
146+
const webSocketApi1 = new apiGWv2.WebSocketApi(this, "WebsocketAPI1", {
147+
routeSelectionExpression: "$request.body.action",
148+
connectRouteOptions: {
149+
integration: new WebSocketLambdaIntegration("WebsocketAPIConnect", handler),
150+
},
151+
disconnectRouteOptions: {
152+
integration: new WebSocketLambdaIntegration("WebsocketAPIDisconnect", handler),
153+
},
154+
defaultRouteOptions: {
155+
integration: new WebSocketLambdaIntegration("WebsocketAPIDefault", handler),
156+
returnResponse: true,
157+
},
158+
})
159+
160+
new apiGWv2.WebSocketStage(this, "WebsocketAPIProd1", {
161+
stageName: "prod",
162+
webSocketApi: webSocketApi1,
163+
autoDeploy: true,
164+
})
165+
166+
const webSocketApi2 = new apiGWv2.WebSocketApi(this, "WebsocketAPI2", {
167+
routeSelectionExpression: "$request.body.action",
168+
connectRouteOptions: {
169+
integration: new WebSocketLambdaIntegration("WebsocketAPIConnect", streamHandler),
170+
},
171+
disconnectRouteOptions: {
172+
integration: new WebSocketLambdaIntegration("WebsocketAPIDisconnect", streamHandler),
173+
},
174+
defaultRouteOptions: {
175+
integration: new WebSocketLambdaIntegration("WebsocketAPIDefault", streamHandler),
176+
},
177+
})
178+
179+
webSocketApi2.grantManageConnections(streamHandler)
180+
181+
new apiGWv2.WebSocketStage(this, "WebsocketAPIProd2", {
182+
stageName: "prod",
183+
webSocketApi: webSocketApi2,
184+
autoDeploy: true,
185+
})
186+
187+
new core.CfnOutput(this, "LambdaURL", {
188+
value: bufferedUrl.url,
189+
})
190+
191+
new core.CfnOutput(this, "LambdaURLStream", {
192+
value: streamUrl.url,
193+
})
194+
195+
new core.CfnOutput(this, "APIGatewayV1URL", {
196+
value: restAPI.url,
197+
})
198+
199+
new core.CfnOutput(this, "APIGatewayV2URL", {
200+
value: httpAPI.apiEndpoint,
201+
})
202+
203+
new core.CfnOutput(this, "WebSocketAPIURLReturn", {
204+
value: webSocketApi1.apiEndpoint,
205+
})
206+
207+
new core.CfnOutput(this, "WebSocketAPIURLStream", {
208+
value: webSocketApi2.apiEndpoint,
209+
})
84210
}
85-
})
86-
87-
const deploy = new apiGWv1.Deployment(stack, `${prefix}-API-REST-Deploy`, {
88-
api: restAPI,
89-
})
90-
91-
const stages = ["dev"].map(stageName => {
92-
const stage = new apiGWv1.Stage(stack, `${prefix}-API-REST-Stage-${stageName}`, {
93-
stageName,
94-
deployment: deploy,
95-
})
96-
handler.addPermission(`${prefix}Func-Policy-API-REST-Stage-${stageName}`, {
97-
principal: new iam.ServicePrincipal("apigateway.amazonaws.com"),
98-
action: "lambda:InvokeFunction",
99-
sourceArn: restAPI.arnForExecuteApi("*", "/*", stageName)
100-
})
101-
return stage
102-
})
103-
104-
handler.addPermission(`${prefix}Func-Policy-API-REST`, {
105-
principal: new iam.ServicePrincipal("apigateway.amazonaws.com"),
106-
action: "lambda:InvokeFunction",
107-
sourceArn: restAPI.arnForExecuteApi()
108-
})
109-
110-
const httpAPI = new apiGWv2.HttpApi(stack, `${prefix}API-HTTP`, {
111-
apiName: "sample-app-http",
112-
createDefaultStage: true,
113-
})
114-
115-
httpAPI.addRoutes({
116-
path: "/{proxy+}",
117-
methods: [
118-
apiGWv2.HttpMethod.ANY,
119-
],
120-
integration: integrationV2,
121-
})
122-
123-
new apiGWv2.HttpStage(stack, `${prefix}APIStage`, {
124-
httpApi: httpAPI,
125-
stageName: "test",
126-
autoDeploy: true,
127-
})
128-
129-
const integrationWS = new WebSocketLambdaIntegration("WebsocketAPI", handler)
130-
131-
const webSocketApi = new apiGWv2.WebSocketApi(stack, `${prefix}API-WS`, {
132-
apiName: "websocket-api",
133-
routeSelectionExpression: "$request.body.action",
134-
connectRouteOptions: {
135-
integration: integrationWS,
136-
},
137-
disconnectRouteOptions: {
138-
integration: integrationWS,
139-
},
140-
defaultRouteOptions: {
141-
integration: integrationWS,
142-
},
143-
})
144-
145-
new apiGWv2.WebSocketStage(stack, `${prefix}API-WS-Prod`, {
146-
stageName: "prod",
147-
webSocketApi,
148-
autoDeploy: true,
149-
})
211+
}
212+
213+
new SampleLambdaApp(app, "SampleLambdaApp", {env})
150214

151215
app.synth()
Lines changed: 13 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,13 @@
1+
import type { Config } from "jest";
2+
3+
const config: Config = {
4+
verbose: true,
5+
preset: "ts-jest",
6+
testEnvironment: "node",
7+
moduleFileExtensions: ["ts", "js"],
8+
testRegex: "/test/.+\.ts$",
9+
// globalSetup: "<rootDir>/tests/jest.globalSetup.ts",
10+
// globalTeardown: "<rootDir>/tests/jest.globalTeardown.ts",
11+
};
12+
13+
export default config;

contrib/sample-lambda-app/package.json

Lines changed: 17 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -2,14 +2,27 @@
22
"name": "lambda-http-adaptor-sample",
33
"license": "Apache-2.0",
44
"scripts": {
5-
"cdk": "cdk"
5+
"cdk": "cdk",
6+
"test": "jest"
67
},
78
"dependencies": {
8-
"@types/node": "^22.8.5",
9-
"aws-cdk": "^2.164.1",
10-
"aws-cdk-lib": "^2.164.1",
9+
"@types/node": "^22.9.0",
10+
"aws-cdk": "^2.165.0",
11+
"aws-cdk-lib": "^2.165.0",
1112
"constructs": "^10.4.2",
1213
"ts-node": "^10.9.2",
1314
"typescript": "^5.6.3"
15+
},
16+
"devDependencies": {
17+
"@aws-crypto/sha256-universal": "^5.2.0",
18+
"@aws-sdk/client-cloudformation": "^3.682.0",
19+
"@aws-sdk/credential-provider-node": "^3.682.0",
20+
"@smithy/protocol-http": "^4.1.5",
21+
"@smithy/signature-v4": "^4.2.1",
22+
"@types/jest": "^29.5.14",
23+
"aws-sigv4-fetch": "^4.0.1",
24+
"jest": "^29.7.0",
25+
"ts-jest": "^29.2.5",
26+
"undici": "^6.20.1"
1427
}
1528
}

0 commit comments

Comments
 (0)