Java on the AWS cloud using Lambda, Api Gateway and CloudFormation
On a previous post we implemented a java based aws lambda function and deployed it using CloudFront. Since we have our lambda function set up we will integrate it with a 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 API gateway as if it 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 step is to 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"}, "/*"] ]} } }
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 got 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 sourcecode on github.
Reference: | Java on the AWS cloud using Lambda, Api Gateway and CloudFormation from our JCG partner Emmanouil Gkatziouras at the gkatzioura blog. |