Poccia_CallingFunctions_00 By Danilo Poccia

This article was excerpted from the book AWS Lambda in Action.

 

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.


Poccia_CallingFunctions_01

Figure 1 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.

Note This example is available in Node.js only because it’s using EJS templates.


Listing 1 ejsWebsite (Node.js)

 
console.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));
  var fileName = './content' + event.path + 'index.ejs';  #A
  console.log(fileName)
  fs.readFile(fileName, function(err, data) {
    if (err) {
      callback("Error 404");  #B
    } else {
      var html = ejs.render(data.toString());  #C
      callback(null, { data: html });  #D
    }
  });
};
 

#A Build a local file name, included in the function deployment package, based on the path in the event

#B If the file’s missing, fail returning a “404” string that the Amazon API Gateway can intercept and manage to return HTTP 404 status

#C Interpret the EJS template server side to produce HTML content

#D Return the HTML wrapped in JSON to preserve encoding


 

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.


Listing 2 Root EJS template

 
<html>
  <head>
    <title>Home Page</title>
  </head>
  <body>
    <h1>Home Page</h1>
    <p>The home page at <strong><%= new Date() %></strong></p> #A
    <ul>
      <li><a href="about/">About</a></li>
      <li><a href="contact/">Contact</a></li>
    </ul>
  </body>
</html>
  

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


Listing 3 About EJS template

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

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


Listing 4 Contact EJS template

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

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


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 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.