Skip to content

Commit b3c8732

Browse files
committed
some changes to make this example independent of preexisting external APIs
1 parent eb7e9af commit b3c8732

File tree

7 files changed

+221
-48
lines changed

7 files changed

+221
-48
lines changed

java/http-proxy-apigateway/README.md

Lines changed: 9 additions & 7 deletions
Original file line numberDiff line numberDiff line change
@@ -12,14 +12,15 @@
1212
<!--END STABILITY BANNER-->
1313

1414
This example creates an API Gateway with proxy resources for 2 HTTP backends.
15-
More HTTP backend APIs can be easily added.
1615
This is useful for scenarios when incoming requests must be routed to one or more backend API hosts.
17-
An HTTP proxy integration enables direct interactions between clients and backends without any intervention from API Gateway after the API method is set up.
16+
An HTTP proxy integration enables direct interactions between clients and backends without any intervention from the API Gateway after the API method is set up.
1817

1918
> For more information on using HTTP proxy integrations with the APIGateway check out this [AWS tutorial](https://docs.aws.amazon.com/apigateway/latest/developerguide/api-gateway-create-api-as-simple-proxy-for-http.html).
2019
21-
> For demonstration purposes this CDK example deploys a solution that routes to the [PetsStore API](http://petstore-demo-endpoint.execute-api.com/) (from this [AWS tutorial](https://docs.aws.amazon.com/apigateway/latest/developerguide/api-gateway-create-api-as-simple-proxy-for-http.html)) and to the [OpenTrivia API](https://opentdb.com).
22-
> If you prefer to use your own HTTP backend APIs modify the argument used to call the [`HttpProxyApiGatewayStack`](src/main/java/com/myorg/HttpProxyApiGatewayStack.java) constructor in the [`HttpProxyApiGatewayApp`](src/main/java/com/myorg/HttpProxyApiGatewayApp.java) class.
20+
> For demonstration purposes this CDK example deploys a solution that routes to a couple of test HTTP APIs.
21+
> The 2 test HTTP APIs are implemented using lambdas exposed through function URLs.
22+
> This example can be modified though, if you prefer to use your own HTTP backend APIs.
23+
> To do that you can modify the `createHTTPTestAPIs` method in the [`HttpProxyApiGatewayStack`](src/main/java/com/myorg/HttpProxyApiGatewayStack.java) class to return a list of `ProxyResourceParameters` corresponding to your own resources.
2324
2425
## Build
2526

@@ -37,12 +38,13 @@ This will install the necessary CDK, then this example's dependencies, and then
3738

3839
Run `cdk deploy`.
3940
This will deploy / redeploy the Stack to AWS.
40-
After the CDK deployment is successful, 2 URL examples will be available in the terminal console output:
41+
After the CDK deployment is successful, 2 URL examples will be available in the terminal console:
4142

42-
- One for the `PetStoreProxyEndPointGetRequestExample` stack output
43-
- One for the `OpenTriviaProxyEndPointGetRequestExample` stack output
43+
- One for the `HttpProxyApiGatewayStack.HelloFunctionResourceExample` output
44+
- One for the `HttpProxyApiGatewayStack.ByeFunctionResourceExample` output
4445

4546
At this point, you can copy each of the 2 URLs and paste them in the address bar of a browser to invoke the 2 APIs.
47+
Also note that both URLs have the same host (the DNS of the new API Gateway created).
4648

4749
## Useful commands
4850

java/http-proxy-apigateway/pom.xml

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -9,7 +9,7 @@
99

1010
<properties>
1111
<project.build.sourceEncoding>UTF-8</project.build.sourceEncoding>
12-
<cdk.version>2.167.1</cdk.version>
12+
<cdk.version>2.171.1</cdk.version>
1313
<constructs.version>[10.0.0,11.0.0)</constructs.version>
1414
<junit.version>5.7.1</junit.version>
1515
<maven.compiler.target>22</maven.compiler.target>

java/http-proxy-apigateway/src/main/java/com/myorg/HttpProxyApiGatewayApp.java

Lines changed: 4 additions & 18 deletions
Original file line numberDiff line numberDiff line change
@@ -2,28 +2,14 @@
22

33
import software.amazon.awscdk.App;
44
import software.amazon.awscdk.StackProps;
5-
import software.amazon.awscdk.services.apigatewayv2.HttpMethod;
6-
7-
import java.util.List;
8-
9-
import static com.myorg.HttpProxyApiGatewayStack.*;
105

116
public class HttpProxyApiGatewayApp {
127
public static void main(final String[] args) {
138
var app = new App();
14-
new HttpProxyApiGatewayStack(
15-
app,
16-
"HttpProxyApiGatewayStack",
17-
StackProps.builder()
18-
.stackName("HttpProxyApiGatewayStack")
19-
.build(),
20-
List.of(
21-
// Replace with resources corresponding to your own HTTP backend APIs if you want to.
22-
// Add more resources if you need to.
23-
new ProxyResourceParameters("PetStore", "http://petstore-demo-endpoint.execute-api.com", HttpMethod.ANY.name(), "/petstore/pets?type=fish"),
24-
new ProxyResourceParameters("OpenTrivia", "https://opentdb.com", HttpMethod.ANY.name(), "/api.php?amount=10")
25-
)
26-
);
9+
var stackProps = StackProps.builder()
10+
.stackName("HttpProxyApiGatewayStack")
11+
.build();
12+
new HttpProxyApiGatewayStack(app, "HttpProxyApiGatewayStack", stackProps);
2713
app.synth();
2814
}
2915
}

java/http-proxy-apigateway/src/main/java/com/myorg/HttpProxyApiGatewayStack.java

Lines changed: 55 additions & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -5,11 +5,19 @@
55
import software.amazon.awscdk.Stack;
66
import software.amazon.awscdk.StackProps;
77
import software.amazon.awscdk.services.apigateway.*;
8+
import software.amazon.awscdk.services.lambda.*;
89
import software.constructs.Construct;
910

11+
import java.io.IOException;
12+
import java.nio.file.Files;
13+
import java.nio.file.Path;
14+
import java.util.ArrayList;
1015
import java.util.List;
1116
import java.util.Map;
1217

18+
import static software.amazon.awscdk.services.apigatewayv2.HttpMethod.ANY;
19+
import static software.amazon.awscdk.services.lambda.Runtime.PYTHON_3_12;
20+
1321
public class HttpProxyApiGatewayStack extends Stack {
1422

1523
public record ProxyResourceParameters(String resourceId, String baseUrl, String httpMethod, String exampleRequest) {
@@ -18,15 +26,49 @@ public record ProxyResourceParameters(String resourceId, String baseUrl, String
1826

1927
private final RestApi restApi;
2028

21-
public HttpProxyApiGatewayStack(final Construct scope, final String id, final StackProps props, List<ProxyResourceParameters> proxyResourceParameters) {
29+
public HttpProxyApiGatewayStack(final Construct scope, final String id, final StackProps props) {
2230
super(scope, id, props);
2331
restApi = RestApi.Builder.create(this, "RestApi")
2432
.restApiName("RestApi")
2533
.cloudWatchRole(true)
2634
.cloudWatchRoleRemovalPolicy(RemovalPolicy.DESTROY)
2735
.endpointTypes(List.of(EndpointType.EDGE))
2836
.build();
29-
proxyResourceParameters.forEach(this::createProxyResource);
37+
createHTTPTestAPIs().forEach(this::createProxyResource);
38+
}
39+
40+
private List<ProxyResourceParameters> createHTTPTestAPIs() {
41+
return Map.of(
42+
"HelloFunction", "/hello?from=AWS",
43+
"ByeFunction", "/bye?from=AWS"
44+
).entrySet().stream()
45+
.map(functionEntry -> {
46+
var functionName = functionEntry.getKey();
47+
var parameters = functionEntry.getValue();
48+
var function = Function.Builder.create(this, functionName)
49+
.functionName(functionName)
50+
.code(InlineCode.fromInline(getInlineCode("src/main/resources/lambdas/" + functionName + ".py")))
51+
.handler("index.handler")
52+
.runtime(PYTHON_3_12)
53+
.build();
54+
var lambdaFunctionAlias = Alias.Builder.create(this, functionName + "ProdAlias")
55+
.aliasName("Prod")
56+
.version(function.getCurrentVersion())
57+
.build();
58+
var functionURL = FunctionUrl.Builder.create(this, functionName + "Url")
59+
.function(lambdaFunctionAlias)
60+
.authType(FunctionUrlAuthType.NONE)
61+
.invokeMode(InvokeMode.BUFFERED)
62+
.cors(
63+
FunctionUrlCorsOptions.builder()
64+
.allowedOrigins(List.of("*"))
65+
.allowedMethods(List.of(HttpMethod.GET))
66+
.allowedHeaders(List.of("*"))
67+
.build()
68+
)
69+
.build();
70+
return new ProxyResourceParameters(functionName + "Resource", functionURL.getUrl(), ANY.name(), parameters);
71+
}).collect(ArrayList::new, List::add, List::addAll);
3072
}
3173

3274
private void createProxyResource(ProxyResourceParameters proxyResourceParameters) {
@@ -54,14 +96,22 @@ private void createProxyResource(ProxyResourceParameters proxyResourceParameters
5496
)
5597
)
5698
.build();
57-
var httpIntegration = new HttpIntegration(proxyResourceParameters.baseUrl + "/{proxy}", httpIntegrationProps);
99+
var httpIntegration = new HttpIntegration(proxyResourceParameters.baseUrl + "{proxy}", httpIntegrationProps);
58100
proxyResource.addMethod(proxyResourceParameters.httpMethod, httpIntegration, methodOptions);
59101
var proxyResourceUrl = restApi.urlForPath(proxyResource.getPath());
60-
CfnOutput.Builder.create(this, proxyResourceParameters.resourceId + "ProxyEndPointAnyRequestTemplate")
102+
CfnOutput.Builder.create(this, proxyResourceParameters.resourceId + "ProxyUrl")
61103
.value(proxyResourceUrl)
62104
.build();
63-
CfnOutput.Builder.create(this, proxyResourceParameters.resourceId + "ProxyEndPointGetRequestExample")
105+
CfnOutput.Builder.create(this, proxyResourceParameters.resourceId + "Example")
64106
.value(proxyResourceUrl.replace("/{proxy+}", proxyResourceParameters.exampleRequest))
65107
.build();
66108
}
109+
110+
private String getInlineCode(String path) {
111+
try {
112+
return new String(Files.readAllBytes(Path.of(path)));
113+
} catch (IOException e) {
114+
throw new RuntimeException(e);
115+
}
116+
}
67117
}
Lines changed: 7 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,7 @@
1+
import json
2+
def handler(event, context):
3+
fromValue = event.get('queryStringParameters', {}).get('from', 'Lambda')
4+
return {
5+
'statusCode': 200,
6+
'body': json.dumps('Bye from ' + fromValue + '!')
7+
}
Lines changed: 7 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,7 @@
1+
import json
2+
def handler(event, context):
3+
fromValue = event.get('queryStringParameters', {}).get('from', 'Lambda')
4+
return {
5+
'statusCode': 200,
6+
'body': json.dumps('Hello from ' + fromValue + '!')
7+
}

java/http-proxy-apigateway/src/test/java/com/myorg/HttpProxyApiGatewayTest.java

Lines changed: 138 additions & 17 deletions
Original file line numberDiff line numberDiff line change
@@ -10,12 +10,10 @@
1010
import software.amazon.awscdk.App;
1111
import software.amazon.awscdk.StackProps;
1212
import software.amazon.awscdk.cxapi.CloudFormationStackArtifact;
13-
import software.amazon.awscdk.services.apigatewayv2.HttpMethod;
1413

1514
import java.util.*;
1615
import java.util.stream.Stream;
1716

18-
import static com.myorg.HttpProxyApiGatewayStack.*;
1917
import static com.myorg.TestUtils.*;
2018

2119
public class HttpProxyApiGatewayTest {
@@ -25,17 +23,10 @@ public class HttpProxyApiGatewayTest {
2523
@BeforeAll
2624
public static void setUp() {
2725
var app = App.Builder.create().build();
28-
var httpProxyApiGatewayStack = new HttpProxyApiGatewayStack(
29-
app,
30-
"HttpProxyApiGatewayStack",
31-
StackProps.builder()
32-
.stackName("HttpProxyApiGatewayStack")
33-
.build(),
34-
List.of(
35-
new ProxyResourceParameters("PetStore", "http://petstore-demo-endpoint.execute-api.com", HttpMethod.ANY.name(), "/petstore/pets?type=fish"),
36-
new ProxyResourceParameters("OpenTrivia", "https://opentdb.com", HttpMethod.ANY.name(), "/api.php?amount=10")
37-
)
38-
);
26+
var stackProps = StackProps.builder()
27+
.stackName("HttpProxyApiGatewayStack")
28+
.build();
29+
var httpProxyApiGatewayStack = new HttpProxyApiGatewayStack(app, "HttpProxyApiGatewayStack", stackProps);
3930
Optional.of(app)
4031
.map(App::synth)
4132
.flatMap(
@@ -74,7 +65,7 @@ public void testRestApi() {
7465

7566
@Test
7667
@DisplayName("Test if the expected IAM role for the REST API is present in the resources of the stack.")
77-
public void testIamRole() {
68+
public void testRestAPIIamRole() {
7869
var iamRoleMatchMap = Map.of(
7970
"/Type", "AWS::IAM::Role",
8071
"/Properties/AssumeRolePolicyDocument/Statement/0/Action", "sts:AssumeRole",
@@ -98,7 +89,16 @@ public void testApiGatewayAccount() {
9889
);
9990
var restApiId = findResourceId(stackResourcesMap, restApiMatchMap);
10091
var iamRoleMatchMap = Map.of(
101-
"/Type", "AWS::IAM::Role"
92+
"/Type", "AWS::IAM::Role",
93+
"/Properties/AssumeRolePolicyDocument/Statement/0/Action", "sts:AssumeRole",
94+
"/Properties/AssumeRolePolicyDocument/Statement/0/Effect", "Allow",
95+
"/Properties/AssumeRolePolicyDocument/Statement/0/Principal/Service", "apigateway.amazonaws.com",
96+
"/Properties/AssumeRolePolicyDocument/Version", "2012-10-17",
97+
"/Properties/ManagedPolicyArns/0/Fn::Join/0", "",
98+
"/Properties/ManagedPolicyArns/0/Fn::Join/1/0", "arn:",
99+
"/Properties/ManagedPolicyArns/0/Fn::Join/1/1/Ref", "AWS::Partition",
100+
"/Properties/ManagedPolicyArns/0/Fn::Join/1/2", ":iam::aws:policy/service-role/AmazonAPIGatewayPushToCloudWatchLogs",
101+
"/DeletionPolicy", "Delete"
102102
);
103103
var iamRoleId = findResourceId(stackResourcesMap, iamRoleMatchMap);
104104
var apiGatewayAccountMatchMap = Map.of(
@@ -199,13 +199,134 @@ public void testApiGatewayMethods() {
199199
"/Properties/RequestParameters/method.request.path.proxy", "true",
200200
"/Properties/RestApiId/Ref", restApiId
201201
);
202-
Long expectedPermissionsCount = 2L;
202+
Long expectedApiGatewayMethodsCount = 2L;
203203
Assertions.assertTrue(
204204
Optional.ofNullable(findResources(stackResourcesMap, apiGatewayMethodsMatchMap))
205205
.map(Map::entrySet)
206206
.map(Set::stream)
207207
.map(Stream::count)
208-
.filter(methodsCont -> methodsCont.equals(expectedPermissionsCount))
208+
.filter(methodsCont -> methodsCont.equals(expectedApiGatewayMethodsCount))
209+
.isPresent()
210+
);
211+
}
212+
213+
@Test
214+
@DisplayName("Test if the expected IAM roles for the test lambdas are present in the resources of the stack.")
215+
public void testLambdaFunctionsIAMRoles() {
216+
var lambdaFunctionsIAMRolesMatchMap = Map.of(
217+
"/Type", "AWS::IAM::Role",
218+
"/Properties/AssumeRolePolicyDocument/Statement/0/Action", "sts:AssumeRole",
219+
"/Properties/AssumeRolePolicyDocument/Statement/0/Effect", "Allow",
220+
"/Properties/AssumeRolePolicyDocument/Statement/0/Principal/Service", "lambda.amazonaws.com",
221+
"/Properties/AssumeRolePolicyDocument/Version", "2012-10-17",
222+
"/Properties/ManagedPolicyArns/0/Fn::Join/0", "",
223+
"/Properties/ManagedPolicyArns/0/Fn::Join/1/0", "arn:",
224+
"/Properties/ManagedPolicyArns/0/Fn::Join/1/1/Ref", "AWS::Partition",
225+
"/Properties/ManagedPolicyArns/0/Fn::Join/1/2", ":iam::aws:policy/service-role/AWSLambdaBasicExecutionRole"
226+
);
227+
Long expectedLambdaFunctionsIAMRolesCount = 2L;
228+
Assertions.assertTrue(
229+
Optional.ofNullable(findResources(stackResourcesMap, lambdaFunctionsIAMRolesMatchMap))
230+
.map(Map::entrySet)
231+
.map(Set::stream)
232+
.map(Stream::count)
233+
.filter(methodsCont -> methodsCont.equals(expectedLambdaFunctionsIAMRolesCount))
234+
.isPresent()
235+
);
236+
}
237+
238+
@Test
239+
@DisplayName("Test if the expected lambda functions are present in the resources of the stack.")
240+
public void testLambdaFunctions() {
241+
var lambdaFunctionsMatchMap = Map.of(
242+
"/Type", "AWS::Lambda::Function",
243+
"/Properties/Runtime", "python3.12"
244+
);
245+
Long expectedLambdaFunctionsCount = 2L;
246+
Assertions.assertTrue(
247+
Optional.ofNullable(findResources(stackResourcesMap, lambdaFunctionsMatchMap))
248+
.map(Map::entrySet)
249+
.map(Set::stream)
250+
.map(Stream::count)
251+
.filter(methodsCont -> methodsCont.equals(expectedLambdaFunctionsCount))
252+
.isPresent()
253+
);
254+
}
255+
256+
@Test
257+
@DisplayName("Test if the expected lambda versions are present in the resources of the stack.")
258+
public void testLambdaVersions() {
259+
var lambdaVersionsMatchMap = Map.of(
260+
"/Type", "AWS::Lambda::Version"
261+
);
262+
Long expectedLambdaVersionsCount = 2L;
263+
Assertions.assertTrue(
264+
Optional.ofNullable(findResources(stackResourcesMap, lambdaVersionsMatchMap))
265+
.map(Map::entrySet)
266+
.map(Set::stream)
267+
.map(Stream::count)
268+
.filter(methodsCont -> methodsCont.equals(expectedLambdaVersionsCount))
269+
.isPresent()
270+
);
271+
}
272+
273+
@Test
274+
@DisplayName("Test if the expected lambda aliases are present in the resources of the stack.")
275+
public void testLambdaAliases() {
276+
var lambdaAliasesMatchMap = Map.of(
277+
"/Type", "AWS::Lambda::Alias",
278+
"/Properties/Name", "Prod"
279+
);
280+
Long expectedLambdaAliasesCount = 2L;
281+
Assertions.assertTrue(
282+
Optional.ofNullable(findResources(stackResourcesMap, lambdaAliasesMatchMap))
283+
.map(Map::entrySet)
284+
.map(Set::stream)
285+
.map(Stream::count)
286+
.filter(methodsCont -> methodsCont.equals(expectedLambdaAliasesCount))
287+
.isPresent()
288+
);
289+
}
290+
291+
@Test
292+
@DisplayName("Test if the expected lambda permissions are present in the resources of the stack.")
293+
public void testLambdaPermissions() {
294+
var lambdaPermissionsMatchMap = Map.of(
295+
"/Type", "AWS::Lambda::Permission",
296+
"/Properties/Action", "lambda:InvokeFunctionUrl",
297+
"/Properties/FunctionUrlAuthType", "NONE",
298+
"/Properties/Principal", "*"
299+
);
300+
Long expectedLambdaPermissionsCount = 2L;
301+
Assertions.assertTrue(
302+
Optional.ofNullable(findResources(stackResourcesMap, lambdaPermissionsMatchMap))
303+
.map(Map::entrySet)
304+
.map(Set::stream)
305+
.map(Stream::count)
306+
.filter(methodsCont -> methodsCont.equals(expectedLambdaPermissionsCount))
307+
.isPresent()
308+
);
309+
}
310+
311+
@Test
312+
@DisplayName("Test if the expected lambda function URLs are present in the resources of the stack.")
313+
public void testLambdaFunctionURLs() {
314+
var lambdaFunctionsURLsMatchMap = Map.of(
315+
"/Type", "AWS::Lambda::Url",
316+
"/Properties/AuthType", "NONE",
317+
"/Properties/InvokeMode", "BUFFERED",
318+
"/Properties/Qualifier", "Prod",
319+
"/Properties/Cors/AllowHeaders/0", "*",
320+
"/Properties/Cors/AllowMethods/0", "GET",
321+
"/Properties/Cors/AllowOrigins/0", "*"
322+
);
323+
Long expectedLambdaFunctionsURLsCount = 2L;
324+
Assertions.assertTrue(
325+
Optional.ofNullable(findResources(stackResourcesMap, lambdaFunctionsURLsMatchMap))
326+
.map(Map::entrySet)
327+
.map(Set::stream)
328+
.map(Stream::count)
329+
.filter(methodsCont -> methodsCont.equals(expectedLambdaFunctionsURLsCount))
209330
.isPresent()
210331
);
211332
}

0 commit comments

Comments
 (0)