AWS Lambda: Calling functions from a web browser

This article was excerpted from the book AWS Lambda in Action. A few live demos, are available here. The full code used in the book is in this repo.

Danilo Poccia
5 min readAug 30, 2016

When configuring the Amazon API Gateway to use Lambda functions, you can choose to have no authentication and leave the API publicly available. Using this option together with the HTTP GET method allows your API to be called by a web browser. You need to return the expected content type for HTML (text/html) to implement a public website.

The architectural configuration we’ll be using in our example is in figure 1. The client application’s a web browser; AWS Lambda and Amazon API Gateway are used to distribute a public website on the Internet. The advantage this offers compared with static website hosting, such as that available with Amazon S3, is that the Lambda function can generate server-side dynamic content.

You can serve web content from a Lambda function using the Amazon API Gateway, and expose the function via HTTP GET. You need to be careful in configuring the integration to return the right HTTP Content-Type, for example “text/html” for HTML.

As an example, let’s create a simple website using Embedded JavaScript (EJS) templates that’re dynamically served to the end-users. I’m using EJS templates in this example, but any kind of server-side technology works. You need a single Lambda function, “ejsWebsite” (listing 1), that you can integrate with multiple resources in the Amazon API Gateway. For example, the root resource of the API (/) and a resource parameter that can be used for any single-level paths (/{path}). Calling this function will return HTML content.

This example is in Node.js because it’s using EJS templates. In the book I try to do most of the examples in Node.js and Python.

Listing 1 ejsWebsite.jsconsole.log('Loading function');const fs = require('fs');
const ejs = require('ejs');
exports.handler = (event, context, callback) => {
console.log('Received event: ',
JSON.stringify(event, null, 2));
// Build a local file name,
// included in the function deployment package,
// based on the path in the event
var fileName = './content' + event.path + 'index.ejs';
console.log(fileName)
fs.readFile(fileName, function(err, data) {
if (err) {
// If the file’s missing, fail returning
// a "404" string that the Amazon API Gateway
// can intercept and manage
// to return HTTP 404 status
callback("Error 404");
} else {
// Interpret the EJS template server side
// to produce HTML content
var html = ejs.render(data.toString());
// Return the HTML wrapped in JSON
// to preserve encoding
callback(null, { data: html });
}
});
};

Create the “ejsTemplate” Lambda function using the basic execution role and all default parameters. Because the ejs module’s required, you need to install it with npm and create a ZIP archive to upload your deployment package. In the ZIP archive, include a content folder with some sample EJS templates to be interpreted by the Lambda function. For example, for a tiny website with About and Contact pages, you can include the following files:

content/index.ejs
content/about/index.ejs
content/contact/index.ejs

Each of these files are described in listings 2, 3 and 4, respectively. As you can see, these files are similar to each other and contain some dynamic content that’s evaluated on the server by the Lambda function before the result is returned by the Amazon API Gateway to the browser.

Root EJS template

Listing 2 content/index.ejs<html>
<head>
<title>Home Page</title>
</head>
<body>
<h1>Home Page</h1>
<p>The home page at <strong><%= new Date() %></strong></p>
<ul>
<li><a href="about/">About</a></li>
<li><a href="contact/">Contact</a></li>
</ul>
</body>
</html>

The JavaScript code between <%= and %> is interpreted server side by the Lambda function.

About EJS template

Listing 3 content/about/index.ejs<html>
<head>
<title>About</title>
</head>
<body>
<h1>Home Page</h1>
<p>The about page at <strong><%= new Date() %></strong></p>
<ul>
<li><a href="../">Home Page</a></li>
<li><a href="../contact/">Contact</a></li>
</ul>
</body>
</html>

Contact EJS template

Listing 4 content/contact/index.ejs<html>
<head>
<title>Contact</title>
</head>
<body>
<h1>Home Page</h1>
<p>The contact page at <strong><%= new Date() %></strong></p>
<ul>
<li><a href="../">Home Page</a></li>
<li><a href="../about/">About</a></li>
</ul>
</body>
</html>

This part of the template’s using the EJS template syntax to get the current date and time and replace it server-side:

<%= new Date() %>

Integrating the Lambda functions with the Amazon API Gateway

You now need to integrate this Lambda function with the Amazon API Gateway. From the API Gateway console, create a “Simple Website” API. This won’t be a normal Web API, but it’ll be used as a public website.

Create a Method for the root (/) resource. Your code needs to answer only the HTTP GET request for this simple website; choose the GET method from the menu. You can easily extend this example to support other HTTP verbs such as POST.

In the Integration Request, select the “ejsTemplate” Lambda function you created and create a Mapping Template to send the “path” to the function. Use the application/json content type and this basic static mapping template:

{
"path": "/"
}

You now need to change the default content type returned by the API call to text/html. In the Method Response, expand the “200” HTTP Status, add the text/html content type with an Empty Model and remove the default application/json content type. Use the code in listing 5 as a mapping template for the text/html content type:

Listing 5 Mapping template for the Amazon API Gateway
to return the content of the data attribute
#set($inputRoot = $input.path('$'))
$inputRoot.data

I’m using a “data” attribute in the JSON payload that’s returned by the function to embed all the HTML content; this template’s extracting the HTML to be the only content returned. If you return HTML content directly, it’s escaped with HTML entities and difficult to use. You can use the Test button to check whether the content and the content type returned by the integration are correct.

A website with a home page is now implemented, but let’s create another integration to manage all single-level paths, such as /about or /contact. Create a new resource with “Page” as the name and {page} as resource path and add a GET method to this resource as well. In the Method Request, you now have the “page” resource path parameter. In the Integration Request, use the same “ejsTemplate” function as before, but in the mapping template for the application/json content type, use the following template to pass the “page” parameter to the function:

{
"path": "/$input.params('page')/"
}

In the Integration Response, replace the default application/json content type with text/html and use the same mapping template as before, using the code in listing 5.

Now you have to manage possible requests that don’t find content (a corresponding EJS template in this case) within the function. To do that, in the Method Response, add a 404 HTTP Status. In the Integration Response, use Add integration response with 404 as Lambda Error Regex to return the 404 HTTP code, in case the page (the EJS template) isn’t found in the content folder of the Lambda function.

Use the Test button to try different “page” values, for example “about/” or “wrong/,” to see what happens when the EJS template’s found or not found.

When you deploy this API in a stage (for example “home”), the website’s publicly accessible and you can navigate through the links using a web browser. Remember to use the full path, the domain, and the stage; for example, (the domain will be different in your case):

https://123ab12ab1.execute-api.use-east-1.amazonaws.com/home

The dates in the EJS templates are evaluated on the server, and you should see the dates being updated as soon as the browser accesses the link again.

The same approach can be extended to generate more complex server-side content using AWS Lambda and the Amazon API Gateway.

--

--

Danilo Poccia

Passioned about IT, IoT, AI, ML, and other acronyms. Writing the Chronicles of Computation book. Chief Evangelist (EMEA) @ AWS. Opinions are my own.