Skip to content

Commit 36b0b26

Browse files
sobrabkaiz-io
andauthored
added a Java example for an API Gateway with proxy resources to HTTP backend APIs (#1101)
* added a Java example for an API Gateway with proxy resources to HTTP backend APIs * some changes to make this example independent of preexisting external APIs * Update pom.xml Supported release version * Update TestUtils.java Fix _ not being allowed --------- Co-authored-by: Michael Kaiser <[email protected]> Co-authored-by: Michael Kaiser <[email protected]>
1 parent 164c326 commit 36b0b26

File tree

10 files changed

+768
-0
lines changed

10 files changed

+768
-0
lines changed
Lines changed: 13 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,13 @@
1+
.classpath.txt
2+
target
3+
.classpath
4+
.project
5+
.idea
6+
.settings
7+
.vscode
8+
*.iml
9+
10+
# CDK asset staging directory
11+
.cdk.staging
12+
cdk.out
13+
Lines changed: 56 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,56 @@
1+
# HTTP Proxy APIGateway
2+
3+
<!--BEGIN STABILITY BANNER-->
4+
5+
---
6+
![Stability: Stable](https://img.shields.io/badge/stability-Stable-success.svg?style=for-the-badge)
7+
> **This is a stable example. It should successfully build out of the box**
8+
>
9+
> This example is built on Construct Libraries marked "Stable" and does not have any infrastructure prerequisites to build.
10+
---
11+
12+
<!--END STABILITY BANNER-->
13+
14+
This example creates an API Gateway with proxy resources for 2 HTTP backends.
15+
This is useful for scenarios when incoming requests must be routed to one or more backend API hosts.
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.
17+
18+
> 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).
19+
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.
24+
25+
## Build
26+
27+
To build this example, you need to be in this example's root directory. Then run the following:
28+
29+
```bash
30+
npm install -g aws-cdk
31+
npm install
32+
cdk synth
33+
```
34+
35+
This will install the necessary CDK, then this example's dependencies, and then build the CloudFormation template. The resulting CloudFormation template will be in the `cdk.out` directory.
36+
37+
## Deploy
38+
39+
Run `cdk deploy`.
40+
This will deploy / redeploy the Stack to AWS.
41+
After the CDK deployment is successful, 2 URL examples will be available in the terminal console:
42+
43+
- One for the `HttpProxyApiGatewayStack.HelloFunctionResourceExample` output
44+
- One for the `HttpProxyApiGatewayStack.ByeFunctionResourceExample` output
45+
46+
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).
48+
49+
## Useful commands
50+
51+
* `mvn package` compile and run tests
52+
* `cdk ls` list all stacks in the app
53+
* `cdk synth` emits the synthesized CloudFormation template
54+
* `cdk deploy` deploy this stack to your default AWS account/region
55+
* `cdk diff` compare deployed stack with current state
56+
* `cdk docs` open CDK documentation
Lines changed: 76 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,76 @@
1+
{
2+
"app": "mvn -e -q compile exec:java",
3+
"watch": {
4+
"include": [
5+
"**"
6+
],
7+
"exclude": [
8+
"README.md",
9+
"cdk*.json",
10+
"target",
11+
"pom.xml",
12+
"src/test"
13+
]
14+
},
15+
"context": {
16+
"@aws-cdk/aws-lambda:recognizeLayerVersion": true,
17+
"@aws-cdk/core:checkSecretUsage": true,
18+
"@aws-cdk/core:target-partitions": [
19+
"aws",
20+
"aws-cn"
21+
],
22+
"@aws-cdk-containers/ecs-service-extensions:enableDefaultLogDriver": true,
23+
"@aws-cdk/aws-ec2:uniqueImdsv2TemplateName": true,
24+
"@aws-cdk/aws-ecs:arnFormatIncludesClusterName": true,
25+
"@aws-cdk/aws-iam:minimizePolicies": true,
26+
"@aws-cdk/core:validateSnapshotRemovalPolicy": true,
27+
"@aws-cdk/aws-codepipeline:crossAccountKeyAliasStackSafeResourceName": true,
28+
"@aws-cdk/aws-s3:createDefaultLoggingPolicy": true,
29+
"@aws-cdk/aws-sns-subscriptions:restrictSqsDescryption": true,
30+
"@aws-cdk/aws-apigateway:disableCloudWatchRole": true,
31+
"@aws-cdk/core:enablePartitionLiterals": true,
32+
"@aws-cdk/aws-events:eventsTargetQueueSameAccount": true,
33+
"@aws-cdk/aws-ecs:disableExplicitDeploymentControllerForCircuitBreaker": true,
34+
"@aws-cdk/aws-iam:importedRoleStackSafeDefaultPolicyName": true,
35+
"@aws-cdk/aws-s3:serverAccessLogsUseBucketPolicy": true,
36+
"@aws-cdk/aws-route53-patters:useCertificate": true,
37+
"@aws-cdk/customresources:installLatestAwsSdkDefault": false,
38+
"@aws-cdk/aws-rds:databaseProxyUniqueResourceName": true,
39+
"@aws-cdk/aws-codedeploy:removeAlarmsFromDeploymentGroup": true,
40+
"@aws-cdk/aws-apigateway:authorizerChangeDeploymentLogicalId": true,
41+
"@aws-cdk/aws-ec2:launchTemplateDefaultUserData": true,
42+
"@aws-cdk/aws-secretsmanager:useAttachedSecretResourcePolicyForSecretTargetAttachments": true,
43+
"@aws-cdk/aws-redshift:columnId": true,
44+
"@aws-cdk/aws-stepfunctions-tasks:enableEmrServicePolicyV2": true,
45+
"@aws-cdk/aws-ec2:restrictDefaultSecurityGroup": true,
46+
"@aws-cdk/aws-apigateway:requestValidatorUniqueId": true,
47+
"@aws-cdk/aws-kms:aliasNameRef": true,
48+
"@aws-cdk/aws-autoscaling:generateLaunchTemplateInsteadOfLaunchConfig": true,
49+
"@aws-cdk/core:includePrefixInUniqueNameGeneration": true,
50+
"@aws-cdk/aws-efs:denyAnonymousAccess": true,
51+
"@aws-cdk/aws-opensearchservice:enableOpensearchMultiAzWithStandby": true,
52+
"@aws-cdk/aws-lambda-nodejs:useLatestRuntimeVersion": true,
53+
"@aws-cdk/aws-efs:mountTargetOrderInsensitiveLogicalId": true,
54+
"@aws-cdk/aws-rds:auroraClusterChangeScopeOfInstanceParameterGroupWithEachParameters": true,
55+
"@aws-cdk/aws-appsync:useArnForSourceApiAssociationIdentifier": true,
56+
"@aws-cdk/aws-rds:preventRenderingDeprecatedCredentials": true,
57+
"@aws-cdk/aws-codepipeline-actions:useNewDefaultBranchForCodeCommitSource": true,
58+
"@aws-cdk/aws-cloudwatch-actions:changeLambdaPermissionLogicalIdForLambdaAction": true,
59+
"@aws-cdk/aws-codepipeline:crossAccountKeysDefaultValueToFalse": true,
60+
"@aws-cdk/aws-codepipeline:defaultPipelineTypeToV2": true,
61+
"@aws-cdk/aws-kms:reduceCrossAccountRegionPolicyScope": true,
62+
"@aws-cdk/aws-eks:nodegroupNameAttribute": true,
63+
"@aws-cdk/aws-ec2:ebsDefaultGp3Volume": true,
64+
"@aws-cdk/aws-ecs:removeDefaultDeploymentAlarm": true,
65+
"@aws-cdk/custom-resources:logApiResponseDataPropertyTrueDefault": false,
66+
"@aws-cdk/aws-s3:keepNotificationInImportedBucket": false,
67+
"@aws-cdk/aws-ecs:reduceEc2FargateCloudWatchPermissions": true,
68+
"@aws-cdk/aws-dynamodb:resourcePolicyPerReplica": true,
69+
"@aws-cdk/aws-ec2:ec2SumTimeoutEnabled": true,
70+
"@aws-cdk/aws-appsync:appSyncGraphQLAPIScopeLambdaPermission": true,
71+
"@aws-cdk/aws-rds:setCorrectValueForDatabaseInstanceReadReplicaInstanceResourceId": true,
72+
"@aws-cdk/core:cfnIncludeRejectComplexResourceUpdateCreatePolicyIntrinsics": true,
73+
"@aws-cdk/aws-lambda-nodejs:sdkV3ExcludeSmithyPackages": true,
74+
"@aws-cdk/aws-stepfunctions-tasks:fixRunEcsTaskPolicy": true
75+
}
76+
}

java/http-proxy-apigateway/pom.xml

Lines changed: 62 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,62 @@
1+
<?xml version="1.0" encoding="UTF-8"?>
2+
<project xsi:schemaLocation="http://maven.apache.org/POM/4.0.0 http://maven.apache.org/xsd/maven-4.0.0.xsd"
3+
xmlns="http://maven.apache.org/POM/4.0.0" xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance">
4+
<modelVersion>4.0.0</modelVersion>
5+
6+
<groupId>com.myorg</groupId>
7+
<artifactId>http-proxy-apigateway</artifactId>
8+
<version>0.1</version>
9+
10+
<properties>
11+
<project.build.sourceEncoding>UTF-8</project.build.sourceEncoding>
12+
<cdk.version>2.171.1</cdk.version>
13+
<constructs.version>[10.0.0,11.0.0)</constructs.version>
14+
<junit.version>5.7.1</junit.version>
15+
<maven.compiler.target>22</maven.compiler.target>
16+
<maven.compiler.source>22</maven.compiler.source>
17+
</properties>
18+
19+
<build>
20+
<plugins>
21+
<plugin>
22+
<groupId>org.apache.maven.plugins</groupId>
23+
<artifactId>maven-compiler-plugin</artifactId>
24+
<version>3.11.0</version>
25+
<configuration>
26+
<release>17</release>
27+
</configuration>
28+
</plugin>
29+
30+
<plugin>
31+
<groupId>org.codehaus.mojo</groupId>
32+
<artifactId>exec-maven-plugin</artifactId>
33+
<version>3.1.0</version>
34+
<configuration>
35+
<mainClass>com.myorg.HttpProxyApiGatewayApp</mainClass>
36+
</configuration>
37+
</plugin>
38+
</plugins>
39+
</build>
40+
41+
<dependencies>
42+
<!-- AWS Cloud Development Kit -->
43+
<dependency>
44+
<groupId>software.amazon.awscdk</groupId>
45+
<artifactId>aws-cdk-lib</artifactId>
46+
<version>${cdk.version}</version>
47+
</dependency>
48+
49+
<dependency>
50+
<groupId>software.constructs</groupId>
51+
<artifactId>constructs</artifactId>
52+
<version>${constructs.version}</version>
53+
</dependency>
54+
55+
<dependency>
56+
<groupId>org.junit.jupiter</groupId>
57+
<artifactId>junit-jupiter</artifactId>
58+
<version>${junit.version}</version>
59+
<scope>test</scope>
60+
</dependency>
61+
</dependencies>
62+
</project>
Lines changed: 15 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,15 @@
1+
package com.myorg;
2+
3+
import software.amazon.awscdk.App;
4+
import software.amazon.awscdk.StackProps;
5+
6+
public class HttpProxyApiGatewayApp {
7+
public static void main(final String[] args) {
8+
var app = new App();
9+
var stackProps = StackProps.builder()
10+
.stackName("HttpProxyApiGatewayStack")
11+
.build();
12+
new HttpProxyApiGatewayStack(app, "HttpProxyApiGatewayStack", stackProps);
13+
app.synth();
14+
}
15+
}
Lines changed: 117 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,117 @@
1+
package com.myorg;
2+
3+
import software.amazon.awscdk.CfnOutput;
4+
import software.amazon.awscdk.RemovalPolicy;
5+
import software.amazon.awscdk.Stack;
6+
import software.amazon.awscdk.StackProps;
7+
import software.amazon.awscdk.services.apigateway.*;
8+
import software.amazon.awscdk.services.lambda.*;
9+
import software.constructs.Construct;
10+
11+
import java.io.IOException;
12+
import java.nio.file.Files;
13+
import java.nio.file.Path;
14+
import java.util.ArrayList;
15+
import java.util.List;
16+
import java.util.Map;
17+
18+
import static software.amazon.awscdk.services.apigatewayv2.HttpMethod.ANY;
19+
import static software.amazon.awscdk.services.lambda.Runtime.PYTHON_3_12;
20+
21+
public class HttpProxyApiGatewayStack extends Stack {
22+
23+
public record ProxyResourceParameters(String resourceId, String baseUrl, String httpMethod, String exampleRequest) {
24+
25+
}
26+
27+
private final RestApi restApi;
28+
29+
public HttpProxyApiGatewayStack(final Construct scope, final String id, final StackProps props) {
30+
super(scope, id, props);
31+
restApi = RestApi.Builder.create(this, "RestApi")
32+
.restApiName("RestApi")
33+
.cloudWatchRole(true)
34+
.cloudWatchRoleRemovalPolicy(RemovalPolicy.DESTROY)
35+
.endpointTypes(List.of(EndpointType.EDGE))
36+
.build();
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);
72+
}
73+
74+
private void createProxyResource(ProxyResourceParameters proxyResourceParameters) {
75+
var parentProxyResource = restApi.getRoot().addResource(proxyResourceParameters.resourceId);
76+
var proxyResource = ProxyResource.Builder.create(this, proxyResourceParameters.resourceId + "ProxyResource")
77+
.parent(parentProxyResource)
78+
.anyMethod(false)
79+
.build();
80+
var integrationOptions = IntegrationOptions.builder()
81+
.requestParameters(
82+
Map.of(
83+
"integration.request.path.proxy", "method.request.path.proxy"
84+
)
85+
)
86+
.build();
87+
var httpIntegrationProps = HttpIntegrationProps.builder()
88+
.proxy(true)
89+
.httpMethod(proxyResourceParameters.httpMethod)
90+
.options(integrationOptions)
91+
.build();
92+
var methodOptions = MethodOptions.builder()
93+
.requestParameters(
94+
Map.of(
95+
"method.request.path.proxy", true
96+
)
97+
)
98+
.build();
99+
var httpIntegration = new HttpIntegration(proxyResourceParameters.baseUrl + "{proxy}", httpIntegrationProps);
100+
proxyResource.addMethod(proxyResourceParameters.httpMethod, httpIntegration, methodOptions);
101+
var proxyResourceUrl = restApi.urlForPath(proxyResource.getPath());
102+
CfnOutput.Builder.create(this, proxyResourceParameters.resourceId + "ProxyUrl")
103+
.value(proxyResourceUrl)
104+
.build();
105+
CfnOutput.Builder.create(this, proxyResourceParameters.resourceId + "Example")
106+
.value(proxyResourceUrl.replace("/{proxy+}", proxyResourceParameters.exampleRequest))
107+
.build();
108+
}
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+
}
117+
}
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+
}

0 commit comments

Comments
 (0)