Home > front end >  Symfony 5.4 - How am i supposed to properly start mercure using symfony
Symfony 5.4 - How am i supposed to properly start mercure using symfony

Time:04-01

I installed mercure using the following command as stated in the documentation:

composer require mercure

After that, i'm supposed to start the mercure server and here is the issue: how?

Symfony's documentation doesn't state that.
Mercure's documentation also doesn't state anything about it, especially using a config file generated in /config/packages/mercure.yaml.

What i've found is that i need to download a separate binary (for my specific platform) and then i'm supposed to start the server from Powershell using the following:

.\/bin/mercure run

However, if i try to pass -envfile ".env", then it doesn't even try to use any of the variables inside.
On top of that, it doesn't seem to be using any environment variables at all, even ones i've defined in cmd.

There are other things like how do i allow CORS, as there isn't something i can change in my config file, it isn't an argument i can pass to the binary and i can't add it as an environment variable.

if i ever try to subscribe to the event (which is done on port 2019 cause of the previous issues), 404 and CORS is returned.

also this is what i get with just .\/bin/mercure run:

mercurebroken?

My .env:

enter image description here

Right now i'm wondering how am i supposed to use mercure, i've looked at a couple videos for symfony 5.4 and they basically either are on linux and it somehow work for them OR It's marked as outdated cause of configs changes, syntax changes and others...

The documentation also doesn't help at all in that regard which kind of make me regret trying to use mercure.

EDIT:

I was told to try fiddling with Caddy configs, but it doesn't seem like mercure is trying to use those either, even tried specifying the config with -config.

Result:

  • Port not set to 3000
  • No allow_cors header

Caddyfile:

    # Learn how to configure the Mercure.rocks Hub on https://mercure.rocks/docs/hub/config
{
    {$GLOBAL_OPTIONS}
    http_port 3000
    https_port 3000
}

localhost:3000

route {
    encode zstd gzip

    @origin header Origin http://localhost:8000
    header @origin Access-Control-Allow-Origin "http://localhost:8000"
    header @origin Access-Control-Request-Method GET
    header Access-Control-Allow-Credentials "true"

    mercure {
        # Publisher JWT key
        publisher_jwt "eyJhbGciOiJIUzI1NiIsInR5cCI6IkpXVCJ9.eyJtZXJjdXJlIjp7InB1Ymxpc2giOlsiKiJdfX0.iHLdpAEjX4BqCsHJEegxRmO-Y6sMxXwNATrQyRNt3GY"

        subscriber_jwt "eyJhbGciOiJIUzI1NiIsInR5cCI6IkpXVCJ9.eyJtZXJjdXJlIjp7InN1YnNjcmliZSI6WyIqIl19fQ.0J4jgLQSHzPMTZ5jiYM8Yv7jmTANRgILG5FKY98LgEU"

        publish_origins *
        # Extra directives
        {$MERCURE_EXTRA_DIRECTIVES}
    }

    respond /healthz 200

    respond "Not Found" 404
}

at this point, i feel like i'm using every tricks in the book and it still doesn't work, i'm probably gonna end up moving to periodic fetch instead, reliable and actually work.

Update 1:

  • Server Start on port 3000
  • CORS is now allowed
  • 401 whenever i try to request a topic

Update 2:

No HTTP errors, event is apparently subscribed from what mercure say, but nothing received, something tell me mercure doesn't support semantics

Update 3:

With a topic such as /update/test/{0}, set in the update, the event source and yaml config, no updates are received on the client

Update 4:

i cannot get updates to work at all, either with or without semantics, here is my config and route atm:

mercure.yaml :

mercure:
    hubs:
        default:
            url: '%env(MERCURE_URL)%'
            public_url: '%env(MERCURE_PUBLIC_URL)%'
            jwt:
                secret: '%env(MERCURE_JWT_SECRET)%'
                publish: '["/update/test/*"]'
                subscribe: '["/update/test/*"]'

My route (DefaultController):

#[Route('/update/test/{id}', name: 'test')]
public function test(HubInterface $hub, ?int $id): Response
{
    $update = new Update("localhost:8000/update/test/{$id}",
    son_encode(
        array(
                 "result" => "success",
                 "data" => "User is in Room {$id}"
             )
        )
    );

    $hub->publish($update);

    return new Response("Success");
}

CodePudding user response:

I will give you an example of mercure running over windows without use of Docker, sorry for my english ok?!!

I will start from an installation of symfony 5.4 with a SSL virtual host: https://myshop.local and MercureBundle.

The goal is to notify for example, to all authenticated users when a new product enter on a shop stock.

First: the mercure hub and your site must run over the same domain or subdomain, in other case isn't possible to share the auth cookie; so our mercure hub will run on https://myshop.local:3000 (the same domain, but another port, check your firewall)

Second: It's neccesary to set the appropiates environment variables, so in your .env.local:

###> symfony/mercure-bundle ###
# See https://symfony.com/doc/current/mercure.html#configuration
# The URL of the Mercure hub, used by the app to publish updates (can be a local URL)
MERCURE_URL=https://myshop.local:3000/.well-known/mercure
# The public URL of the Mercure hub, used by the browser to connect
MERCURE_PUBLIC_URL=https://myshop.local:3000/.well-known/mercure
# The secret used to sign the JWTs
MERCURE_JWT_SECRET=m3rcu353cr37pa55pra53DEV
###< symfony/mercure-bundle ###

third: Mercure it´s based on topics concept (likes a channels), so your mercure.yaml recipe:

mercure:
    hubs:
        default:
            url: '%env(MERCURE_URL)%'
            public_url: '%env(MERCURE_PUBLIC_URL)%'
            jwt:
                secret: '%env(MERCURE_JWT_SECRET)%'
                publish: ['notif/out-of-stock'] # the topic or topics where the notifications will be published coming from the mercure hub.
                subscribe: ['notif/out-of-stock'] # the topic or topics to which users will subscribe to receive notifications.

fourth: You need to download the Windows binaries of mercure, and set somes configs in the Caddy file:

# Learn how to configure the Mercure.rocks Hub on https://mercure.rocks/docs/hub/config
{
    {$GLOBAL_OPTIONS}
}

{$SERVER_NAME:myshop.local:3000}

log

tls C:\wamp64\bin\apache\apache2.4.41\conf\ssl\myshop.local.crt C:\wamp64\bin\apache\apache2.4.41\conf\ssl\myshop.local.key

route {
    encode zstd gzip

    mercure {
        # Transport to use (default to Bolt)
        transport_url {$MERCURE_TRANSPORT_URL:bolt://mercure.db}
        # Publisher JWT key
        publisher_jwt {env.MERCURE_PUBLISHER_JWT_KEY} {env.MERCURE_PUBLISHER_JWT_ALG}
        # Subscriber JWT key
        subscriber_jwt {env.MERCURE_SUBSCRIBER_JWT_KEY} {env.MERCURE_SUBSCRIBER_JWT_ALG}
        cors_origins https://myshop.local
        publish_origins https://myshop.local
        # Extra directives
        {$MERCURE_EXTRA_DIRECTIVES}
    }

    respond /healthz 200

    respond "Not Found" 404
}

three important aspects here:

  1. $SERVER_NAME
  2. log directive: you must set the paths of the .crt and .key files of the SSL certificate of your virtualhost.
  3. cors_origins and publish_origins must set your webapp domain to avoid CORS errors

fifth: you must run your mercure server from your power shell; from inside your mercure binary directory run: $env:MERCURE_PUBLISHER_JWT_KEY='m3rcu353cr37pa55pra53DEV';$env:MERCURE_SUBSCRIBER_JWT_KEY='m3rcu353cr37pa55pra53DEV';.\mercure.exe run -config .\Caddyfile

Important!!: that key is the same of enviroment var MERCURE_JWT_SECRET

Now from a controller, you must publish on a topic a notification:

public function publishNewProductAction(Symfony\Component\Mercure\HubInterface $mercureHub):Response{
           /** insert new product to a database **/
             ...
          $em->persist($newProduct);
          $em->flush();
    
        $data=['productName'=>$newProduct->getName(), 'productPrice'=>$newProduct->getPrice()];
    
          $update = new Symfony\Component\Mercure\Update();
          $hubUpdate = new Symfony\Component\Mercure\Update(
                'notif/new-product', # the topic where you go to publish
                \json_encode($data), # the data that you want to publish on the mercure hub
                true # indicate that need auth
        );
        
        return new Response('A new product has been registered!!'); # its the normal response to user that insert a product (admin user for exmple)
        }

Important: the real power of symfony to create a real-time notification system is in combining the Messenger component and async queue with Mercure. You can investigate about it.

So, on the client side for example:

<script type="text/javascript">
                $(document).ready(function () {

                        const eventSource = new EventSource("{{ mercure('notif/out-of-stock', { subscribe:'notif/out-of-stock'})|escape('js')}}", {withCredentials: true});

                        eventSource.onopen = function () {
                            console.log('Socket connection!');
                        };

                        eventSource.onmessage = function (e) {
                            var data = JSON.parse(e.data);
                            console.log('A new product is enable for you: ' data.productName '. Price: $' data.price);
                        };

                        eventSource.onerror = function () {
                            console.log('Socket error!');
                        };

                    });
            </script>

CodePudding user response:

You have somes errors. First: in your mercure.yaml you must stablish the topics on this way:

publish: ["update/test/{id}"] # your topic must complie with this format
subscribe: ["update/test/{id}"]

In your enviroment variables, you set MERCURE_JWT_SECRET, with that and the topics and subscribers declared in mercure.yaml, the symfony-mercure-bundle complete the JWT passed to the hub, you don't need to set it into de Caddy file.

And, in your function:

#[Route('/update/test/{id}', name: 'test')]
public function test(HubInterface $hub, ?int $id): Response
{
    $update = new Update(sprintf("update/test/%s", $id)),
    json_encode(
        array(
                 "result" => "success",
                 "data" => sprintf("User is in Room %s", $id)
             )
        )
    );

    $hub->publish($update);

    return new Response("Success");
}

Be have in account that the URI segment passed as parameter of Update, does not contain the characters '{}'

  • Related