Lambda Function#

Let create a Lambda function:

  • Execution role
  • Lambda function
  • Inline code
AWSTemplateFormatVersion: 2010-09-09
Description: 'a lambda function'
Resources:
# Create an IAM Role for lambda function
LambdaExecutionRole:
Type: 'AWS::IAM::Role'
Properties:
AssumeRolePolicyDocument:
Version: 2012-10-17
Statement:
- Effect: Allow
Principal:
Service:
- lambda.amazonaws.com
Action:
- 'sts:AssumeRole'
Path: /demo/logaccess/
Policies:
- PolicyName: root
PolicyDocument:
Version: 2012-10-17
Statement:
- Effect: Allow
Action:
- 'logs:CreateLogGroup'
- 'logs:CreateLogStream'
- 'logs:PutLogEvents'
Resource: 'arn:aws:logs:*:*:*'
# Create a Lambda function
LambdaFunction:
Type: AWS::Lambda::Function
Properties:
Handler: 'index.handler'
Role: !GetAtt LambdaExecutionRole.Arn
Code:
ZipFile: |
import json
def handler(event, context):
return {
'statusCode': 200,
'body': json.dumps('Hello from Lambda!')
}
Runtime: 'python3.10'
Timeout: 30
Outputs:
LambdaFunctionArn:
Value: !GetAtt LambdaFunction.Arn
Export:
Name:
Fn::Sub: ${AWS::StackName}-LambdaFunctionArn

API Gateway#

Let create a api gateway and integrate with the lambda function

  • Create a REST API
  • API Gateway execution role
  • Create resource and method
  • Integrate with lambda
AWSTemplateFormatVersion: '2010-09-09'
Description: Create an rest api
Parameters:
BookLambdaStackName:
Type: String
Default: 'cfn-lambda-demo'
ApiName:
Type: String
Default: 'rest-api'
Resources:
# Create an IAM Role for api gateway to invoke lambda functions
ApiGatewayLambdaRole:
Type: 'AWS::IAM::Role'
Properties:
AssumeRolePolicyDocument:
Version: 2012-10-17
Statement:
- Effect: Allow
Principal:
Service:
- apigateway.amazonaws.com
Action:
- 'sts:AssumeRole'
Path: /
Policies:
- PolicyName: root
PolicyDocument:
Version: 2012-10-17
Statement:
- Effect: Allow
Action: 'lambda:*'
Resource: '*'
# Create a rest api
RestApi:
Type: AWS::ApiGateway::RestApi
Properties:
Name: !Ref ApiName
Description: 'rest api demo'
EndpointConfiguration:
Types:
- REGIONAL
# Create a resource named book
BookResource:
Type: AWS::ApiGateway::Resource
Properties:
RestApiId: !Ref RestApi
ParentId: !GetAtt RestApi.RootResourceId
PathPart: 'book'
# Create a GET method for the book resource and integrate with a Lambda function
GetBookMethod:
Type: AWS::ApiGateway::Method
Properties:
RestApiId: !Ref RestApi
ResourceId: !Ref BookResource
HttpMethod: GET
AuthorizationType: NONE
MethodResponses:
- StatusCode: 200
ResponseModels:
application/json: Empty
Integration:
Type: AWS
# get role arn from above created role
Credentials: !GetAtt ApiGatewayLambdaRole.Arn
IntegrationResponses:
- StatusCode: 200
IntegrationHttpMethod: POST
Uri: !Sub
- arn:${AWS::Partition}:apigateway:${AWS::Region}:lambda:path/2015-03-31/functions/${lambdaArn}/invocations
- lambdaArn: !ImportValue
Fn::Sub: ${BookLambdaStackName}-LambdaFunctionArn
# Create deployment stage named Prod
Deployment:
Type: AWS::ApiGateway::Deployment
DependsOn:
- GetBookMethod
Properties:
RestApiId: !Ref RestApi
StageName: Prod

Deployment#

aws cloudformation validate-template \
--template-body file://role.yaml
aws cloudformation create-stack \
--stack-name cfn-lambda-demo \
--template-body file://lambda.yaml \
--capabilities CAPABILITY_NAMED_IAM
aws cloudformation update-stack \
--stack-name cfn-lambda-demo \
--template-body file://lambda.yaml \
--capabilities CAPABILITY_NAMED_IAM
aws cloudformation create-stack \
--stack-name cfn-apigw-demo \
--template-body file://apigw.yaml \
--capabilities CAPABILITY_NAMED_IAM
aws cloudformation update-stack \
--stack-name cfn-apigw-demo \
--template-body file://apigw.yaml \
--capabilities CAPABILITY_NAMED_IAM
aws cloudformation delete-stack \
--stack-name cfn-demo-demo

Handle Error#

Let update lambda

AWSTemplateFormatVersion: 2010-09-09
Description: 'a lambda function'
Resources:
# Create an IAM Role for lambda function
LambdaExecutionRole:
Type: 'AWS::IAM::Role'
Properties:
AssumeRolePolicyDocument:
Version: 2012-10-17
Statement:
- Effect: Allow
Principal:
Service:
- lambda.amazonaws.com
Action:
- 'sts:AssumeRole'
Path: /demo/logaccess/
Policies:
- PolicyName: root
PolicyDocument:
Version: 2012-10-17
Statement:
- Effect: Allow
Action:
- 'logs:CreateLogGroup'
- 'logs:CreateLogStream'
- 'logs:PutLogEvents'
Resource: 'arn:aws:logs:*:*:*'
# Create a Lambda function
LambdaFunction:
Type: AWS::Lambda::Function
Properties:
Handler: 'index.handler'
Role: !GetAtt LambdaExecutionRole.Arn
Code:
ZipFile: |
import json
def handler(event, context):
# raise exception to test api code 500
if 1==2:
raise Exception('Malformed input ...')
# return
return {
'statusCode': 200,
'body': json.dumps('Hello from Lambda!')
}
Runtime: 'python3.10'
Timeout: 30
Outputs:
LambdaFunctionArn:
Value: !GetAtt LambdaFunction.Arn
Export:
Name:
Fn::Sub: ${AWS::StackName}-LambdaFunctionArn

Then update api gateway to handle exception from lambda handler.

AWSTemplateFormatVersion: '2010-09-09'
Description: Create an rest api
Parameters:
BookLambdaStackName:
Type: String
Default: 'cfn-lambda-demo'
ApiName:
Type: String
Default: 'rest-api'
Resources:
# Create an IAM Role for api gateway to invoke lambda functions
ApiGatewayLambdaRole:
Type: 'AWS::IAM::Role'
Properties:
AssumeRolePolicyDocument:
Version: 2012-10-17
Statement:
- Effect: Allow
Principal:
Service:
- apigateway.amazonaws.com
Action:
- 'sts:AssumeRole'
Path: /
Policies:
- PolicyName: root
PolicyDocument:
Version: 2012-10-17
Statement:
- Effect: Allow
Action: 'lambda:*'
Resource: '*'
# Create a rest api
RestApi:
Type: AWS::ApiGateway::RestApi
Properties:
Name: !Ref ApiName
Description: 'rest api demo'
EndpointConfiguration:
Types:
- REGIONAL
# Create a resource named book
BookResource:
Type: AWS::ApiGateway::Resource
Properties:
RestApiId: !Ref RestApi
ParentId: !GetAtt RestApi.RootResourceId
PathPart: 'book'
# Enable CORS for this resource
# Create a GET method for the book resource and integrate with a Lambda function
GetBookMethod:
Type: AWS::ApiGateway::Method
Properties:
RestApiId: !Ref RestApi
ResourceId: !Ref BookResource
HttpMethod: GET
AuthorizationType: NONE
MethodResponses:
- StatusCode: 200
ResponseParameters:
method.response.header.Access-Control-Allow-Origin: true
method.response.header.Access-Control-Allow-Methods: true
method.response.header.Access-Control-Allow-Headers: true
ResponseModels:
application/json: Empty
- StatusCode: 500
ResponseParameters:
method.response.header.Access-Control-Allow-Origin: true
method.response.header.Access-Control-Allow-Methods: true
method.response.header.Access-Control-Allow-Headers: true
ResponseModels:
application/json: Empty
Integration:
Type: AWS
# get role arn from above created role
Credentials: !GetAtt ApiGatewayLambdaRole.Arn
IntegrationResponses:
- StatusCode: 200
ResponseParameters:
method.response.header.Access-Control-Allow-Origin: "'*'"
method.response.header.Access-Control-Allow-Methods: "'GET,POST,PUT,DELETE,OPTIONS'"
method.response.header.Access-Control-Allow-Headers: "'Content-Type,X-Amz-Date,Authorization,X-Api-Key,X-Amz-Security-Token'"
- StatusCode: 500
# http status regex
SelectionPattern: 'Malformed.*'
ResponseParameters:
method.response.header.Access-Control-Allow-Origin: "'*'"
method.response.header.Access-Control-Allow-Methods: "'GET,POST,PUT,DELETE,OPTIONS'"
method.response.header.Access-Control-Allow-Headers: "'Content-Type,X-Amz-Date,Authorization,X-Api-Key,X-Amz-Security-Token'"
IntegrationHttpMethod: POST
Uri: !Sub
- arn:${AWS::Partition}:apigateway:${AWS::Region}:lambda:path/2015-03-31/functions/${lambdaArn}/invocations
- lambdaArn: !ImportValue
Fn::Sub: ${BookLambdaStackName}-LambdaFunctionArn
# Create deployment stage named Prod
Deployment:
Type: AWS::ApiGateway::Deployment
DependsOn:
- GetBookMethod
Properties:
RestApiId: !Ref RestApi
StageName: Prod

Enable CORS#

Let update api gateway to enable CORS

AWSTemplateFormatVersion: '2010-09-09'
Description: Create an rest api
Parameters:
BookLambdaStackName:
Type: String
Default: 'cfn-lambda-demo'
ApiName:
Type: String
Default: 'rest-api'
Resources:
# Create an IAM Role for api gateway to invoke lambda functions
ApiGatewayLambdaRole:
Type: 'AWS::IAM::Role'
Properties:
AssumeRolePolicyDocument:
Version: 2012-10-17
Statement:
- Effect: Allow
Principal:
Service:
- apigateway.amazonaws.com
Action:
- 'sts:AssumeRole'
Path: /
Policies:
- PolicyName: root
PolicyDocument:
Version: 2012-10-17
Statement:
- Effect: Allow
Action: 'lambda:*'
Resource: '*'
# Create a rest api
RestApi:
Type: AWS::ApiGateway::RestApi
Properties:
Name: !Ref ApiName
Description: 'rest api demo'
EndpointConfiguration:
Types:
- REGIONAL
# Create a resource named book
BookResource:
Type: AWS::ApiGateway::Resource
Properties:
RestApiId: !Ref RestApi
ParentId: !GetAtt RestApi.RootResourceId
PathPart: 'book'
# Enable CORS for this resource
# Create a GET method for the book resource and integrate with a Lambda function
GetBookMethod:
Type: AWS::ApiGateway::Method
Properties:
RestApiId: !Ref RestApi
ResourceId: !Ref BookResource
HttpMethod: GET
AuthorizationType: NONE
MethodResponses:
- StatusCode: 200
ResponseParameters:
method.response.header.Access-Control-Allow-Origin: true
method.response.header.Access-Control-Allow-Methods: true
method.response.header.Access-Control-Allow-Headers: true
ResponseModels:
application/json: Empty
- StatusCode: 500
ResponseParameters:
method.response.header.Access-Control-Allow-Origin: true
method.response.header.Access-Control-Allow-Methods: true
method.response.header.Access-Control-Allow-Headers: true
ResponseModels:
application/json: Empty
Integration:
Type: AWS
# get role arn from above created role
Credentials: !GetAtt ApiGatewayLambdaRole.Arn
IntegrationResponses:
- StatusCode: 200
ResponseParameters:
method.response.header.Access-Control-Allow-Origin: "'*'"
method.response.header.Access-Control-Allow-Methods: "'GET,POST,PUT,DELETE,OPTIONS'"
method.response.header.Access-Control-Allow-Headers: "'Content-Type,X-Amz-Date,Authorization,X-Api-Key,X-Amz-Security-Token'"
- StatusCode: 500
# http status regex
SelectionPattern: 'Malformed.*'
ResponseParameters:
method.response.header.Access-Control-Allow-Origin: "'*'"
method.response.header.Access-Control-Allow-Methods: "'GET,POST,PUT,DELETE,OPTIONS'"
method.response.header.Access-Control-Allow-Headers: "'Content-Type,X-Amz-Date,Authorization,X-Api-Key,X-Amz-Security-Token'"
IntegrationHttpMethod: POST
Uri: !Sub
- arn:${AWS::Partition}:apigateway:${AWS::Region}:lambda:path/2015-03-31/functions/${lambdaArn}/invocations
- lambdaArn: !ImportValue
Fn::Sub: ${BookLambdaStackName}-LambdaFunctionArn
# Create deployment stage named Prod
Deployment:
Type: AWS::ApiGateway::Deployment
DependsOn:
- GetBookMethod
Properties:
RestApiId: !Ref RestApi
StageName: Prod

Load Test#

Use curl

curl -v https://z7eg72axm4.execute-api.us-west-2.amazonaws.com/Prod/book

Simple frontend to test

<html>
<head>
<title>Test</title>
</head>
<body>
<div>
<h1>Hello</h1>
</div>
</body>
<script>
const callAPI = async () => {
const response = await fetch(
'https://z7eg72axm4.execute-api.us-west-2.amazonaws.com/Prod/book',
{
method: 'GET'
}
)
console.log(response)
console.log(await response.text())
}
callAPI()
</script>
</html>

Load test python script.

#
# artillery quick -n 2100 --count 100 ENDPOINT
import time
import requests
from concurrent.futures import ThreadPoolExecutor
URL = "https://9lbz0mxhk5.execute-api.us-west-2.amazonaws.com/Prod/book"
NO_CONCUR_REQUEST = 20
COUNT = 1
def send_request():
resp = requests.get(
URL,
headers={
"x-api-key": "YWqb31LYW34UxwsTMCv8l7ECOJjkIgLw6o5OFq6W",
},
)
print(resp.text)
def test_concurrent():
with ThreadPoolExecutor(max_workers=NO_CONCUR_REQUEST) as executor:
for k in range(1, NO_CONCUR_REQUEST):
executor.submit(send_request)
while True:
print(f"{NO_CONCUR_REQUEST} requests {COUNT}")
test_concurrent()
time.sleep(1)
COUNT += 1
# send_request()
# for k in range(2):