Running AWS Lambda Rust locally

vrinta.com
4 min readJun 15, 2023

--

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.

Typical Architecture AWS API-GW with Lambda integration
Typical Architecture AWS API-GW with Lambda integration

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.

Select Amazon Api Gateway REST Api in the menu of creating a new lambda
Choose these options in the cargo lambda new command

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

--

--

vrinta.com

We are a group of consultants that are interested in various fields: Software Engineering, Financial and Lifehacks. Our plan is to blog about our experiences