Serverless Computing using AWS Lambda
Serverless computing, although available in the public cloud since 2014, has been getting some attention lately, and so I decided to investigate. All the big cloud providers are offering some form of serverless computing which, despite the name, doesn't mean that there aren't servers involved somewhere (e.g. Google's Cloud Functions and Amazon's Lambda Functions.)
The benefit of using these services is that there is no need to worry about either the cost of running or maintaining any servers that might be involved in the implementation. In the case of Lambda functions, there is a very small charge per invocation of the function, and no charge at all when the functions are not running. Serverless databases are typically billed by the amount of data stored and not for how long they are running. These properties of serverless computing make it ideal for building applications that need to have a high availability but are not in constant use.
I quite often use web based services to do simple calculations I need at work, e.g. a simple unix time converter. Sometimes, though, I can't find a converter for a task that would have a smaller audience. For example, we use a four byte integer to hold a code for a video format, and I often need to convert that to a human readable string.
A simple console app that could perform this logic would have a main function like this ...
static void Main(string[] args)
{
string input = Console.ReadLine();
UInt32 fourCC = Convert.ToUInt32(input, 16);
Byte[] output = new byte[4] { (byte)(fourCC >> 24), (byte)(fourCC >> 16), (byte)(fourCC >> 8), (byte)fourCC };
Console.WriteLine(System.Text.Encoding.ASCII.GetString(output));
Console.ReadKey();
}
The problem with taking this approach to the problem is that there is a cost associated with deploying and maintaining the application in the form of build servers, installers, artifact hosting, etc.
A second option would be to build a simple web app, and host it on a local web server. Again there is a cost associated with maintaining the web server to ensure its continued availability. The potential uptime of the server could be improved by provisioning a VM in the cloud and hosting the application there, but that would incur a fixed monthly charge regardless of how often the application was used. With a serverless computing solution, on the other hand, this high availability but infrequently used application can be deployed at a lower cost; paying just a very small amount per use.
For the illustrative purposes of this article, I'm going to implement this application using a server / client architecture (although I appreciate that, in this case, the algorithm could easily be run completely on the client side.)
The desire is to replace the c# code, above, with an AWS Lambda implementation. Amazon provide a toolkit for Visual Studio that allows a C# Lambda function to be easily written and deployed. After installing the toolkit, create a new project of type "AWS Lambda Project (.NET Core)". The empty project has a single function called Function Handler
which should be replaced with code that is practically identical to that in the console application.
public string FunctionHandler(string input, ILambdaContext context)
{
UInt32 fourCC = Convert.ToUInt32(input, 16);
Byte[] output = new byte[4] { (byte)(fourCC >> 24), (byte)(fourCC >> 16), (byte)(fourCC >> 8), (byte)fourCC };
return System.Text.Encoding.ASCII.GetString(output);
}
Right clicking on the project reveals a "Publish to AWS Lambda" context menu item. Upon publishing, it is possible to test the Lambda function inside the AWS Console. Note, that since the function takes a string, the test needs to be setup with an input including double quotes, e.g. "0x76323130"
. The test should return a result like this
Execution result: succeeded(logs)
Details
The area below shows the result returned by your function execution. Learn more about returning results from your function.
"v210"
Summary
Code SHA-256
CBX7CogYcGFoznkPl6+vlpUiamcPbIJtl15k8azl15k=
Request ID
ec3e4cd9-b92e-11e8-8f04-215190a10824
Duration
0.21 ms
Billed duration
100 ms
It looks like the billed duration is in increments of 100ms, so, in this instance, the Lambda function is not doing an optimum amount of work. That being said, Amazon's free tier offers 1,000,000 free transactions a month of a low memory function such as this one.
Having created a Lambda function, the next step is to expose it publically. To do this requires the use of Amazon's API Gateway. The API gateway creates an HTTP interface that can call a Lambda Function (amonst other things) on the client's behalf. After using the AWS Console to create an API, the next task is to use the Actions dropdown buttons "Create Method" item to add a POST handler that will pass data to the Lambda function. The Lambda function can be selected from the list, and the default parameters will be sufficient for a simple test. However, the default parameters for the method assume that the data will be passed back and forth as a JSON string, and so it makes sense to change the "Method Response" and "Integration Response" parameters to reflect the fact that the function returns a string.
The "Method Response" can be configured by adding a response header named "Content-Type" and removing the model for the response body. The "Integration Response" can then be modified to provide the "Mapping Value" of 'text/Plain'
for the "Content-Type" header (note the single quotes). This will ensure that the HTTP response will have the appropriate MIME type for this kind of data. Finally, as we saw earlier, the response from the Lambda function has double quotes around the returned string. These can be removed by adding a "Mapping Template" to the "Integration Response" for the text/plain
content. The template should be created with the text $input.path('$')
which will server to remove the double quotes.
Once all this is configured, we can "Deploy API" from the Actions dropdown, to create a "stage". The stage, once created, will provide an "Invoke URL" which is the public API. A tool such as Postman is useful testing the published functionality.
Once this API is published, the functionality is easily accessible programatically but it is possible to use the same steps to publish a UI from which to exercise the API. We will create another Lambda function which will publish a static HTML page, with script to call the POST API.
Using the AWS toolkit from within Visual Studio, create another "AWS Lambda Project (.NET Core)" project, but this time change the function so that it takes no parameters and returns a string
public string FunctionHandler(ILambdaContext context)
{
return @"
<html>
<header>
<link rel='stylesheet' href='https://fonts.googleapis.com/icon?family=Material+Icons'>
<link rel='stylesheet' href='https://code.getmdl.io/1.3.0/material.indigo-pink.min.css'>
<script defer src='https://code.getmdl.io/1.3.0/material.min.js'></script>
<title>AckFlip - FourCC</title>
</header>
<body>
<div class='mdl-textfield mdl-js-textfield mdl-textfield--floating-label'>
<input class='mdl-textfield__input' type='text' id='hexText'>
<label class='mdl-textfield__label' for='hexText'>Hex...</label>
</div>
<div class='mdl-textfield mdl-js-textfield mdl-textfield--floating-label'>
<input class='mdl-textfield__input' type='text' id='fourCCText' readonly>
<label class='mdl-textfield__label' id='fourCCLabel' for='fourCCText'>FourCC...</label>
</div>
<script>
function myFunction()
{
var xhr = new XMLHttpRequest();
xhr.open('POST', '', true);
//Send the proper header information along with the request
xhr.setRequestHeader('Content-Type', 'text/plain');
xhr.onreadystatechange = function() {//Call a function when the state changes.
if (this.readyState == XMLHttpRequest.DONE && this.status == 200)
{
document.getElementById('fourCCText').value = this.responseText;
document.getElementById('fourCCText').parentElement.MaterialTextfield.checkDirty();
}
}
xhr.send('""' + document.getElementById('hexText').value + '""');
}
</script>
<button onclick='myFunction()' class='mdl-button mdl-js-button mdl-button--fab mdl-js-ripple-effect mdl-button--colored'>
<i class='material-icons'>refresh</i>
</button>
</body>
</html>";
}
Note, that there's no real logic here other than to return a static string which defines the HTML for the static web page. The HTML code uses Material Design Lite to improve the appearance and simply retrieves the text value from an input control before calling the desired POST method. The response from the POST method is displayed in a second input control.
This Lambda function is published using the "Publish to AWS Lambda" described earlier. The API Gateway console is again used to expose the Lambda function via an HTTP method; however, this time, a GET method is used. In the same way that the POST response was modified to expose the returned string as a text/plain
MIME type, the exact same steps need to be taken to expose the returned HTML string as a text/html
MIME type.
Once all this is in place and the "Deploy API" reinvoked, the static webpage can be invoked simply by clicking the "Invoke URL" displayed in the stage to open a new browser tab which will call the newly exposed GET method.
At this point, we have a fully functional web app; the initial GET method returns a static web page with scripting that will call the POST method when the functionality exposed in the UI is requested. The only thing required to make this more useful is to accociate the deployed stage with an easily remembered domain name.
I have been using Google Domains to manage my domain names lately as I find the DNS configuration easy to manage in their console. I spent a few happy hours searching for a domain name I liked and came up with ackflip.com. I wanted something that I could also use for similar applications and flip invokes the act of converting from one form to another. Ack is commonly used as an abbreviation for acknowlegement in communication protocols, and so this had relevance for me too. Finally, since all the obvious domain names have been taken, I've found that short real words with the beginning and/or end letter missing are easy for me to remember and also still available.
AWS first requires that I generate a certificate with their Certificate Manager. After adding ackflip.com, and fourcc.ackflip.com to the certificate, I chose to use DNS validation. This entails using the Google Domain's DNS setting page to add a CNAME record for each of the domains added to the certificate. The key and value of the CNAME record are both long URLs provided in the certificate manager and designed to be unique. Soon after these are added, the certificate manager shows that the certificate is valid.
The next step is to return to the API Gateway and to use the "Custom Domain Names" menu item to associate the domain name with the stage. After entering the domain name and picking the certificate from a list, the basename of the URL is associated with a stage. Since, this example provides only a single service via a stage, only a single "base path mapping" of the root path to the stage is necessary.
Upon submitting this configuration, a "Target Domain Name" is displayed. All that is required to link the domain name to the stage is to return to the Google Domain console and create a new CNAME record linking fourcc.ackflip.com to this target value.