Let’s get straight to the point. In cases where Web applications require specialist tasks to be performed, can those tasks be farmed out to a high-performance stack-based WebAssembly(Wasm) Virtual Machine(VM) via the Web?
What does Function as a Service (FaaS) mean?
The term Function as a Service (FaaS) refers to executing the logic of pre-existing functions via secure HTTP requests, over the Web.
Simply put, you no longer need to run your own servers. Instead, you can use existing functions that other developers have written, or you can create and deploy your own custom server-side functionality as a FaaS.
SecondState’s FaaS Website provides many great examples, demonstrations and tutorials which will help you to take full advantage of FaaS technology. Also this article dives quite deeply into Web programming using FaaS technology. After getting familiar with FaaS, you will find yourself in the territory of being able to create zero-infrastructure applications; which is very exciting!!!
What does zero-infrastructure mean?
In our previous article, “Implementing zero-infrastructure web content in less than 5 minutes”, we showed you how to utilise FaaS technology to create powerful Web applications.
These Web applications can include functionality such as AI inference (image detection), image manipulation (watermarking images, flipping images) and much much more.
file:// URI scheme.
Any server-side logic, that your application requires, will execute seamlessly without you having to build or maintain any of your own server-side infrastructure. No more firewall configuration, network security issues, storage concerns, load balancing configuration, OS patching/updating. Oh! and no more electricity bills.
I hope all of this content inspires you to join this ecosystem and perhaps even share your own custom functions with others.
Limitations of client-side scripting languages
Being able to process binary data is super important. If we can process binary data then we can process image data, binary files, binary network protocols and so forth.
We are going to see if this is something we can solve with FaaS. Reason being, it is not ideal that every developer is required to re-implement byte manipulation code from scratch over and over again. In fact, software design principles like DRY (Don’t Repeat Yourself) encourage code to be written once and reused often. This is where FaaS can help.
Data for the FaaS
Creating ArrayBuffers for testing
If you were building a bytes comparison FaaS call into your App, then the data which your client-side application were processing (searching/matching) would most likely come from some part of your application i.e. images, files etc. However, for simplicity sake, we are just going to create some arbitrary data in this article (so we can get on with explaining our design and implementation).
The following ArrayBuffers are one million bytes, in size.
const buffer_1 = new ArrayBuffer(1000000);
const buffer_2 = new ArrayBuffer(1000000);
We can also go ahead and confirm the length of the ArrayBuffers; how many bytes are present in
Remember, these are just ArrayBuffers, not views; as the following code confirms for us.
The ArrayBuffer object is used to represent a generic, fixed-length raw binary data buffer. It is an array of bytes, often referred to in other languages as a byte array.
A TypedArray object describes an array-like view of an underlying binary data buffer and also provide a mechanism for reading and writing raw binary data in these memory buffers .
If we create a new instance of a typed array (i.e. a
Int8Array), by passing in one of the above ArrayBuffers (to the typed array’s constructor) we will not actually consume too much additional internal memory. This is because typed arrays (when created by accepting an
ArrayBuffer in their constructor) actually just reference the original
ArrayBuffer; and operate on that array buffer address .
const needle = new Uint8Array(buffer_1);
const haystack = new Uint8Array(buffer_2);
The Uint8Array has a fill method which allows us to quickly populate all of the 1000000 individual bytes with a value between
The contents for
view_1 now looks something like this
[111, 111, 111, 111, 111, ... 111, 111, 111]
The contents for
view_2 now looks something like this
[222, 222, 222, 222, 222, ... 222, 222, 222]
Call using ArrayBuffers
needle array. Right?
Instead of the complex encoding/decoding, we can just dedicate the first 10 bytes (of the single ArrayBuffer we are sending accross the Web) to represent the length of the
Given the above, let us consider implementing a very simple encoding and decoding design for this task.
In this example, let’s pretend that the length of the
needle is 1 million bytes.
As you can see the first 10 bytes identify the
needle_length. Seven of the ten available bytes have a single digit character in them. The remaining three are just zeroed out. Then, the
needle (starting from the 11th byte) is exactly one million bytes long. The remaining bytes are the
To implement the above design, first, we need to find the length of the
needle and the
needle_length = needle.length;
haystack_length = needle.length;
Then, we need to ensure that the needle length is not longer than what the first 10 bytes can hold (remembering that each of the 10 bytes only holds a single digit character). This maximum allowable amount
9999999999 is actually the byte based representation of
9.999999999 Gb which, let’s be honest, is more than enough head room for any byte array which is going to be sent over HTTP.
Remember also, that it is only the
needle which is restricted to
9.9Gb. Technically, the
haystack can be any length; as long as the
needle is always smaller than the
haystack. This is where the next step of making sure that the needle’s length is smaller than or equal to the haystack’s length comes in. Oh, and of course the
needle_length can not be less than one.
if(needle_length < 1 || needle_length > 99999999 || needle_length > haystack_length)
needle_length qualifies, all we have to do, is convert the
needle_length (which we calculated above
1000000) into the sequence of 10 bytes, so we end up with the first 10 elements of the new single byte array looking like this.
[0, 0, 0, 1, 0, 0, 0, 0, 0, 0]
Of course the first 10 bytes can represent any number ranging from 1 to 9999999999.
The Rust/Wasm (FaaS) side
Here is an example of the Rust logic which decodes the single array into a needle and the haystack.
We are meshing together the front-end and back-end code.
As testing exceeded needle sizes of 20000, the response times were ~30 seconds. As testing exceeded the needle size of ~50000 the client became unresponsive; the only way to continue was close and reopen the client.
The round trip for these tests stretched from Australia to Central USA and back. These tests would show the entire FaaS round trip to be significantly faster if the client were closer to the particular FaaS endpoint which was used.
Please try out the demo below if you are in the USA (or anywhere in the world for that matter). Perhaps leave a comment to let us know how fast it was for you.
A live demo
You can test this out for yourself at this live demo URL.
Also, if you would like any help with creating your own FaaS please get in touch.
Thanks for reading!