Thanks to visit codestin.com
Credit goes to github.com

Skip to content

[Mercure] update mercure documentation #15149

New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Merged
merged 1 commit into from
Apr 6, 2021
Merged
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
169 changes: 114 additions & 55 deletions mercure.rst
Original file line number Diff line number Diff line change
Expand Up @@ -159,20 +159,19 @@ service, including controllers::
namespace App\Controller;

use Symfony\Component\HttpFoundation\Response;
use Symfony\Component\Mercure\PublisherInterface;
use Symfony\Component\Mercure\HubInterface;
use Symfony\Component\Mercure\Update;

class PublishController
{
public function __invoke(PublisherInterface $publisher): Response
public function __invoke(HubInterface $hub): Response
{
$update = new Update(
'http://example.com/books/1',
json_encode(['status' => 'OutOfStock'])
);

// The Publisher service is an invokable object
$publisher($update);
$hub->publish($update);

return new Response('published!');
}
Expand Down Expand Up @@ -297,17 +296,14 @@ by using the ``AbstractController::addLink`` helper method::
use Symfony\Bundle\FrameworkBundle\Controller\AbstractController;
use Symfony\Component\HttpFoundation\JsonResponse;
use Symfony\Component\HttpFoundation\Request;
use Symfony\Component\WebLink\Link;
use Symfony\Component\Mercure\Discovery;

class DiscoverController extends AbstractController
{
public function __invoke(Request $request): JsonResponse
public function __invoke(Request $request, Discovery $discovery): JsonResponse
{
// This parameter is automatically created by the MercureBundle
$hubUrl = $this->getParameter('mercure.default_hub');

// Link: <http://localhost:3000/.well-known/mercure>; rel="mercure"
$this->addLink($request, new Link('mercure', $hubUrl));
$discovery->addLink($request);

return $this->json([
'@id' => '/books/1',
Expand Down Expand Up @@ -346,13 +342,13 @@ of the ``Update`` constructor to ``true``::
// src/Controller/Publish.php
namespace App\Controller;

use Symfony\Bundle\FrameworkBundle\Controller\AbstractController;
use Symfony\Component\HttpFoundation\Response;
use Symfony\Component\Mercure\PublisherInterface;
use Symfony\Component\Mercure\Update;

class PublishController
class PublishController extends AbstractController
{
public function __invoke(PublisherInterface $publisher): Response
public function __invoke(HubInterface $hub): Response
{
$update = new Update(
'http://example.com/books/1',
Expand All @@ -362,7 +358,7 @@ of the ``Update`` constructor to ``true``::

// Publisher's JWT must contain this topic, a URI template it matches or * in mercure.publish or you'll get a 401
// Subscriber's JWT must contain this topic, a URI template it matches or * in mercure.subscribe to receive the update
$publisher($update);
$hub->publish($update);

return new Response('private update published!');
}
Expand Down Expand Up @@ -406,44 +402,71 @@ This cookie will be automatically sent by the web browser when connecting to the
Then, the Hub will verify the validity of the provided JWT, and extract the topic selectors
from it.

To generate the JWT, we'll use the ``lcobucci/jwt`` library. Install it:
add your JWT secret to the configuration as follow ::

.. code-block:: terminal
.. configuration-block::

.. code-block:: yaml

# config/packages/mercure.yaml
mercure:
hubs:
default:
url: https://mercure-hub.example.com/.well-known/mercure
jwt:
secret: '!ChangeMe!'

.. code-block:: xml

<!-- config/packages/mercure.xml -->
<?xml version="1.0" encoding="UTF-8" ?>
<config>
<hub
name="default"
url="https://mercure-hub.example.com/.well-known/mercure"
>
<jwt secret="!ChangeMe!"/>
</hub>
</config>

.. code-block:: php

$ composer require lcobucci/jwt
// config/packages/mercure.php
$container->loadFromExtension('mercure', [
'hubs' => [
'default' => [
'url' => 'https://mercure-hub.example.com/.well-known/mercure',
'jwt' => [
'secret' => '!ChangeMe!',
]
],
],
]);

And here is the controller::

// src/Controller/DiscoverController.php
namespace App\Controller;

use Lcobucci\JWT\Configuration;
use Lcobucci\JWT\Signer\Hmac\Sha256;
use Lcobucci\JWT\Signer\Key;
use Symfony\Bundle\FrameworkBundle\Controller\AbstractController;
use Symfony\Component\HttpFoundation\Request;
use Symfony\Component\HttpFoundation\Response;
use Symfony\Component\WebLink\Link;
use Symfony\Component\Mercure\Authorization;
use Symfony\Component\Mercure\Discovery;

class DiscoverController extends AbstractController
{
public function __invoke(Request $request): Response
public function __invoke(Request $request, Discovery $discovery, Authorization $authorization): Response
{
$hubUrl = $this->getParameter('mercure.default_hub');
$this->addLink($request, new Link('mercure', $hubUrl));

$key = Key\InMemory::plainText('mercure_secret_key'); // don't forget to set this parameter! Test value: !ChangeMe!
$configuration = Configuration::forSymmetricSigner(new Sha256(), $key);
$discovery->addLink($request);

$token = $configuration->builder()
->withClaim('mercure', ['subscribe' => ["http://example.com/books/1"]]) // can also be a URI template, or *
->getToken($configuration->signer(), $configuration->signingKey())
->toString();
$response = new JsonResponse([
'@id' => '/demo/books/1',
'availability' => 'https://schema.org/InStock'
]);

$response = $this->json(['@id' => '/demo/books/1', 'availability' => 'https://schema.org/InStock']);
$response->headers->set(
'set-cookie',
sprintf('mercureAuthorization=%s; path=/.well-known/mercure; secure; httponly; SameSite=strict', $token)
$response->headers->setCookie(
$authorization->createCookie($request, ["http://example.com/books/1"])
);

return $response;
Expand All @@ -459,15 +482,17 @@ Programmatically Generating The JWT Used to Publish
---------------------------------------------------

Instead of directly storing a JWT in the configuration,
you can create a service that will return the token used by
the ``Publisher`` object::
you can create a token provider that will return the token used by
the ``HubInterface`` object::

// src/Mercure/MyJwtProvider.php
// src/Mercure/MyTokenProvider.php
namespace App\Mercure;

final class MyJwtProvider
use Symfony\Component\Mercure\JWT\TokenProviderInterface;

final class MyTokenProvider implements TokenProviderInterface
{
public function __invoke(): string
public function getToken(): string
{
return 'the-JWT';
}
Expand All @@ -484,7 +509,8 @@ Then, reference this service in the bundle configuration:
hubs:
default:
url: https://mercure-hub.example.com/.well-known/mercure
jwt_provider: App\Mercure\MyJwtProvider
jwt:
provider: App\Mercure\MyTokenProvider

.. code-block:: xml

Expand All @@ -494,8 +520,9 @@ Then, reference this service in the bundle configuration:
<hub
name="default"
url="https://mercure-hub.example.com/.well-known/mercure"
jwt-provider="App\Mercure\MyJwtProvider"
/>
>
<jwt provider="App\Mercure\MyTokenProvider"/>
</hub>
</config>

.. code-block:: php
Expand All @@ -507,7 +534,9 @@ Then, reference this service in the bundle configuration:
'hubs' => [
'default' => [
'url' => 'https://mercure-hub.example.com/.well-known/mercure',
'jwt_provider' => MyJwtProvider::class,
'jwt' => [
'provider' => MyJwtProvider::class,
]
],
],
]);
Expand Down Expand Up @@ -568,29 +597,59 @@ its Mercure support.
Testing
--------

During functional testing there is no need to send updates to Mercure. They will
be handled by a stub publisher::
During unit testing there is not need to send updates to Mercure.

You can instead make use of the `MockHub`::

// tests/Functional/.php
namespace App\Tests\Unit\Controller;

// tests/Functional/Fixtures/PublisherStub.php
use App\Controller\MessageController;
use Symfony\Component\Mercure\HubInterface;
use Symfony\Component\Mercure\JWT\StaticTokenProvider;
use Symfony\Component\Mercure\MockHub;
use Symfony\Component\Mercure\Update;

class MessageControllerTest extends TestCase
{
public function testPublishing()
{
$hub = new MockHub('default', 'https://internal/.well-known/mercure', new StaticTokenProvider('foo'), function(Update $update): string {
// $this->assertTrue($update->isPrivate());

return 'id';
});

$controller = new MessageController($hub);

...
}
}

During functional testing you can instead decorate the Hub::

// tests/Functional/Fixtures/HubStub.php
namespace App\Tests\Functional\Fixtures;

use Symfony\Component\Mercure\PublisherInterface;
use Symfony\Component\Mercure\HubInterface;
use Symfony\Component\Mercure\Update;

class PublisherStub implements PublisherInterface
class HubStub implements HubInterface
{
public function __invoke(Update $update): string
public function publish(Update $update): string
{
return '';
return 'id';
}

// implement rest of HubInterface methods here
}

PublisherStub decorates the default publisher service so no updates are actually
sent. Here is the PublisherStub implementation::
HubStub decorates the default hub service so no updates are actually
sent. Here is the HubStub implementation::

# config/services_test.yaml
App\Tests\Functional\Fixtures\PublisherStub:
decorates: mercure.hub.default.publisher
App\Tests\Functional\Fixtures\HubStub:
decorates: mercure.hub.default


Debugging
Expand Down