ntavelis/mercure-php

向 mercure 站点发布消息

v1.1.0 2021-11-13 15:42 UTC

README

Latest Version on Packagist Software License CI codecov

此包从您的 PHP 应用程序向 mercure 站点发布通知。这些消息可以从客户端(Web 浏览器或移动应用程序)进一步消费,以提供应用程序的实时更新。这一切都归功于 Mercure 协议,您可以在这里了解更多关于该协议的信息。

dunglas 致敬,他在 mercure 项目 中的工作。

安装

通过 Composer 安装此包

$ composer require ntavelis/mercure-php

Mercure 站点安装

Mercure 站点是使用 GO 语言编写的二进制文件,应该启动并运行,以便从 PHP 应用程序接收消息。

请参阅官方文档了解如何安装站点

发送公开通知

我们需要从我们的 PHP 服务器向 mercure 站点发布消息,然后在我们的客户端(例如通过 JavaScript 浏览器)消费它们。

PHP 代码

以下示例是一个控制器,在 symfony 框架中

<?php

namespace App\Controller;

use Ntavelis\Mercure\Messages\Notification;
use Ntavelis\Mercure\Providers\PublisherTokenProvider;
use Ntavelis\Mercure\Publisher;
use Symfony\Bundle\FrameworkBundle\Controller\AbstractController;
use Symfony\Component\HttpClient\Psr18Client;
use Symfony\Component\HttpFoundation\JsonResponse;
use Symfony\Component\Routing\Annotation\Route;

class PublishController extends AbstractController
{
    /**
     * @Route("/publish", name="publish")
     */
    public function index()
    {
        $notification = new Notification(['https:///books/2'], ['data' => 'new public event']);
        
        $publisher = new Publisher(
            'https://:3000/.well-known/mercure',
            new PublisherTokenProvider('aVerySecretKey'),
            new Psr18Client()
        );

        $publisher->send($notification);

        return new JsonResponse(['success']);
    }
}

注意:当我们初始化发布者时,我们需要传递一个 PSR-18 兼容的客户端,在我们的例子中我们使用 symfony/http-client。此包不提供客户端,您需要自己初始化并传递一个给发布者。例如,要提供 symfony http-client,您需要先通过 composer 安装它

composer require symfony/http-client

提示:您可以通过使用流畅 API 来手动构建类,以达到相同的结果。

通知类

Ntavelis\Mercure\Messages\Notification 的第一个参数是要发布通知的主题数组。主题可以是任何对您有意义的字符串,例如 'orders'、'clients'、'notes'、'https:///books/2' 等。第二个参数是要传递给客户端的数据数组,该数组将被 json 编码,并从客户端接收,客户端可以据此采取行动。

发布者类

这是一个实际上将通知发送到 mercure 站点的类,它期望在实例化时接收三个参数。Mercure 站点 URL、实现 Ntavelis\Mercure\Contracts\TokenProviderInterface 的类(您可以使用包中的一个,或提供自己的)以及上述提到的 PSR-18 兼容客户端的实例。

客户端 JavaScript 代码

为了消费上述公开消息,我们的客户端代码将如下所示

// The subscriber subscribes to updates for any topic matching https:///books/{id}
const url = new window.URL('https://:3000/.well-known/mercure');
url.searchParams.append('topic', 'https:///books/{id}');

const eventSource = new EventSource(url.toString());

// The callback will be called every time an update is published
eventSource.onmessage = e => {
    console.log(JSON.parse(e.data));// do something with the payload
};

注意:我们使用了一个通配符 id,所以我们将收到任何给定 {id} 的书籍的通知。

以上示例使用了原生 js 代码,没有使用任何库。请参阅EventSource 文档以获取更多信息。

可选地,我们可以为我们的主题指定一个特定的类型,并在前端仅监听该类型,更多信息 在这里

私有消息

与公共消息不同,私有消息并非面向所有人消费。私有消息是指仅面向已验证消费者消费的消息。

要发布和消费私有消息,我们需要三样东西

  1. 从我们的PHP服务器代码发布私有通知。
  2. 提供一个端点生成客户端的JWT令牌。
  3. 客户端向后端发送请求以获取JWT令牌,该令牌证明我们能够接收私有消息并使用收到的令牌订阅事件。

PHP代码(步骤1)

从我们的PHP服务器代码中,我们现在必须使用Ntavelis\Mercure\Messages\PrivateNotification类,该类接收与Notification类相同的参数,但将通知标记为私有。

<?php

namespace App\Controller;

use Ntavelis\Mercure\Messages\PrivateNotification;
use Ntavelis\Mercure\Providers\PublisherTokenProvider;
use Ntavelis\Mercure\Publisher;
use Symfony\Bundle\FrameworkBundle\Controller\AbstractController;
use Symfony\Component\HttpClient\Psr18Client;
use Symfony\Component\HttpFoundation\JsonResponse;
use Symfony\Component\Routing\Annotation\Route;

class PublishController extends AbstractController
{
    /**
     * @Route("/publish", name="publish")
     */
    public function index()
    {
        $notification = new PrivateNotification(
            ['https:///author/ntavelis/books/155'],
            ['data' => 'new private event']
        );

        $publisher = new Publisher(
            'https://:3000/.well-known/mercure',
            new PublisherTokenProvider('aVerySecretKey'),
            new Psr18Client()
        );

        $publisher->send($notification);

        return new JsonResponse(['success']);
    }
}

这就完成了,我们发布了一条仅针对用户ntavelis的私有消息,主题指定为https:///author/ntavelis/books/155。也许他是我们应用中书籍的作者,我们希望向客户端发送通知以更新他的私有仪表板。

提示:您可以通过使用流畅 API 来手动构建类,以达到相同的结果。

提供生成客户端令牌的端点(步骤2)

为了在我们的JavaScript中消费消息,我们需要在订阅中心时提供一个有效的令牌,以证明我们有权限接收私有通知。为此,我们可以向PHP端点发起Ajax请求以接收令牌。此包将为我们生成令牌,我们只需提供一个客户端可以调用的端点以接收令牌。

这是生成客户端(订阅者)令牌的PHP代码

<?php

namespace App\Controller;

use Ntavelis\Mercure\Providers\SubscriberTokenProvider;
use Symfony\Bundle\FrameworkBundle\Controller\AbstractController;
use Symfony\Component\HttpFoundation\JsonResponse;
use Symfony\Component\HttpFoundation\Request;
use Symfony\Component\Routing\Annotation\Route;

class SubscribeController extends AbstractController
{
    /**
     * @Route("/subscribe", name="subscribe")
     */
    public function index(Request $request)
    {
        $content = $request->getContent();

        $contentArray = json_decode($content, true);
        $topic = $contentArray['topic'];

        // TODO authorize the request
        $provider = new SubscriberTokenProvider('aVerySecretKey');
        $token = $provider->getToken([$topic]);

        return new JsonResponse(['token' => $token]);
    }
}

在上面的示例中,我们使用了Ntavelis\Mercure\Providers\SubscriberTokenProvider来获取特定主题的有效令牌。

注意:授权请求取决于您,您应该检查请求是否有效,并且它可以接收此主题的私有通知。

在客户端获取令牌并使用该令牌订阅事件(步骤3)

最后一步,将所有这些放在一起,从我们的客户端代码中获取令牌,并使用此令牌从中心订阅事件。

注意:我们将在本例中使用polyfill库将授权头传递给中心,因为EventSource不支持原生支持。

// use a polyfill library
import { EventSourcePolyfill } from 'event-source-polyfill';

// Make a post request to the server to obtain the token for the topic we want to receive notifications for
const token = fetch('/subscribe', {
    method: 'POST',
    headers: {
        'Content-Type': 'application/json',
    },
    body: JSON.stringify({topic: 'https:///author/ntavelis/books/155'}), // send the topic we need to authenticate on
}).then(response => response.json())
    .then((json) => json.token);

// When we have the token subscribe to the EventSource by passing the token
token.then((token) => {
    const url = new window.URL('https://:3000/.well-known/mercure');
    url.searchParams.append('topic', 'https:///author/ntavelis/books/155');
    // Authorization header
    const eventSourceInitDict = {
        headers: {
            'Authorization': 'Bearer ' + token
        }
    };
    const es = new EventSourcePolyfill(url.toString(), eventSourceInitDict);
    es.onmessage = e => {
        console.log(JSON.parse(e.data));
    };
});

请记住,您还可以使用基于cookie的认证连接到中心,您可以在此处了解更多信息 在这里

额外

如果您想为特定类型配置通知,请参阅文档 在这里

变更日志

请参阅 CHANGELOG 以获取有关最近更改的更多信息。

贡献

请参阅 CONTRIBUTINGCODE_OF_CONDUCT 以获取详细信息。

安全

如果您发现任何与安全相关的问题,请通过电子邮件 davelis89@gmail.com 发送,而不是使用问题跟踪器。

致谢

许可协议

MIT许可协议(MIT)。有关更多信息,请参阅 许可文件