Mixing Text and Binary Data in Call Arguments

Sep 28, 2020 • 4 minutes to read

In the previous two articles, we have learnt how to use text, JSON, binary data, and even webhook resources as FaaS function call arguments. But we have also seen the need to mix multiple arguments in a single function call. In this article, we will demonstrate how to do that.

The source code for the custom watermark example in this article is available on Github.

Watermark an image

The Rust function takes two call arguments: a string for the watermark text, and a byte array for an image (in a &[u8]). It adds a watermark text to the image, and then returns the watermarked image in a byte array (in a Vec<u8>).

#[wasm_bindgen]
pub fn watermark (watermark_text: &str, img_buf: &[u8]) -> Vec<u8> {
    // Read the input image
    let mut img = image::load_from_memory(img_buf).unwrap();
    let (w,h) = img.dimensions();
    let scale = Scale {
      x: w as f32 /10.0,
      y: h as f32 /10.0,
    };

    // Prepare fonts for the watermark
    let font = Vec::from(include_bytes!("DejaVuSans.ttf") as &[u8]);
    let font = Font::try_from_vec(font).unwrap();

    // Draw the watermark on the input image
    drawing::draw_text_mut(&mut img, image::Rgba([255u8, 255u8, 255u8, 255u8]), 0+(h/10),h/2, scale, &font, watermark_text);

    // Write and return the watermarked image
    let mut buf = vec![];
    img.write_to(&mut buf, image::ImageOutputFormat::Png).unwrap();
    return buf;
}

Before compiling, make sure that your Cargo.toml file has declared the correct dependencies.

[dependencies]
image = "=0.23.7"
imageproc = "=0.21.0"
rusttype = "=0.9.2"
wasm-bindgen = "=0.2.61"

Build and deploy

You need to use the ssvmup tool to compile the Rust function into WebAssembly bytecode (i.e., the wasm file). Do the following and the compiled wasm file is in the pkg directory.

$ ssvmup build

Upload the wasm file in the pkg folder to the FaaS /api/executables RPC service endpoint. Double check the .wasm file name before you upload.

$ curl --location --request POST 'https://rpc.ssvm.secondstate.io:8081/api/executables' \
--header 'Content-Type: application/octet-stream' \
--header 'SSVM-Description: watermark' \
--data-binary '@pkg/watermark_lib_bg.wasm'

Once the wasm file is deployed, the FaaS returns a wasm_id for the deployment. You will use this wasm_id to access the functions in the deployed wasm file later.

{"wasm_id":148,"wasm_sha256":"0xfb413547a8aba56d0349603a7989e269f3846245e51804932b3e02bc0be4b665","usage_key":"00000000-0000-0000-0000-000000000000","admin_key":"00xxxxxx-xxxx-xxxx-xxxx-4adc960fd2b8"}

Invoke the function via HTTP Post

You can now call the Rust function over the web. The RPC service endpoint is /api/multipart/run/wasm_id/function_name/bytes where the wasm_id is the ID for the wasm file we deployed, and the function_name is the name of the function we want to invoke in the wasm file.

Notice the multipart in the service endpoint URL. Since the function takes multiple input arguments, we need to send in the request as a HTTP multipart request, with each part containing one argument. We are calling the /bytes endpoint because the function returns a Vec<u8> value.

The HTTP request body parts are passed to the function as call arguments. The part name determines the sequence of the arguments. For example, the part named input_1 contains the first call argument, and the part named input_2 contains the second call argument, etc.

We put a string for the watermark text and an image file cat.png into the request body as two parts. The HTTP response body contains the returned bytes from the function. The response body is saved to the tmp.png file by the curl command.

$ curl --location --request POST 'https://rpc.ssvm.secondstate.io:8081/api/multipart/run/148/watermark/bytes' \
--header 'Content-Type: multipart/form-data' \
--form 'input_1=Meow world!' \
--form 'input_2=@test/cat.png' \
--output tmp.png

Open the tmp.png file and you will see the watermark!

Rust FaaS

Use a webhook as function input

An important use case of FaaS is to trigger the function from another service, such as a messaging queue. In this case, the calling service often passes in an URL pointing to the data, instead of submitting the data itself in a HTTP request. With the Second State FaaS, you can also pass in a URL in the input, and the FaaS will fetch the data in the URL and pass it to the function as an argument.

It is important to note that the fetch URL’s multipart name must start with fetch_input. If you just use the regular input name, the URL itself will be passed as a string argument.

$ curl --location --request POST 'https://rpc.ssvm.secondstate.io:8081/api/multipart/run/148/watermark/bytes' \
--header 'Content-Type: multipart/form-data' \
--form 'input_1=Woof world!' \
--form 'fetch_input_2=https://www.secondstate.io/demo/dog.png' \
--output tmp.png

Open the tmp.png file and you will see the watermark on the dog!

Rust FaaS

Web UI

On a static web page, you can use JavaScript to make an AJAX call to this FaaS function. The AJAX function posts a multipart request. The first part is a text string of the watermark message, and the second part is an uploaded image file. Those two parts are passed to the FaaS function as two arguments. The AJAX response is binary data for the watermarked image (bytes). The JavaScript displays the response image on the page.

Source code | Live demo

  var formData = new FormData();
  formData.append('input_1', $('#input_1').val());
  formData.append('input_2', $('#input_2')[0].files[0]);

  $.ajax({
      url: "https://rpc.ssvm.secondstate.io:8081/api/multipart/run/148/watermark/bytes",
      type: "post",
      data : formData,
      contentType: false,
      processData: false,
      xhrFields:{
        responseType: 'blob'
      },
      success: function (data) {
        const img_url = URL.createObjectURL(data);
        $('#wm_img').prop('src', img_url);
      }
  });

What’s next

With the /multipart/ service endpoint and multipart HTTP Post requests, we can create functions that take multiple strings, byte arrays, and fetch_input URLs, as input arguments.

In the next article, we will shift our focus to the return value of the function. In particular, we would like to send the return value to another web service and create a process chain of serverless applications.

RustJavaScriptWebAssemblyNode.jsFaaSRust FaaSServerlesscloud computing
A high-performance, extensible, and hardware optimized WebAssembly Virtual Machine for automotive, cloud, AI, and blockchain applications