Java on AWS Using Lambda, API Gateway, and CloudFormation
In a previous post, we implemented a Java-based AWS Lambda function and deployed it using CloudFormation.
Now that we have our Lambda function set up, we will integrate it with an HTTP endpoint using AWS API Gateway.
Amazon API Gateway is a fully managed service that makes it easy for developers to create, publish, maintain, monitor, and secure APIs at any scale. With a few clicks in the AWS Management Console, you can create an API that acts as a “front door” for applications to access data, business logic, or functionality from your back-end services, such as workloads running on Amazon Elastic Compute Cloud (Amazon EC2), code running on AWS Lambda, or any Web application
For this example, imagine that API Gateway is an HTTP connector.
We will change our original function in order to implement a division.
package com.gkatzioura.deployment.lambda;
import com.amazonaws.services.lambda.runtime.Context;
import com.amazonaws.services.lambda.runtime.RequestHandler;
import java.math.BigDecimal;
import java.util.Map;
import java.util.logging.Logger;
/**
* Created by gkatzioura on 9/10/2016.
*/
public class RequestFunctionHandler implements RequestHandler<Map<String,String>,String> {
private static final String NUMERATOR_KEY = "numerator";
private static final String DENOMINATOR_KEY = "denominator";
private static final Logger LOGGER = Logger.getLogger(RequestFunctionHandler.class.getName());
public String handleRequest(Map <String,String> values, Context context) {
LOGGER.info("Handling request");
if(!values.containsKey(NUMERATOR_KEY)||!values.containsKey(DENOMINATOR_KEY)) {
return "You need both numberator and denominator";
}
try {
BigDecimal numerator = new BigDecimal(values.get(NUMERATOR_KEY));
BigDecimal denominator= new BigDecimal(values.get(DENOMINATOR_KEY));
return numerator.divide(denominator).toString();
} catch (Exception e) {
return "Please provide valid values";
}
}
}
Then, we will change our Lambda code and update it on S3.
aws s3 cp build/distributions/JavaLambdaDeployment.zip s3://lambda-functions/JavaLambdaDeployment.zip
Next, we update our CloudFormation template and add the API Gateway forwarding requests to our Lambda function.
First, we have to declare our REST API.
"AGRA16PAA": {
"Type": "AWS::ApiGateway::RestApi",
"Properties": {"Name": "CalculationApi"}
}
Then, we need to add a REST resource. Inside the DependsOn element, we can see the id of our REST API. Therefore, CloudWatch will create the resource after the REST API has been created.
"AGR2JDQ8": {
"Type": "AWS::ApiGateway::Resource",
"Properties": {
"RestApiId": {"Ref": "AGRA16PAA"},
"ParentId": {
"Fn::GetAtt": ["AGRA16PAA","RootResourceId"]
},
"PathPart": "divide"
},
"DependsOn": [
"AGRA16PAA"
]
}
Another crucial part is to add a permission in order to be able to invoke our Lambda function.
"LPI6K5": {
"Type": "AWS::Lambda::Permission",
"Properties": {
"Action": "lambda:invokeFunction",
"FunctionName": {"Fn::GetAtt": ["LF9MBL", "Arn"]},
"Principal": "apigateway.amazonaws.com",
"SourceArn": {"Fn::Join": ["",
["arn:aws:execute-api:", {"Ref": "AWS::Region"}, ":", {"Ref": "AWS::AccountId"}, ":", {"Ref": "AGRA16PAA"}, "/*"]
]}
}
}
The last step would be to add the API gateway method in order to be able to invoke our Lambda function from the API Gateway. Furthermore, we will add an API Gateway deployment instruction.
"Deployment": {
"Type": "AWS::ApiGateway::Deployment",
"Properties": {
"RestApiId": { "Ref": "AGRA16PAA" },
"Description": "First Deployment",
"StageName": "StagingStage"
},
"DependsOn" : ["AGM25KFD"]
},
"AGM25KFD": {
"Type": "AWS::ApiGateway::Method",
"Properties": {
"AuthorizationType": "NONE",
"HttpMethod": "POST",
"ResourceId": {"Ref": "AGR2JDQ8"},
"RestApiId": {"Ref": "AGRA16PAA"},
"Integration": {
"Type": "AWS",
"IntegrationHttpMethod": "POST",
"IntegrationResponses": [{"StatusCode": 200}],
"Uri": {
"Fn::Join": [
"",
[
"arn:aws:apigateway:",
{"Ref": "AWS::Region"},
":lambda:path/2015-03-31/functions/",
{"Fn::GetAtt": ["LF9MBL", "Arn"]},
"/invocations"
]
]
}
},
"MethodResponses": [{
"StatusCode": 200
}]
}
So, we ended up with our new CloudWatch configuration.
{
"AWSTemplateFormatVersion": "2010-09-09",
"Resources": {
"LF9MBL": {
"Type": "AWS::Lambda::Function",
"Properties": {
"Code": {
"S3Bucket": "lambda-functions",
"S3Key": "JavaLambdaDeployment.zip"
},
"FunctionName": "SimpleRequest",
"Handler": "com.gkatzioura.deployment.lambda.RequestFunctionHandler",
"MemorySize": 128,
"Role": "arn:aws:iam::274402012893:role/lambda_basic_execution",
"Runtime": "java8"
}
},
"Deployment": {
"Type": "AWS::ApiGateway::Deployment",
"Properties": {
"RestApiId": { "Ref": "AGRA16PAA" },
"Description": "First Deployment",
"StageName": "StagingStage"
},
"DependsOn" : ["AGM25KFD"]
},
"AGM25KFD": {
"Type": "AWS::ApiGateway::Method",
"Properties": {
"AuthorizationType": "NONE",
"HttpMethod": "POST",
"ResourceId": {"Ref": "AGR2JDQ8"},
"RestApiId": {"Ref": "AGRA16PAA"},
"Integration": {
"Type": "AWS",
"IntegrationHttpMethod": "POST",
"IntegrationResponses": [{"StatusCode": 200}],
"Uri": {
"Fn::Join": [
"",
[
"arn:aws:apigateway:",
{"Ref": "AWS::Region"},
":lambda:path/2015-03-31/functions/",
{"Fn::GetAtt": ["LF9MBL","Arn"]},
"/invocations"
]
]
}
},
"MethodResponses": [{"StatusCode": 200}]
},
"DependsOn": ["LF9MBL","AGR2JDQ8","LPI6K5"]
},
"AGR2JDQ8": {
"Type": "AWS::ApiGateway::Resource",
"Properties": {
"RestApiId": {"Ref": "AGRA16PAA"},
"ParentId": {
"Fn::GetAtt": ["AGRA16PAA","RootResourceId"]
},
"PathPart": "divide"
},
"DependsOn": ["AGRA16PAA"]
},
"AGRA16PAA": {
"Type": "AWS::ApiGateway::RestApi",
"Properties": {
"Name": "CalculationApi"
}
},
"LPI6K5": {
"Type": "AWS::Lambda::Permission",
"Properties": {
"Action": "lambda:invokeFunction",
"FunctionName": {"Fn::GetAtt": ["LF9MBL", "Arn"]},
"Principal": "apigateway.amazonaws.com",
"SourceArn": {"Fn::Join": ["",
["arn:aws:execute-api:", {"Ref": "AWS::Region"}, ":", {"Ref": "AWS::AccountId"}, ":", {"Ref": "AGRA16PAA"}, "/*"]
]}
}
}
}
}
Last but not least, we have to update our previous CloudFormation stack.
So, we uploaded our latest template:
aws s3 cp cloudformationjavalambda2.template s3://cloudformation-templates/cloudformationjavalambda2.template
And all we have to do is to update our stack:
aws cloudformation update-stack --stack-name JavaLambdaStack --template-url https://s3.amazonaws.com/cloudformation-templates/cloudformationjavalambda2.template
Our stack has just been updated.
We can get to our API Gateway endpoint and try to issue a post.
curl -H "Content-Type: application/json" -X POST -d '{"numerator":1,"denominator":"2"}' https://{you api gateway endpoint}/StagingStage/divide
"0.5"
You can find the source code on GitHub.