Serverless Application Using AWS S3, AWS APIGateway and AWS Lambda — A CLI Method

There are so many GUI examples on the net, here is the CLI way!

Above diagram is what we will be building. AWS S3 bucket will be configured to work as a static website. The web page will have an AJAX call to the Lambda function via the APIGateway endpoint.

Let us get started

Create A Lambda Function

Policy for the role to be used by the Lambda function

{
"Version": "2012-10-17",
"Statement": [
{
"Effect": "Allow",
"Principal": {
"Service": "lambda.amazonaws.com"
},
"Action": "sts:AssumeRole"
}
]
}

Create a role

aws iam create-role --role-name sree-lambda --assume-role-policy-document file://policy.json

Python function

print "Starting..\n"
def sree_handler(event, context):
kg=event['Kg']
lb=float(kg)*2.20462
ans = str(kg) + " Kgs is " + str(round(lb, 2)) + " lbs\n"
print ans
return ans

Zip the function

zip sree-fn.zip sree_fn.py

Create the Lambda Function

aws lambda create-function --function-name sree-py1 --zip-file fileb://sree-fn.zip --handler sree_fn.sree_handler --runtime python2.7 --role arn:aws:iam::111111111:role/sree-lambda

Test the function

aws lambda invoke --function-name sree-py1 --payload '{"Kg":"100"}' results
{
"StatusCode": 200,
"ExecutedVersion": "$LATEST"
}
cat results
"100 Kgs is 220.46 lbs\n"

Create APIGateway Endpoint

Create REST API

api_id=$(aws apigateway create-rest-api --name 'SreeDemo' --description 'My CLI Demo' --query id --output text);echo $api_idroot_id=$(aws apigateway get-resources --rest-api-id $api_id --query items[0].id --output text)

Create Resource

id=$(aws apigateway create-resource --rest-api-id $api_id  --path-part kg-to-lb \
--parent-id $root_id --query id --output text)

Create a GET method with Query String Parameter

aws apigateway put-method --rest-api-id $api_id --resource-id $root_id \
--http-method GET --authorization-type NONE \
--no-api-key-required \
--request-parameters "{ \"method.request.querystring.kg\": false }"

Add Method Response

aws apigateway put-method-response --rest-api-id $api_id \
--resource-id $root_id --http-method GET \
--status-code 200 --response-models "{\"application/json\": \"Empty\"}"

Get the Lambda Function ARN and Assemble the URI

fn_arn=$(aws lambda get-function --function-name sree-py1 --query Configuration.FunctionArn --output text)uri=arn:aws:apigateway:us-west-2:lambda:path/2015-03-31/functions/$fn_arn/invocations

Add Integration to connect APIGateway and Lambda

Here we also pass the mapping from our query string to JSON needed for Lambda function.

aws apigateway put-integration --rest-api-id $api_id --resource-id $root_id \
--type AWS \
--http-method GET \
--integration-http-method POST \
--uri $uri \
--request-templates "{ \"application/json\": \"{\\n\\\"Kg\\\": \\\"\$input.params('kg')\\\"\\n}\" }"

Add Permission to Lambda to Allow APIGateway Calls

aws lambda add-permission --function-name sree-py1 \
--statement-id apigateway-test-3 --action lambda:InvokeFunction \
--principal apigateway.amazonaws.com \
--source-arn "arn:aws:execute-api:us-west-2:111111111:$api_id/*/GET/"

Add Integration response.

Notice the CORS header being added here. This is required for this endpoint to allow being called by other websites.

aws apigateway put-integration-response --rest-api-id $api_id \
--resource-id $root_id --http-method GET \
--status-code 200 \
--content-handling CONVERT_TO_TEXT \
--response-parameters "{\"method.response.header.Access-Control-Allow-Origin\": \"'*'\"}"
{
"statusCode": "200",
"responseParameters": {
"method.response.header.Access-Control-Allow-Origin": "'*'"
},
"contentHandling": "CONVERT_TO_TEXT"
}

Create a Deployment / Stage

aws apigateway get-stages --rest-api-id $api_id
{
"item": []
}
aws apigateway get-deployments --rest-api-id $api_id
{
"items": []
aws apigateway create-deployment --rest-api-id $api_id --stage-name stage1
{
"id": "snks4p",
"createdDate": 1542957908
}
aws apigateway get-stages --rest-api-id $api_id
{
"item": [
{
"deploymentId": "snks4p",
"stageName": "stage1",
"cacheClusterEnabled": false,
"cacheClusterStatus": "NOT_AVAILABLE",
"methodSettings": {},
"createdDate": 1542957908,
"lastUpdatedDate": 1542957908
}
]
}
aws apigateway get-deployments --rest-api-id $api_id
{
"items": [
{
"id": "snks4p",
"createdDate": 1542957908
}
]
}

Test the APIGateway endpoint

curl https://$api_id.execute-api.us-west-2.amazonaws.com/stageprod?kg=22
"22 Kgs is 48.5 lbs\n"

Create a Serverless Web Application

Make the S3 Bucket

sree_BUCKET_NAME=sree-test-2018
sree_BUCKET_REGION=us-west-2
sree_BUCKET_URL=http://$sree_BUCKET_NAME.s3-website-$sree_BUCKET_REGION.amazonaws.com/
aws s3 mb --region $sree_BUCKET_REGION s3://$sree_BUCKET_NAME

Public Read Bucket Policy

cat > s3-www-policy.json <<EOT
{
"Version":"2012-10-17",
"Statement":[{
"Sid":"PublicReadForGetBucketObjects",
"Effect":"Allow",
"Principal": "*",
"Action":["s3:GetObject"],
"Resource":["arn:aws:s3:::$sree_BUCKET_NAME/*"
]
}
]
}
EOT
aws s3api put-bucket-policy --bucket $sree_BUCKET_NAME --policy file://s3-www-policy.json

Website Options

cat > s3.www.json <<EOT
{
"IndexDocument": {
"Suffix": "index.html"
},
"ErrorDocument": {
"Key": "404.html"
},
"RoutingRules": [
{
"Redirect": {
"ReplaceKeyWith": "index.html"
},
"Condition": {
"KeyPrefixEquals": "/"
}
}
]
}
EOT
aws s3api put-bucket-website --bucket $sree_BUCKET_NAME --website-configuration file://s3.www.json

Index page

<!DOCTYPE html>
<html lang="en">
<head>
<meta charset="utf-8">
<title>Serverless Website</title>
<meta http-equiv="X-UA-Compatible" content="IE=edge">
<meta name="viewport" content="width=device-width, initial-scale=1">
<link href="https://cdnjs.cloudflare.com/ajax/libs/twitter-bootstrap/3.3.6/css/bootstrap.min.css" rel="stylesheet">
<script src="https://ajax.googleapis.com/ajax/libs/jquery/1.12.4/jquery.min.js"></script>
<!--[if lt IE 9]>
<script src="https://cdnjs.cloudflare.com/ajax/libs/html5shiv/3.7.3/html5shiv.min.js"></script>
<script src="https://oss.maxcdn.com/respond/1.4.2/respond.min.js"></script>
<![endif]-->
</head>
<body>
<div class="container">
<h1>Serverless Website</h1>
<h2>Kgs to Lbs Converter Using AWS APIGateway and Lambda</h2>
<form class="form-inline">
<div class="form-group mx-sm-3 mb-2">
<label for="inputPassword2" class="sr-only">Password</label>
<input type="text" class="form-control" id="kg" placeholder="1" value="1">
</div>
<button type="submit" onClick="conv();return false;" class="btn btn-primary mb-2">Convert to Lbs</button>
</form>
<h1>
<div id="res"><h1>
</h1>
</h1>
</div>
</div>
<script src="https://cdnjs.cloudflare.com/ajax/libs/twitter-bootstrap/3.3.6/js/bootstrap.min.js"></script>
<script type="text/javascript">
$(document).ready(function(){
});
function conv(){
$("#res").html("<h1>Converting..</h1>")
$("#res").load("https://1tnldoaurb.execute-api.us-west-2.amazonaws.com/stage1?kg="+$("#kg").val(),function() {

var s=$("#res").html();
s=s.replace('"', "")
s=s.replace('"', "")
s=s.replace('\\n', "")
$("#res").html(s);
});
alert( "Lambda function executed succesfully!" );
}
</script>
</body>
</html>

After uploading the above index we are ready to test the S3 website!

Final Test

Thanks for your time. Do follow for more such end to end demos.

Sreeprakash Neelakantan

Written by

AWS Certified DevOps Engineer & Solutions Architect Professional — Docker | Kubernetes | DevOps — Trainer | Running | Swimming | Cycling

Welcome to a place where words matter. On Medium, smart voices and original ideas take center stage - with no ads in sight. Watch
Follow all the topics you care about, and we’ll deliver the best stories for you to your homepage and inbox. Explore
Get unlimited access to the best stories on Medium — and support writers while you’re at it. Just $5/month. Upgrade