DEV Community

Zachary E. Sohovich
Zachary E. Sohovich

Posted on

Creating a placeholder image generator microservice in Rust and now

Over the majority of 2019 I've been practicing Rust. One service that I've fell in love with in general is now.sh. Now.sh 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.

init

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

placeholder-example
 - now.json
 - api
  - Cargo.toml
  - src
    - main.rs

configuring now.sh

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

{
  "functions": {
    "api/**/*.rs": {
      "runtime": "now-rust@1.0.1"
    }
  }
}

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.

...

[dependencies]
image = "0.22.3"
http = "0.1"
now_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 placeholder.rs

Your folder structure should now look like this

placeholder-example
- now.json
- api
  - Cargo.toml
  - placeholder.rs

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

use http::{StatusCode};
use now_lambda::{error::NowError, IntoResponse, Request, Response};
use image::{DynamicImage};
use 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/placeholder.rs file

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

fn handler(req: Request) -> Result<impl IntoResponse, NowError> {
  // Get the path
  let uri = req.uri();
  // Split the path
  let uri_split = uri.path().split("/");
  // Create a Vector containing each parameter
  let parameters = uri_split.collect::<Vec<&str>>();
  // The first parameter [1] (0 is the first "/") and the second parameter [2] are our values for (x, y)
  let img_x = parameters[1].parse::<u32>().unwrap();
  let img_y = parameters[2].parse::<u32>().unwrap();
  // Create our buffer for serving later
  let mut buffer = Vec::new();
  // Create our dynamic image
  let img = DynamicImage::new_rgb8(img_x, img_y);
  // Write to our buffer
  img.write_to(&mut buffer, PNG);
  // Build our response
  let response = Response::builder().status(StatusCode::OK).header("Content-Type", "image/png").body(buffer).expect("Interal Server Error");
  // Server our response
  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:

{
  "functions": {
    "api/**/*.rs": {
      "runtime": "now-rust@1.0.1"
    }
  },
  "routes": [
    { "src": "/([0-9]+)/([0-9]+)", "dest": "/api/image.rs" }
  ]
}

This just tells now.json that when the user requests http://example.com/[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!

Discussion (0)