# Running Rust natively in AWS Lambda and testing it locally

Hi, dear reader!

### EDIT: [It is now possible to run Rust in AWS Lambda](https://aws.amazon.com/de/blogs/opensource/rust-runtime-for-aws-lambda/), making this tutorial obsolete. Thank you for reading!

### Today I‘ll show you a way of running [Rust](https://www.rust-lang.org/en-US/) in [AWS Lambda](https://aws.amazon.com/lambda/) without incurring in the performance penalty caused by interacting with JavaScript or Python, while allowing you to test the changes locally.

![](https://miro.medium.com/max/1400/1*wL9FvRCwlO8X0ysJ8348kw.png?q=20)

The goal of this post is to develop a Lambda function that creates a thumbnail given an image, showcasing not only the use of native Rust in AWS Lambda Functions but also the ability to test the program locally.

This tutorial uses [rust-aws-lambda,](https://github.com/srijs/rust-aws-lambda) a crate for writing AWS Lambda functions in pure Rust. For local testing, we’ll be using the Go image from [docker-lambda](https://github.com/lambci/docker-lambda) which allows us to run Go (and Rust) binaries, while simulating the Lambda environment.

> This project [rust-aws-lambda] forgoes embedding and instead leverages lambda’s official Go support. (…) Lambda does not care that the Linux binary it runs is written in Rust rather than Go as long as they behave the same.

# Setup

In order to be able to compile Rust executables, the `x86_64-unknown-linux-musl`target alongside `musl-gcc` must be installed. The target can be installed using rustup:


```sh
$ rustup target add x86_64-unknown-linux-musl
```


Depending on your operating system, your installation steps for `musl-gcc` may vary. On Ubuntu, you can use `$ apt-get install musl`.

That’s all that is need to compile the program. In case you want to test the binary locally, you should also have [Docker](https://www.docker.com/) installed.


# Hello, World!

The crate [rust-aws-lambda](https://github.com/srijs/rust-aws-lambda) provides the abstraction for building simple AWS Lambda functions and functions with API Gateway integration. We’ll be using the second in order to mirror a real-life scenario.

The simplest program can be written as follows:

```rust
extern crate aws_lambda as lambda;

fn main() {
    lambda::gateway::start(|_req /* API Gateway Request */| {
        let res = lambda::gateway::response() // Construct API Gateway Response
            .status(200)
            .body(lambda::gateway::Body::from("Hello, World!"))?; // Convert String to Body

        Ok(res) // Return response
    })
}
```

*   `lambda::gateway::start` takes a closure that will be actually run;
*   `_req` represents the API Gateway request and has fields like the request’s header and body;
*   `lambda::gateway::response` is a response builder that mimics the API Gateway HTTP response.

# Running

Having met the requirements, running the program is done in two steps: compilation and running.

*   Compilation can be made using cargo and the target defined above, as follows: `$ cargo build --target x86_64-unknown-linux-musl`
*   In order to run the compiled binary, we have to use the docker-lambda image: `docker run --rm -v "$PWD":/var/task lambci/lambda:go1.x <path-to-compiled-executable>`

![Expected result](https://miro.medium.com/max/1400/1*ISK9SnqffFrkIEKiqQW9xw.png?q=20)

* * *

# Program Flow

We will now define the full program flow and how our function will work.

1.  The API Gateway receives a JSON where the `body` key maps to the base64-encoded image, as shown by the template:


```json
{
  "body": "<base64-encoded-image>"
}
```

2. The function extracts the body from the request as a `&str` :

```rust
fn main() {
    lambda::gateway::start(|req| {
        let base64_image: &str = req.body().as_str()?;
      
       //...
    })
}
```

3. Decodes the base64 image and loads the result into a `[DynamicImage](https://docs.rs/image/0.20.1/image/enum.DynamicImage.html)`. `DynamicImage` is an enum with the various `ColorType` and pixel size variations (that represents how the content is laid out) and contains useful image transformation functions, which we will be using;

```rust
fn main() {
    lambda::gateway::start(|req| {
        let base64_image: &str = req.body().as_str()?;

        let image: Vec<u8> = base64::decode(base64_image)?; // Decode base64 image
        
        let image: DynamicImage = load_from_memory(&image)?; // Convert image into `DynamicImage`

        // ...
    })
}
```

4. Create a 128x128 thumbnail from the given image:

```rust
fn main() {
    lambda::gateway::start(|req| {
        //... 
        let image = load_from_memory(&image)?;

        let thumbnail = image.thumbnail(128, 128); // Create the 128x128 thumbnail

        // ...
    })
}
```

5. Encode thumbnail as PNG, and then as base64:

```rust
fn encode_png_image(image: DynamicImage) -> Result<Vec<u8>, ImageError> {
    let mut result: Vec<u8> = Vec::new(); // Creates the output `Vec<u8>`

    image.write_to(&mut result, ImageOutputFormat::PNG)?; // Writes `image` to `result` as a PNG

    Ok(result)
}

fn main() {
    lambda::gateway::start(|req| {
        //...
      
        let thumbnail = image.thumbnail(128, 128);

        let encoded_thumbnail = encode_png_image(thumbnail)?; // Encode image as PNG
        let encoded_thumbnail = base64::encode(&encoded_thumbnail); // Encode PNG as base64
      
        //...
    })
}
```

6. Return the response, with the correct status code:

```rust
fn main() {
    lambda::gateway::start(|req| {
        //...

        let encoded_thumbnail = encode_png_image(thumbnail)?;
        let encoded_thumbnail = base64::encode(&encoded_thumbnail);

        let res = lambda::gateway::response() // Create a response
            .status(200) // Set HTTP status code as 200 (Ok)
            .body(lambda::gateway::Body::from(encoded_thumbnail))?; // Convert the encoded_thumbnail to 

        Ok(res)
    })
}
```

This step finalizes the program flow. You can see the final code [here](https://github.com/bernardobelchior/rust-aws-lambda-tutorial/blob/master/src/main.rs). Now, we just need to test our code!

# Testing locally

Testing a production Lambda Function while still developing involves uploading the handler multiple times, which creates a big feedback loop between the writing of the code and the testing of that code. In order to solve that, we are going to use [docker-lambda](https://github.com/lambci/docker-lambda). It provides an environment similar to AWS Lambda Function’s so that we can test the code in our own computers.

![Photo used for testing](https://miro.medium.com/max/1400/1*yVfXcuVL8WQ0wE3FIiiAmw.png?q=20)

First of all, we need to compile our new code, which is done as follows:

`$ cargo build --target x86_64-unknown-linux-musl`

* * *

Then, image must be converted into the format our function is expecting:


```json
{
  "body": "<base64-encoded-image>"
}
```


You can use the tools you want, but I have used the shell command `base64 <image>` to encode the image as base64, and then manually create the JSON structure above. I recommend you save the file into something like `photo.json`.

* * *

Once that is done, we are ready to test our function. We will use the following command:

`$ cat <image.json> | docker run --rm -v "$PWD":/var/task -i -e DOCKER_LAMBDA_USE_STDIN=1 lambci/lambda:go1.x <path-to-compiled-executable>`

The `-i -e DOCKER_LAMBDA_USE_STDIN=1` arguments order the Docker image to receive the API Gateway request from the `stdin`, and using `cat` with the pipe will redirect the JSON we created earlier into the standard input of the Docker container.

![Result of running the above command](https://miro.medium.com/max/1400/1*uw8DQMWawpbhGPxm3CqpxA.png?q=20)

If you run the command above, you’ll see some JSON being spit out of the standard output with a huge `body`. That’s our thumbnail encoded in base64, so we now have save it into an image.

* * *

Again, you can use any tools you want to extract the image, but I’ll use some shell commands so help us out. First, we’ll need to obtain the `body` field of the JSON object, which we can do using `[jq](https://stedolan.github.io/jq/)`. Then, we’ll decode the image, which is in base64, and save it into its own PNG file. The final command looks like this:

```bash
$ cat <image.json> | docker run --rm -v "$PWD":/var/task -i -e DOCKER_LAMBDA_USE_STDIN=1 lambci/lambda:go1.x <path-to-compiled-executable> | jq -r .body | base64 -d > thumbnail.png
```

If you now open `thumbnail.png`, you should see something like this:

![128x128 thumbnail](https://miro.medium.com/max/1400/1*g7jxiRyFsRzBg29MLCDfaA.png?q=20)

# Uploading to AWS

After having confirmed your function works as expected, you can now upload it to AWS Lambda. When creating a new function, don’t forget to set the runtime to “Go 1.x”, otherwise it will not work.

![Lambda Function creation](https://miro.medium.com/max/1400/1*qWICYV7Uzc1bAKIVEbgzgg.png?q=20)

When created, you can upload the handler, which is a zip file with the Rust executable and try it out for yourself! If you compile with the `--release` flag, the function takes around 600ms; using the debug build takes around 15 seconds.

In order to actually test our Lambda Function, we’ll create a test event, following the structure defined earlier. This is what is looks like:

![Function test event](https://miro.medium.com/max/1400/1*LEOkrdu0SzaaoAaisAnXcQ.png?q=20)

After creating the event, you can run it by clicking “Test” on the upper right corner of the screen. Wait a bit until the function finished and you should have the expected result!

![Our function working!](https://miro.medium.com/max/1400/1*aEH0SwwfE9Lr6yzbB2fH4w.png?q=20)

You can check the result body decoding the `body` of the response, using `base64 -d <path-to-base64-encoded-file>` and check the final thumbnail!

Congratulations! You have finished the tutorial! 🎉🎉

The code and image are available in this [GitHub repository](https://github.com/bernardobelchior/rust-aws-lambda-tutorial).

* * *

## Thank you for reading this post! I hope it was useful and I would really appreciate some feedback, especially in case you see something missing, unclear or wrong.

# 👋

Thanks to u/chrisoverzero for letting me know that you can use `jq -r .body` instead of `jq .body | cut -d '"' -f 2`.
