8 October, 2018

MQTT instead of HTTP to ensure keep-alive on PHP backend

Using MQTT to create live notifications for users in the project which backend does not maintain a permanent connection.

If your project backend uses HTTP protocol (for example a website) that does not maintain a permanent connection and "dies" immediately after execution, there is a conceptually different solution to this problem than commonsense WebSockets — MQTT.

We'd like to share our experience of using MQTT protocol in one of the internal Gurtam projects. In Gurtam we have own CRM portal which relies on a PHP-powered backend. The idea was to add the real-time notifications feature to instantly alert the users of several types of events. At the same time, we didn't want PHP to run a separate blocking process for each logged user to ensure instant notification.

We chose an alternative approach to helping PHP backend maintain a permanent connection — the flespi MQTT broker and MQTT.js client library. Note that since PHP does not natively support MQTT, we used the HTTP REST API to publish messages to the broker from the backend.

With MQTT there is no such thing as request and response. Once a new message is generated by the publisher (backend), it gets into the MQTT broker that immediately sends it to all subscribers (affected company employees). MQTT implementation also easily handles any connectivity issues — no messages will get lost due to the specified session expiry interval.

This is how the interaction between the project parts looks like:

Create a flespi token

For each user in the project, we create a token with ACL that allows subscription only. For example, below is a token with access for subscription to three topics: 'project_name', 'project_name/group_name', 'project_name/group_name/user_id':

{
"access": {
"acl": [
{
"actions": [
"subscribe"
],
"topic": "project_name",
"uri": "mqtt"
},
{
"actions": [
"subscribe"
],
"topic": "project_name/group_name",
"uri": "mqtt"
},
{
"actions": [
"subscribe"
],
"topic": "project_name/group_name/user1",
"uri": "mqtt"
}
],
"type": 2
},
"info": "user1 - project_name/group_name/user1",
"ttl": 31536000
}

You can create a token via flespi REST API here: https://flespi.io/docs/#/platform/!/tokens/post_customer_tokens (or follow the video guide here).

Customize backend

Configure the backend for users as the task suggests. We added three notification types similar to topic types (common, group, personal) and notification categories: common, reports, etc.

Every notification on the backend is created for one or more users. Once the new notification appears, the backend automatically publishes a message to the MQTT broker. The target topic depends on the notification type.

To publish messages we use a token with appropriate ACL:

Configure frontend

On the frontend we use MQTT.js library and connect every user to flespi MQTT broker using a token and a unique customer_id. Upon connect, the user is subscribed to the ‘project_name/#’ topic:

var mqtt = require('mqtt');
var notificationCount = 0;
var client = mqtt.connect('mqtts://mqtt.flespi.io:8883', {
clientId: 'project_name_' + Math.random().toString(36).substring(5),
protocolVersion: 5,
clean: true,
sessionExpiryInterval: 600,
username: 'FlespiToken XXXXXXXXXXXX'
});
client.on('connect', function (connack) {
client.on('message', function (topic, message, packet) {
notificationCount++;
});
});
client.subscribe('project_name/#');

Use 'clean: false' on reconnect — then if the broker has new messages for the user, they will receive them.

That’s it! Now you have live notifications with no time spent on the WebSocket-based layer and all features of MQTT 5.0 available!

Side notes

In the process of development we did the following to handle incoming messages from the broker:

  • Message content published to the broker is a JSON-encoded text format, every message has an "id" attribute.

  • Almost every message has a "read" attribute — it allows using several tabs in a browser or even using multiple devices at the same time with synchronized data between them. If you read a notification once, just send a request to your backend (and to broker respectively) that the notification is already read (e.g. {"id":1,"read":true}) and all subscribed clients (tabs/browsers/devices) will receive this message and set the "read" status to it.

  • Messages may have a "category" attribute to be able to use notifications for different events in different code placements and with different handlers.

  • Published message broker has an "expiry_interval" of 10 minutes — that’s an easy way to receive messages if your connection to the network was lost (but not for all "status" attributes, see next).

  • Every message has a "status" attribute that allows filtering actions by type and by messages expiry date. For example, token regeneration without messages loss. Every token has an expiry date. We save it in the database and when expiration date comes, we just regenerate the token — delete it in the database and before deleting an old one and creating a new one in flespi send a message {"status":401,"id":null,"description":"Token will be regenerated"} to the broker (without "expiry_interval"). All subscribed clients (tabs/browsers/devices) receive this message and try to require a new token from backend periodically.

***

Even if you know how to establish keep-alive connections with WebSockets, consider the alternative MQTT-based approach we discussed above. Then you won't have to bother creating a WebSocket-based service — MQTT will take care of consistent communication and no message will ever get lost.