Creating a placeholder image generator microservice in Rust and now

Posted on November 28, 2019

Over the majority of 2019 I've been practicing Rust. One service that I've fell in love with in general is allows serverless function deployment, and that includes using community builders so you can use your own custom language.

In this tutorial we'll be utilizing the now-rust for deployment, and image-rs for quickly generating a image to server.


First thing we need to generate is our new project. Normally, we'd directly initialize a cargo project, but this project will have a special file architecture.

Create a new directory mkdir placeholder-example and cd placeholder-example

placeholder-example is going to be our project root. We'll want to create a cargo project within this folder.

cargo new api to create our api project. This is going to be our primary api endpoint.

Next we'll want to create a now.json file in the root directory.

Your folder structure should now look like this

2 - now.json
3 - api
4 - Cargo.toml
5 - src
6 -


Next we're going to want to configure the now.json file to serve our serverless function. The primary piece we're going to need is telling now what builder to use. Make your now.json file look like this

2 "functions": {
3 "api/**/*.rs": {
4 "runtime": "now-rust@1.0.1"
5 }
6 }

This is going to tell now which runtime that any .rs file within our api folder should use the now-rust

Next let's move on to configuring the serverless function itself

adding our dependencies

We're going to want to add our dependencies first. Open api/Cargo.toml and add these dependencies to it.

4image = "0.22.3"
5http = "0.1"
6now_lambda = "0.1"

The image-rs is going to be for serving our generated image. The http is going to be for wrapping our function into an http handler, and the now_lambda is a wrapper that configures that lambda functionality for us

configuring our image handler

Now for the juicy stuff! We finally get to configure the actual handler!

Don't worry if you're new to Rust like I am! It's only a few lines of code!

For this part, we're actually going to delete our src folder. We don't really need any binary or anything, now is going to handle all that for us. So remove the api/src folder and create a new file in the api folder called

Your folder structure should now look like this

2- now.json
3- api
4 - Cargo.toml
5 -

Open api/ and add our dependencies to the top of the file

1use http::{StatusCode};
2use now_lambda::{error::NowError, IntoResponse, Request, Response};
3use image::{DynamicImage};
4use image::ImageOutputFormat::PNG;

For http we're simply utilizing a status code. For now_lambda we're utilizing it's wrapper around http to server our requests, and responses. For image, we're utilizing the DynamicImage enum for actually generating our image, and then we're also utilizing image's ImageOutputFormat to output into a PNG

We need to create a handler function that accepts a Request and outputs a Result. We want to allow our users to supploy a width and a height via the path in the URL, so we'll want to decode those two things from the path in the URL. Once we have our width and height we can generate a new image, convert it into a buffer, then convert it into a PNG, and then server that image in the response.

That's going to look like this in your api/ file

1use http::{StatusCode};
2use now_lambda::{error::NowError, IntoResponse, Request, Response};
3use image::{DynamicImage};
4use image::ImageOutputFormat::PNG;
6fn handler(req: Request) -> Result<impl IntoResponse, NowError> {
7 // Get the path
8 let uri = req.uri();
9 // Split the path
10 let uri_split = uri.path().split("/");
11 // Create a Vector containing each parameter
12 let parameters = uri_split.collect::<Vec<&str>>();
13 // The first parameter [1] (0 is the first "/") and the second parameter [2] are our values for (x, y)
14 let img_x = parameters[1].parse::<u32>().unwrap();
15 let img_y = parameters[2].parse::<u32>().unwrap();
16 // Create our buffer for serving later
17 let mut buffer = Vec::new();
18 // Create our dynamic image
19 let img = DynamicImage::new_rgb8(img_x, img_y);
20 // Write to our buffer
21 img.write_to(&mut buffer, PNG);
22 // Build our response
23 let response = Response::builder().status(StatusCode::OK).header("Content-Type", "image/png").body(buffer).expect("Interal Server Error");
24 // Server our response
25 Ok(response)

Now we have our function! Yay! The last thing we need to do is configure our now.json file to look for a specific path when serving our handler. If we don't have those width and height the function will fail.

So open your now.json file and add these lines:

2 "functions": {
3 "api/**/*.rs": {
4 "runtime": "now-rust@1.0.1"
5 }
6 },
7 "routes": [
8 { "src": "/([0-9]+)/([0-9]+)", "dest": "/api/" }
9 ]

This just tells now.json that when the user requests[digit]/[digit] to server our function.

Last but not least, deploy our new serverless function via the now cli by running now in your terminal

Once now deploys your serverless function you should have a working placeholder image generator!