Running AWS Lambda Rust locally
In this story we are going to investigate how to run the Lambda locally for debugging purposes. The typical use case for AWS Lambda is depicted in the following picture. A REST client calls an AWS API Gateway endpoint and it invokes an AWS Lambda.
In order to create an AWS Lambda for such an architecture please follow the steps in the Deploying out first AWS Lambda in Rust and in the first step choose Amazon Api Gateway REST Api as shown in the following image.
So how do you test the code locally as if it was called from the API Gateway? Luckily there is a docker base image from AWS with the provided.al2 runtime. This is the runtime we use for our Rust applications.
In the following steps we will present an example on how to run an AWS Lambda locally (tested on MacOS).
Step 1: Compile the Lambda Application
We compile our Lambda application (that was created using the cargo-lambda package) with the following:
cargo lambda build --release --arm64
Step 2: Create a docker file to create an image of the function
In the root of your new project create a new file called Dockerfile copy the following code inside it and save it.
FROM public.ecr.aws/lambda/provided:al2-arm64
COPY ./target/lambda/lambda-playground/bootstrap ${LAMBDA_RUNTIME_DIR}
# Set the CMD to your handler (could also be done as a parameter override outside of the Dockerfile)
CMD [ "hello.handler" ]
The above code in essence uses the AWS base image of the Lambda Runtime and copies the binary of our Lambda that we created in Step 1.
Step 3: Create the actual image
You need to navigate to the directory where you created the Dockerfile above and run the following command. This will create a docker image called “example-runtime”.
docker build -t example-runtime --file Dockerfile .
Step 4: Run the example image
The following will start the image you created exposing the Lambda in port 8080 of your localhost
docker run -p 8080:8080 -i --rm example-runtime
Step 5: Calling the Lambda
This is the most dodgy part. Remember the first image of this article that we showed the typical architecture? The REST client (Web/Mobile) call reaches the API-GW, then the API-GW makes some transformations of its own (e.g. adds some headers) and then invokes the Lambda with a rather large request. So what is this request that we should call the HTTP endpoint of the Lambda function locally?
Such an example request can be found here: https://github.com/awslabs/aws-lambda-rust-runtime/blob/main/lambda-events/src/fixtures/example-apigw-request.json
In fact this Repo has Lambda requests for all kinds of Lambda invocations (e.g. for Kafka topic etc.). So, after you run Step 4 above and the Lambda is running locally, if you invoke a POST to the Local function URL: http://127.0.0.1:8080/2015-03-31/functions/function/invocations with the example JSON as body:
{
"resource": "/{proxy+}",
"path": "/hello/world",
"httpMethod": "GET",
"headers": {
"Accept": "*/*",
"Accept-Encoding": "gzip, deflate",
"cache-control": "no-cache",
"CloudFront-Forwarded-Proto": "https",
"CloudFront-Is-Desktop-Viewer": "true",
"CloudFront-Is-Mobile-Viewer": "false",
"CloudFront-Is-SmartTV-Viewer": "false",
"CloudFront-Is-Tablet-Viewer": "false",
"CloudFront-Viewer-Country": "US",
"Content-Type": "application/json",
"headerName": "headerValue",
"Host": "gy415nuibc.execute-api.us-east-1.amazonaws.com",
"Postman-Token": "9f583ef0-ed83-4a38-aef3-eb9ce3f7a57f",
"User-Agent": "PostmanRuntime/2.4.5",
"Via": "1.1 d98420743a69852491bbdea73f7680bd.cloudfront.net (CloudFront)",
"X-Amz-Cf-Id": "pn-PWIJc6thYnZm5P0NMgOUglL1DYtl0gdeJky8tqsg8iS_sgsKD1A==",
"X-Forwarded-For": "54.240.196.186, 54.182.214.83",
"X-Forwarded-Port": "443",
"X-Forwarded-Proto": "https"
},
"multiValueHeaders": {
"Accept": [
"*/*"
],
"Accept-Encoding": [
"gzip, deflate"
],
"cache-control": [
"no-cache"
],
"CloudFront-Forwarded-Proto": [
"https"
],
"CloudFront-Is-Desktop-Viewer": [
"true"
],
"CloudFront-Is-Mobile-Viewer": [
"false"
],
"CloudFront-Is-SmartTV-Viewer": [
"false"
],
"CloudFront-Is-Tablet-Viewer": [
"false"
],
"CloudFront-Viewer-Country": [
"US"
],
"Content-Type": [
"application/json"
],
"headerName": [
"headerValue"
],
"Host": [
"gy415nuibc.execute-api.us-east-1.amazonaws.com"
],
"Postman-Token": [
"9f583ef0-ed83-4a38-aef3-eb9ce3f7a57f"
],
"User-Agent": [
"PostmanRuntime/2.4.5"
],
"Via": [
"1.1 d98420743a69852491bbdea73f7680bd.cloudfront.net (CloudFront)"
],
"X-Amz-Cf-Id": [
"pn-PWIJc6thYnZm5P0NMgOUglL1DYtl0gdeJky8tqsg8iS_sgsKD1A=="
],
"X-Forwarded-For": [
"54.240.196.186, 54.182.214.83"
],
"X-Forwarded-Port": [
"443"
],
"X-Forwarded-Proto": [
"https"
]
},
"queryStringParameters": {
"username": "vrinta"
},
"pathParameters": {
"proxy": "hello/world"
},
"stageVariables": {
"stageVariableName": "stageVariableValue"
},
"requestContext": {
"accountId": "12345678912",
"resourceId": "roq9wj",
"path": "/hello/world",
"stage": "testStage",
"domainName": "gy415nuibc.execute-api.us-east-2.amazonaws.com",
"domainPrefix": "y0ne18dixk",
"requestId": "deef4878-7910-11e6-8f14-25afc3e9ae33",
"protocol": "HTTP/1.1",
"identity": {
"cognitoIdentityPoolId": "theCognitoIdentityPoolId",
"accountId": "theAccountId",
"cognitoIdentityId": "theCognitoIdentityId",
"caller": "theCaller",
"apiKey": "theApiKey",
"apiKeyId": "theApiKeyId",
"accessKey": "ANEXAMPLEOFACCESSKEY",
"sourceIp": "192.168.196.186",
"cognitoAuthenticationType": "theCognitoAuthenticationType",
"cognitoAuthenticationProvider": "theCognitoAuthenticationProvider",
"userArn": "theUserArn",
"userAgent": "PostmanRuntime/2.4.5",
"user": "theUser"
},
"authorizer": {
"principalId": "admin",
"clientId": 1,
"clientName": "Exata"
},
"resourcePath": "/{proxy+}",
"httpMethod": "POST",
"requestTime": "15/May/2020:06:01:09 +0000",
"requestTimeEpoch": 1589522469693,
"apiId": "gy415nuibc"
},
"body": "{\r\n\t\"a\": 1\r\n}"
}
we are able to get a response. You can play around with the queryStringParameters or any other field and read it in the code. In fact in our example Lambda that we return the request params we get a response like the following:
{"statusCode":200,"headers":{"content-type":"application/json"},"multiValueHeaders":{"content-type":["application/json"]},"body":"username=vrinta","isBase64Encoded":false}
Example Code
Working example code for this use case can be found here: https://github.com/vrinta/lambda-playground/tree/aws-lambda-rust-locally