Build

A Crash Course in Functions

Adam Bavosa on Jul 17, 2018
A Crash Course in Functions

What are Functions?

functionsThe answer will go over your head unless you first understand PubNub's Pub/Sub. PubNub's core product is a Publish-Subscribe API for sending 32KB or smaller messages between Internet-connected devices. A benefit of using PubNub over any other system or homegrown solution is that messages sent over our data streaming network are upgraded to the fast lane. Messages sent over PubNub reach their destination in under 0.25 seconds, but usually much faster (30 – 40ms). The network has multiple points of presence on every inhabited continent. This allows messages to be sent using the shortest network path, for the lowest latency. Additionally, multiple, global points of presence enable PubNub to continue to work if data centers fail.


A comparison of AWS Lambda and Functions is available here


If a device can connect to the internet, it can speak PubNub. There are over 70 PubNub SDKs; one for every programming language and device, including a REST API. Any real-time, event-driven system can benefit from PubNub's guaranteed, high-speed data delivery.

Developers use PubNub's Pub/Sub for

and much, much more.

Functions are an additional bit of value that a developer can add to plain Pub/Sub messages. Functions enable the developer to execute their own code every time a PubNub message is sent. This code can be used to augment, transform, route, filter, or aggregate PubNub messages in between sender and receiver.

The code can change the message itself, signal another service, or reply to the requester with a standard REST API response.

Why would a developer use Functions?

Functions make the PubNub network programmable. Reasons to use Functions are best explained using real-life use cases.

Use cases for Functions

  • Translation – Send a chat app message in English, and the recipient receives it in Spanish.
  • Analytics – Aggregate data points provided by an IoT device and publish statistics to a dashboard graph.
  • Secure API Calls – Trigger a one-off alert when a secure message meets certain criteria – email, text message, tweet, mobile push, etc.
  • Network Logic – Tally up millions of votes or question answers during real-time audience interaction.
  • Serve Data – Build a globally replicated, low latency, production REST API in 5 minutes.
  • Countless use cases are available because developers can provide arbitrary code to Functions.

With features like Vault, PubNub apps can use Functions to securely hold third-party API keys. The developer can make requests to external, private APIs in real time. These API calls can be a trigger or they can be used to enrich an in-flight PubNub message. The Functions KV Store can be used to store 32KB or smaller data objects. This makes Functions a great place to manage application state, store user session data, and more.

Which type of Function should I use?

There are 4 types of Function event handlers, and they are not created equal. The event handler you should select depends on your use case. Here are some high-level properties of each event handler:

Before Publish or Fire

  • Executes when a user Publishes/Fires and completes before Subscribers receive the message.
  • Allows the Function code to mutate the PubNub message contents.
  • Great for chat app message real-time language translation.

After Publish or Fire

  • Executes after a user Publishes or Fires.
  • Execution does not block Subscribed users from receiving the message (asynchronous).
  • Cannot mutate the PubNub message contents.
  • Great for triggering alerts through a third-party API like email, text, push, etc.

After Presence

  • Executes after a presence event for the specified channel fires.
  • Execution is non-blocking (asynchronous).
  • Cannot mutate the PubNub message contents.
  • Great for triggering alerts when a user goes offline in a multiplayer game.

On Request

  • Executes when an HTTP request is made to the Function's unique, custom URL.
  • Generates and sends HTTP responses in a syntax similar to Express for Node.js.
  • Great for building an auto-scaling REST API (guide to build this is here).

Examples

Before Publish or Fire

export default (request) => {
    const xhr = require('xhr');
    console.log('1. original', request.message);
    return xhr.fetch('https://randomuser.me/api/')
        .then((reply) => {
            request.message.name = JSON.parse(reply.body).results[0].name.first;
            console.log('2. augmented', request.message);
            return request.ok();
        }).catch((err) => {
            console.error(err);
            return request.abort();
        });
};

Above we have an example external API request. The Function changes a property in the PubNub message body before the subscribers receive the message. When executed, the message augmentation is illustrated by the console.log lines. The name property gets populated with a random name from an external API.

Functions used as a chat message translator

The same concept can be applied to a real-time chat app with language translation. See the BLOCKS catalog for free Functions code that you can import to your PubNub app.

After Publish or Fire

const vault = require('vault');
const xhr = require('xhr');
const basicAuth = require('codec/auth');
// Use the Functions Vault Module to store these keys securely.
// username : '__YOUR_CLICKSEND_USER_NAME__'
// authKey  : '__YOUR_CLICKSEND_AUTH_KEY__'
const url = 'https://rest.clicksend.com/v3/sms/send';
export default (request) => {
    const getAPIKeys = new Promise((resolve, reject) => {
        vault.get('username').then((username) => {
            vault.get('authKey').then((authKey) => {
                resolve(basicAuth.basic(username, authKey));
            });
        });
    });
    return getAPIKeys.then((authorization) => {
        // Sends a text message!
        return xhr.fetch(url, {
            'method'  : 'POST',
            'headers' : {
                'Authorization' : authorization,
                'Content-Type': 'application/json'
            },
            'body': JSON.stringify({
                'messages': [{
                  'source': 'PubNub-Functions',
                  'from': 'Bob',
                  'body': request.message.text,
                  'to': request.message.phone_number,
                }]
            }),
            'timeout' : 5000
        });
    }).then((reply) => {
        return request.ok();
    }).catch((err) => {
        console.error(err);
        return request.abort();
    });
};

The above code asynchronously sends a text message using the ClickSend API. The private API credentials for ClickSend is stored in the Functions Vault. The keys are treated like environment variables on a server and are not displayed in the source code. The BLOCKS Catalog contains more than 80 open source Function event handlers, to link your app up to popular APIs.

After Presence

const kvstore = require('kvstore');
export default (request) => {
    const userId = request.message.uuid;
    // If a user is leaving a chat, save
    // their last online time in the KV Store
    if (request.message.action === 'leave') {
        // 1 year in minutes is 525600
        return kvstore.set(`last-online-${userId}`, {
            datetime: new Date(request.message.timestamp * 1000)
        }, 525600)
        .then(() => {
            return request.ok();
        })
        .catch((err) => {
            console.error(err);
            return request.abort();
        });
    } else {
        return request.ok();
    }
};

This After Presence example shows a function that is triggered by Presence events on a specified PubNub channel. When a user joins or leaves your channel, this Function executes with event information. The event message body includes the user's UUID, timestamp, and the total occupancy of the channel.

The last online indicator in a chat app

The example writes “last online” data to the Functions KV Store. The intent is that this will later be retrieved by other users in a chat app. A user's friend list UI would show the last date and time their friend was active in the chat app, by fetching data from the KV Store.

On Request

export default (request, response) => {
    const xhr = require('xhr');
    response.headers['Access-Control-Allow-Origin'] = '*';
    response.headers['Access-Control-Allow-Headers'] = 'Origin, X-Requested-With, Content-Type, Accept';
    response.headers['Access-Control-Allow-Methods'] = 'GET, POST, OPTIONS, PUT, DELETE';
    // Choose route based on request.params and request.method
    // Execute the controller function in the controllers object
    const route = request.params.route;
    const method = request.method.toLowerCase();
    const body = JSON.parse(request.body);
    // Functions for defined 'Routes'.
    // Add functions for each object under HTTP method keys
    let controllers = {
        default: {},
        kitty: {}
    };
    // default GET request returns 200 OK
    controllers.default.get = () => {
        response.status = 200;
        return response.send();
    };
    controllers.kitty.get = () => {
        const url = 'http://thecatapi.com/api/images/get?results_per_page=1';
        return xhr.fetch(url, {
            'method': 'GET'
        }).then((result) => {
            response.status = 302;
            response.headers['Location'] = result.url;
            return response.send();
        });
    }
    // GET request with empty route returns 200
    if (!route && method === 'get') {
        return controllers.default.get();
    } else if (
        method &&
        route &&
        controllers[route] &&
        controllers[route][method]
    ) {
        return controllers[route][method]();
    } else {
        response.status = 404;
        return response.send();
    }
};

Here we have an On Request handler which responds to regular HTTP requests with HTTP responses. By using the URL parameter of route and a verb like GET, this Function can act as a REST API and respond to as many types of requests the developer defines.

Testing out a Function's execution can be done using the test payload user interface in the Functions editor. To get to the Functions editor, log into your forever free PubNub account and click the FUNCTIONS tab.

Create a Functions Event Handler

The editor allows a developer to PubNub publish a JSON message body to the Function's channel. For On Request event handlers, conventional HTTP requests can be made to the URL of the Function endpoint.

For in-transit message event handlers:

Test payload in Functions

For On Request event handlers:

PubNub Test Request Tool to Endpoint

Operationalization

Functions code can be written, deployed, and maintained from a developer's command line using the Functions CLI.

Unit tests for Functions can be performed on a developer's local machine, or in a continuous integration environment. See the Functions CI/CD example guide here.

Alerting and monitoring for Functions is available to use through our monitoring tool. The GitHub repository of the tool here and an implementation tutorial is in this post, here.

The PubNub BLOCKS Catalog has more than 80 open source Functions to integrate third-party APIs into your PubNub app. Try them out for free today.