Mercure

Real-time notifications with Mercure

Dev PHP

Hello y’all, today I’m gonna talk about how you can get real-time notifications on our webapp or website using a nice tool called Mercure.

Mercure it’s a pretty nice, lightweight protocol built from the ground to publish data from the servers to the clients.

It is a modern and efficient alternative to timer-based polling and to WebSocket.

Because it is built on top Server-Sent Events (SSE), Mercure is supported out of the box in most modern browsers (Edge and IE require a polyfill) and has high-level implementations in many programming languages.

Today I’m gonna show how to push data using Laravel. So let’s go for it.

Running Mercure

Easiest way to have Mercure up and running is using a Docker image for it. Here is the docker compose file I’m using in this example:

It’s important to tell, that the Mercure setup is made through environment variables, so pay attention on it.

version: '2'
services:
mercure:
    image: dunglas/mercure
    environment:
      # You should definitely change all these values in production
      - JWT_KEY=YourSecretKey
      - DEMO=1
      - ALLOW_ANONYMOUS=1
      - HEARTBEAT_INTERVAL=30s
      - ADDR=:3000
      - CORS_ALLOWED_ORIGINS=*
      - PUBLISH_ALLOWED_ORIGINS=http://mercure:3000,http://localhost:3000
    ports:
      - "3000:3000"
    networks:
      - backend
networks:
  backend:
    driver: bridge

Now just start the container:

docker-compose up

Using Mercure binaries

Another option is to download the Mercure binaries from the Github page. If you download it, you can run using the following command:

JWT_KEY='YourSecretKey' ADDR=':3000' DEMO=1 ALLOW_ANONYMOUS=1 CORS_ALLOWED_ORIGINS=* PUBLISH_ALLOWED_ORIGINS='http://localhost:3000' ./mercure

Creating your JWT

Mercure uses JWT (JSON Web Tokens) as authorization method for publishing and subscribing. So it’s a must to have a token when you want to publish a message.

For subscribing it’s not necessary while you are just playing around and using the env variables DEMO=1 and ALLOW_ANONYMOUS=1

So, to create your token go to https://jwt.io/. There you need to fill out two things, the payload and the secret.

The JWT must be signed with the same secret key than the one used by the Hub to verify the JWT (YourSecretKey in our example). Its payload must contain at least the following structure to be allowed to publish:

{
    "mercure": {
        "publish": []
    }
}

Because the array is empty, our Laravel app will only be authorized to publish public updates.

So here is how it looks like in the end:

Installing the dependencies on your Laravel project

Let’s suppose you already have a Laravel project (if you don’t have it, follow these steps: https://laravel.com/docs/5.8/installation#installing-laravel). So now we need to install the package which will handle the message publishing for us:

composer require idplus/laravel-mercure-publisher

The package will automatically register its service provider.

Out of the box, you can define the “Mercure Hub Url” and his “JWT Secret” via Environment Variable in your .env file:

MERCURE_PUBLISH_URL=http://127.0.0.1:3000/hub
MERCURE_JWT_SECRET=your.token.here

Optionally, you can publish the entire config file to config/mercure.php with this command:

php artisan vendor:publish --provider="Idplus\Mercure\MercureServiceProvider"

Here is the default contents of the configuration file:

return [
    'hub' => [
        'url' => env('MERCURE_PUBLISH_URL','http://127.0.0.1:3000/hub'),
        'jwt' => env('MERCURE_JWT_SECRET'),
    ],
    'jwt_provider' => null,
    'queue_name' => null,
];

Publishing your first message

After a few steps of setup, let’s finally publish or first message. 😀 Let’s create a controller send a message:

php artisan make:controller PublisherController

The controller class will be created in the directory <ProjectRoot>/app/Http/Controllers. Let’s create a function called send, which will publish the message. Here is the code of our method:

namespace App\Http\Controllers;

use Idplus\Mercure\Publify;
use Symfony\Component\Mercure\Update;

class PublisherController extends Controller
{
    public function send(Publify $publisher)
    {
        $data = ['status' => 'Working!!'];
        $update = new Update(
            'mytopicname',
            json_encode($data)
        );
        $publisher($update);

        return 'OK';
    }
}

Explaining a little bit about the code above, Publify is a service which dispatches the data to Mercure hub and Update is a Symfony Mercure Component loaded which provides an Update value object representing the update to be published.

The $publisher object is a callable object, so you can call it passing the Update object.

One thing to pay attention here, we are passing to the Update object, to parameters, the topic where the message will be publish and the message payload itself, as string. You don’t have to create the topic on Mercure, this will be done on the fly.

Creating the subscriber

Now, let’s create an HTML page, which will listen to the messages sent to Mercure. In Laravel the views are placed on <ProjectRoot>/resources/views here is the code:

<!doctype html>
<html>
    <head>
        <meta charset="utf-8">
        <meta name="viewport" content="width=device-width, initial-scale=1">

        <title>Mercure</title>
    <script>
        const es = new EventSource('http://127.0.0.1:3000/hub?topic=mytopicname');
        es.onmessage = e => {
            // Will be called every time an update is published by the server
            alert(JSON.parse(e.data).status);
        }
    </script>
    </head>
    <body>
        <h1>Listening for messages</h1>
    </body>
</html>

It’s time register the routes for our methods. I will save you the time to type and you can copy it from here:

Route::get('/publish', 'PublisherController@send');
Route::get('/subscribe', static function () {
    return view('subscribe');
});

On Laravel, the router files are placed on <ProjectRoot>/routes.

Now that we have everything setup, let’s do a test! First of all, let’s spin up a server to execute Laravel. Easiest way is to use the artisan command serve to start it:

php artisan serve

This will spin up the PHP built-in server and listen connection on port 8000. Now, open your browser the url http://127.0.0.1:8000/subscribe and in a new tab or using the terminal, go to http://127.0.0.1:8000/publish and you will see the magic happening 😀

Here’s a GIF showing the functionality:

Working Mercure

Using Mercure in Production environment

Now that we’ve played around with Mercure, there are few things we need to consider before use it in production. Mainly related to configuration and the subscribers.

Turning off demo mode

Before setup Mercure for production, we need to remove the the environment variables DEMO and ALLOW_ANONYMOUS, for security reasons, cause when those flags are turned off, the subscribers will have to be authorized using the JWT.

EventSource doesn’t support custom headers

Now all the subscribers have to send the Authorization header, the problem is that the standard EventSource object doesn’t support custom headers (See: https://developer.mozilla.org/en-US/docs/Web/API/EventSource). In my case, I had to use an Polyfill to do the job. The one I used was this one: https://github.com/Yaffle/EventSource

This is an example about how to use it:

var mercureToken = "Bearer your.Token.Here";
var eventSource = new EventSourcePolyfill('http://127.0.0.1:3000/hub?topic=mytopicname', {
    headers: {
        Authorization: mercureToken
    }
});

Running Mercure on SSL mode

When you are setting up Mercure for production and your webapp is running on HTTPS, you have to setup Mercure accordingly.

Mercure for itself has a built-in functionality to generate the SSL keys using Let’s Encrypt.

Although, when I was deploying Mercure in the production server, this functionality didn’t work, so I had to use the certbot to generate and install the certificate.

Using Supervisor to manage the Mercure daemon

When we use Mercure in the production server, we have to take care when the service is initiated on boot time, it’s not like other services like Nginx for example.

In my case I’ve used Supervisor to manage the Mercure daemon process, here is how you can use Supervisor to do the job for you: https://github.com/dunglas/mercure#how-to-monitor-the-hub-using-supervisor

Problems while running on Windows

Some users have reported a problem while publishing messages, that Mercure only route the messages to the consumers after killed the process. There is currently a bug opened to fix this issue, you can see it here: https://github.com/dunglas/mercure/issues/105

Final notes

So basically that’s it, Mercure is a really cool tool to use for real-time notifications, easy to setup, for both ends (publishers and subscribers) and incredible fast! Totally worth it a try!

If you have any question, just write a comment. 🙂

References:

Leave a Reply

Your email address will not be published. Required fields are marked *