Skip to content

Commit 764d67c

Browse files
committed
minor #16293 [Mercure] Compatibility with the Docker integration and various improvements (tchapi, dunglas)
This PR was merged into the 5.3 branch. Discussion ---------- [Mercure] Compatibility with the Docker integration and various improvements Includes #16151. Closes dunglas/symfony-docker#200. Commits ------- ad0cbe5 nitpicking adf49ef Update mercure.rst daaa3f1 Update mercure.rst ec87085 Update mercure.rst b4fb9fb Update mercure.rst 3ca1aa1 Update mercure.rst 81c1387 Update mercure.rst e35f1cc Update mercure.rst 7496149 review 142a6f9 [mercure] Compatibility with the Docker integration and various improvements 123ad73 Remove unneeded JWT reference f948ab2 Update mercure.rst regarding JWT token secret
2 parents 7cc0866 + ad0cbe5 commit 764d67c

File tree

1 file changed

+112
-83
lines changed

1 file changed

+112
-83
lines changed

mercure.rst

+112-83
Original file line numberDiff line numberDiff line change
@@ -17,12 +17,12 @@ requiring "push" capabilities.
1717
Symfony provides a straightforward component, built on top of
1818
`the Mercure protocol`_, specifically designed for this class of use cases.
1919

20-
Mercure is an open protocol designed from the ground to publish updates from
20+
Mercure is an open protocol designed from the ground up to publish updates from
2121
server to clients. It is a modern and efficient alternative to timer-based
2222
polling and to WebSocket.
2323

2424
Because it is built on top `Server-Sent Events (SSE)`_, Mercure is supported
25-
out of the box in most modern browsers (old versions of Edge and IE require
25+
out of the box in modern browsers (old versions of Edge and IE require
2626
`a polyfill`_) and has `high-level implementations`_ in many programming
2727
languages.
2828

@@ -42,49 +42,44 @@ generated using the API Platform client generator.
4242
Installation
4343
------------
4444

45-
Running a Mercure Hub
46-
~~~~~~~~~~~~~~~~~~~~~
45+
Installing the Symfony Bundle
46+
~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
47+
48+
Run this command to install the Mercure support:
49+
50+
.. code-block:: terminal
51+
52+
$ composer require mercure
4753
4854
To manage persistent connections, Mercure relies on a Hub: a dedicated server
4955
that handles persistent SSE connections with the clients.
5056
The Symfony app publishes the updates to the hub, that will broadcast them to
5157
clients.
5258

53-
.. image:: /_images/mercure/schema.png
54-
55-
An official and open source (AGPL) Hub based on the Caddy web server
56-
can be downloaded as a static binary from `Mercure.rocks`_.
57-
A Docker image, a Helm chart for Kubernetes
58-
and a managed, High Availability Hub are also provided.
59-
60-
If you use `Symfony Docker`_ or the `API Platform distribution`_, a Mercure Hub
61-
is automatically installed and your Symfony application is automatically
62-
configured to use it. You can jump directly to the next section.
59+
Thanks to :ref:`the Docker integration of Symfony </setup/docker>`,
60+
:ref:`Flex <symfony-flex>` proposes to install a Mercure hub.
61+
Run ``docker-compose up`` to start the hub if you have chosen this option.
6362

6463
If you use the :doc:`Symfony Local Web Server </setup/symfony_server>`,
65-
a Mercure hub will be automatically available as a Docker service thanks to its
66-
:ref:`Docker integration <symfony-server-docker>.
67-
68-
Be sure that recent versions of Docker and Docker Compose are properly installed
69-
on your computer and to start the Symfony Local Web Server with the ``--no-tls``
70-
option:
64+
you must start it with the ``--no-tls`` option.
7165

7266
.. code-block:: terminal
7367
7468
$ symfony server:start --no-tls -d
7569
76-
Installing the Symfony Bundle
77-
~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
78-
79-
Run this command to install the Mercure support before using it:
70+
Running a Mercure Hub
71+
~~~~~~~~~~~~~~~~~~~~~
8072

81-
.. code-block:: terminal
73+
.. image:: /_images/mercure/schema.png
8274

83-
$ composer require mercure
75+
If you use the Docker integration, a hub is already up and running,
76+
and you can go straight to the next section.
8477

85-
:ref:`Symfony Flex <symfony-flex>` has automatically installed and configured
86-
MercureBundle. It also created (if needed) and configured a Docker Compose
87-
definition that provides a Mercure service. Run ``docker-compose up`` to start it.
78+
Otherwise, and in production, you have to install a hub by yourself.
79+
An official and open source (AGPL) Hub based on the Caddy web server
80+
can be downloaded as a static binary from `Mercure.rocks`_.
81+
A Docker image, a Helm chart for Kubernetes
82+
and a managed, High Availability Hub are also provided.
8883

8984
Configuration
9085
-------------
@@ -95,57 +90,37 @@ The preferred way to configure MercureBundle is using
9590
When MercureBundle has been installed, the ``.env`` file of your project
9691
has been updated by the Flex recipe to include the available env vars.
9792

98-
If you use the Symfony Local Web Server, Symfony Docker or the API Platform
99-
distribution, the Symfony app is automatically configured and you can skip
100-
straight to the next section.
93+
Also, if you are using the Docker integration with the Symfony Local Web Server,
94+
`Symfony Docker`_ or the `API Platform distribution`_,
95+
the proper environment variables have been automatically set.
96+
Skip straight to the next section.
10197

10298
Otherwise, set the URL of your hub as the value of the ``MERCURE_URL``
10399
and ``MERCURE_PUBLIC_URL`` env vars.
104100
Sometimes a different URL must be called by the Symfony app (usually to publish),
105101
and the JavaScript client (usually to subscribe). It's especially common when
106102
the Symfony app must use a local URL and the client-side JavaScript code a public one.
107-
In this case, ``MERCURE_URL`` must contain the local URL that will be used by the
103+
In this case, ``MERCURE_URL`` must contain the local URL used by the
108104
Symfony app (e.g. ``https://mercure/.well-known/mercure``), and ``MERCURE_PUBLIC_URL``
109105
the publicly available URL (e.g. ``https://example.com/.well-known/mercure``).
110106

111107
The clients must also bear a `JSON Web Token`_ (JWT)
112108
to the Mercure Hub to be authorized to publish updates and, sometimes, to subscribe.
113109

114-
This JWT should be stored in the ``MERCURE_JWT_SECRET`` environment variable.
110+
This token must be signed with the same secret key as the one used by the Hub to verify the JWT (``!ChangeMe!`` in you use the Docker integration).
111+
This secret key must be stored in the ``MERCURE_JWT_SECRET`` environment variable.
112+
MercureBundle will use it to automatically generate and sign the needed JWTs.
115113

116-
The JWT must be signed with the same secret key as the one used by
117-
the Hub to verify the JWT (``!ChangeMe!`` in you use the Local Web Server or
118-
Symfony Docker).
119-
Its payload must contain at least the following structure to be allowed to
120-
publish:
114+
In addition to these environment variables,
115+
MercureBundle provides a more advanced configuration configuration:
121116

122-
.. code-block:: json
123-
124-
{
125-
"mercure": {
126-
"publish": []
127-
}
128-
}
129-
130-
Because the array is empty, the Symfony app will only be authorized to publish
131-
public updates (see the authorization_ section for further information).
132-
133-
.. tip::
134-
135-
The jwt.io website is a convenient way to create and sign JWTs.
136-
Checkout this `example JWT`_, that grants publishing rights for all *topics*
137-
(notice the star in the array).
138-
Don't forget to set your secret key properly in the bottom of the right panel of the form!
139-
140-
.. caution::
141-
142-
Don't put the secret key in ``MERCURE_JWT_SECRET``, it will not work!
143-
This environment variable must contain a JWT, signed with the secret key.
144-
145-
Also, be sure to keep both the secret key and the JWTs... secrets!
146-
147-
If you don't want to use the provided environment variables,
148-
use the following configuration:
117+
* ``secret``: the key to use to sign the JWT (all other options, beside `algorithm`, `subscribe`, and `publish` will be ignored)
118+
* ``publish``: a list of topics to allow publishing to when generating the JWT (only usable when `secret`, or `factory` are provided)
119+
* ``subscribe``: a list of topics to allow subscribing to when generating the JWT (only usable when `secret`, or `factory` are provided)
120+
* ``algorithm``: The algorithm to use to sign the JWT (only usable when `secret` is provided)
121+
* ``provider``: The ID of a service to call to provide the JWT (all other options will be ignored)
122+
* ``factory``: The ID of a service to call to create the JWT (all other options, beside `subscribe`, and `publish` will be ignored)
123+
* ``value``: the raw JWT to use (all other options will be ignored)
149124

150125
.. configuration-block::
151126

@@ -158,6 +133,12 @@ use the following configuration:
158133
url: https://mercure-hub.example.com/.well-known/mercure
159134
jwt:
160135
secret: '!ChangeMe!'
136+
publish: ['foo', 'https://example.com/foo']
137+
subscribe: ['bar', 'https://example.com/bar']
138+
algorithm: 'hmac.sha256'
139+
provider: 'My\Provider'
140+
factory: 'My\Factory'
141+
value: 'my.jwt'
161142
162143
.. code-block:: xml
163144
@@ -168,7 +149,18 @@ use the following configuration:
168149
name="default"
169150
url="https://mercure-hub.example.com/.well-known/mercure"
170151
>
171-
<jwt secret="!ChangeMe!"/>
152+
<jwt
153+
secret="!ChangeMe!"
154+
algorithm="hmac.sha256"
155+
provider="My\Provider"
156+
factory="My\Factory"
157+
value="my.jwt"
158+
>
159+
<publish>foo</publish>
160+
<publish>https://example.com/foo</publish>
161+
<subscribe>bar</subscribe>
162+
<subscribe>https://example.com/bar</subscribe>
163+
</jwt>
172164
</hub>
173165
</config>
174166
@@ -181,11 +173,37 @@ use the following configuration:
181173
'url' => 'https://mercure-hub.example.com/.well-known/mercure',
182174
'jwt' => [
183175
'secret' => '!ChangeMe!',
176+
'publish' => ['foo', 'https://example.com/foo'],
177+
'subscribe' => ['bar', 'https://example.com/bar'],
178+
'algorithm' => 'hmac.sha256',
179+
'provider' => 'My\Provider',
180+
'factory' => 'My\Factory',
181+
'value' => 'my.jwt',
184182
],
185183
],
186184
],
187185
]);
188186
187+
.. tip::
188+
189+
The JWT payload must contain at least the following structure to be allowed to
190+
publish:
191+
192+
.. code-block:: json
193+
194+
{
195+
"mercure": {
196+
"publish": []
197+
}
198+
}
199+
200+
Because the array is empty, the Symfony app will only be authorized to publish
201+
public updates (see the authorization_ section for further information).
202+
203+
The jwt.io website is a convenient way to create and sign JWTs.
204+
Checkout this `example JWT`_, that grants publishing rights for all *topics*
205+
(notice the star in the array).
206+
Don't forget to set your secret key properly in the bottom of the right panel of the form!
189207

190208
Basic Usage
191209
-----------
@@ -253,8 +271,8 @@ Subscribing to updates in JavaScript from a Twig template is straightforward:
253271
}
254272
</script>
255273
256-
The ``mercure()`` Twig function will generate the URL of the Mercure hub
257-
according to the configuration. The URL will include the ``topic`` query
274+
The ``mercure()`` Twig function generates the URL of the Mercure hub
275+
according to the configuration. The URL includes the ``topic`` query
258276
parameters corresponding to the topics passed as first argument.
259277

260278
If you want to access to this URL from an external JavaScript file, generate the
@@ -333,9 +351,9 @@ by using the ``AbstractController::addLink`` helper method::
333351

334352
class DiscoverController extends AbstractController
335353
{
336-
public function __invoke(Request $request, Discovery $discovery): JsonResponse
354+
public function discover(Request $request, Discovery $discovery): JsonResponse
337355
{
338-
// Link: <http://localhost:3000/.well-known/mercure>; rel="mercure"
356+
// Link: <https://hub.example.com/.well-known/mercure>; rel="mercure"
339357
$discovery->addLink($request);
340358

341359
return $this->json([
@@ -351,7 +369,7 @@ and to subscribe to it:
351369
.. code-block:: javascript
352370
353371
// Fetch the original resource served by the Symfony web API
354-
fetch('/books/1') // Has Link: <http://localhost:3000/.well-known/mercure>; rel="mercure"
372+
fetch('/books/1') // Has Link: <https://hub.example.com/.well-known/mercure>; rel="mercure"
355373
.then(response => {
356374
// Extract the hub URL from the Link header
357375
const hubUrl = response.headers.get('Link').match(/<([^>]+)>;\s+rel=(?:mercure|"[^"]*mercure[^"]*")/)[1];
@@ -404,9 +422,9 @@ To provide this JWT, the subscriber can use a cookie,
404422
or a ``Authorization`` HTTP header.
405423

406424
Cookies can be set automatically by Symfony by passing the appropriate options
407-
to the ``mercure()`` Twig function. Cookies set by Symfony will be automatically
425+
to the ``mercure()`` Twig function. Cookies set by Symfony are automatically
408426
passed by the browsers to the Mercure hub if the ``withCredentials`` attribute
409-
of the ``EventSource`` class is set to ``true``. Then, the Hub will verify the
427+
of the ``EventSource`` class is set to ``true``. Then, the Hub verifies the
410428
validity of the provided JWT, and extract the topic selectors from it.
411429

412430
.. code-block:: twig
@@ -482,6 +500,14 @@ And here is the controller::
482500
}
483501
}
484502

503+
504+
.. tip::
505+
506+
You cannot use the ``mercure()`` helper and the ``setCookie()``
507+
method at the same time (it would set the cookie twice on a single request). Choose
508+
either one method or the other.
509+
510+
485511
Programmatically Generating The JWT Used to Publish
486512
---------------------------------------------------
487513

@@ -595,9 +621,9 @@ its Mercure support.
595621
Testing
596622
--------
597623

598-
During unit testing there is no need to send updates to Mercure.
624+
During unit testing it's usually not needed to send updates to Mercure.
599625

600-
You can instead make use of the `MockHub`::
626+
You can instead make use of the `MockHub` class::
601627

602628
// tests/FunctionalTest.php
603629
namespace App\Tests\Unit\Controller;
@@ -624,10 +650,10 @@ You can instead make use of the `MockHub`::
624650
}
625651
}
626652

627-
During functional testing you can instead decorate the Hub::
653+
For functional testing, you can instead create a stub of the Hub::
628654

629-
// tests/Functional/Fixtures/HubStub.php
630-
namespace App\Tests\Functional\Fixtures;
655+
// tests/Functional/Stub/HubStub.php
656+
namespace App\Tests\Functional\Stub;
631657

632658
use Symfony\Component\Mercure\HubInterface;
633659
use Symfony\Component\Mercure\Update;
@@ -642,14 +668,17 @@ During functional testing you can instead decorate the Hub::
642668
// implement rest of HubInterface methods here
643669
}
644670

645-
HubStub decorates the default hub service so no updates are actually
646-
sent. Here is the HubStub implementation:
671+
Use ``HubStub`` to replace the default hub service so no updates are actually
672+
sent:
647673

648674
.. code-block:: yaml
649675
650676
# config/services_test.yaml
651-
App\Tests\Functional\Fixtures\HubStub:
652-
decorates: mercure.hub.default
677+
mercure.hub.default:
678+
class: App\Tests\Functional\Stub\HubStub
679+
680+
As MercureBundle support multiple hubs, you may have to replace
681+
the other service definitions accordingly.
653682

654683
.. tip::
655684

0 commit comments

Comments
 (0)