web-push-libs/web-push

此包已被废弃,不再维护。作者建议使用 minishlink/web-push 包。

PHP Web Push 库

v5.2.5 2020-01-19 21:00 UTC

README

PHP Web Push 库

Build Status SensioLabsInsight

WebPush 可以用于向端点发送通知,服务器以 Web Push 协议的形式提供通知,如 Web Push 协议 所述。由于其标准化,您无需担心它依赖于哪种服务器类型。

要求

  • PHP 7.1+
    • gmp
    • mbstring
    • curl
    • openssl

推荐使用 PHP 7.2+ 以获得更好的性能。

不支持旧版本的 PHP,但您可以使用以下兼容版本

  • PHP 5.6 或 HHVM: v1.x
  • PHP 7.0: v2.x

安装

使用 composer 下载并安装库及其依赖项。

composer require minishlink/web-push

用法

<?php

use Minishlink\WebPush\WebPush;
use Minishlink\WebPush\Subscription;

// array of notifications
$notifications = [
    [
        'subscription' => Subscription::create([
            'endpoint' => 'https://updates.push.services.mozilla.com/push/abc...', // Firefox 43+,
            'publicKey' => 'BPcMbnWQL5GOYX/5LKZXT6sLmHiMsJSiEvIFvfcDvX7IZ9qqtq68onpTPEYmyxSQNiH7UD/98AUcQ12kBoxz/0s=', // base 64 encoded, should be 88 chars
            'authToken' => 'CxVX6QsVToEGEcjfYPqXQw==', // base 64 encoded, should be 24 chars
        ]),
        'payload' => 'hello !',
    ], [
        'subscription' => Subscription::create([
            'endpoint' => 'https://android.googleapis.com/gcm/send/abcdef...', // Chrome
        ]),
        'payload' => null,
    ], [
        'subscription' => Subscription::create([
            'endpoint' => 'https://example.com/other/endpoint/of/another/vendor/abcdef...',
            'publicKey' => '(stringOf88Chars)',
            'authToken' => '(stringOf24Chars)',
            'contentEncoding' => 'aesgcm', // one of PushManager.supportedContentEncodings
        ]),
        'payload' => '{msg:"test"}',
    ], [
          'subscription' => Subscription::create([ // this is the structure for the working draft from october 2018 (https://www.w3.org/TR/2018/WD-push-api-20181026/) 
              "endpoint" => "https://example.com/other/endpoint/of/another/vendor/abcdef...",
              "keys" => [
                  'p256dh' => '(stringOf88Chars)',
                  'auth' => '(stringOf24Chars)'
              ],
          ]),
          'payload' => '{"msg":"Hello World!"}',
      ],
];

$webPush = new WebPush();

// send multiple notifications with payload
foreach ($notifications as $notification) {
    $webPush->sendNotification(
        $notification['subscription'],
        $notification['payload'] // optional (defaults null)
    );
}

/**
 * Check sent results
 * @var MessageSentReport $report
 */
foreach ($webPush->flush() as $report) {
    $endpoint = $report->getRequest()->getUri()->__toString();

    if ($report->isSuccess()) {
        echo "[v] Message sent successfully for subscription {$endpoint}.";
    } else {
        echo "[x] Message failed to sent for subscription {$endpoint}: {$report->getReason()}";
    }
}

/**
 * send one notification and flush directly
 * @var \Generator<MessageSentReport> $sent
 */
$sent = $webPush->sendNotification(
    $notifications[0]['subscription'],
    $notifications[0]['payload'], // optional (defaults null)
    true // optional (defaults false)
);

Web Push 实现的完整示例

身份验证(VAPID)

浏览器需要验证您的身份。称为 VAPID 的标准可以为您在所有浏览器中提供身份验证。您需要为您的服务器创建并提供公钥和私钥。这些密钥必须安全存储,并且不应更改。

您可以在实例化 WebPush 时指定您的身份验证详细信息。键可以直接传递(推荐),或者您可以加载 PEM 文件或其内容

<?php

use Minishlink\WebPush\WebPush;

$endpoint = 'https://android.googleapis.com/gcm/send/abcdef...'; // Chrome

$auth = [
    'GCM' => 'MY_GCM_API_KEY', // deprecated and optional, it's here only for compatibility reasons
    'VAPID' => [
        'subject' => 'mailto:me@website.com', // can be a mailto: or your website address
        'publicKey' => '~88 chars', // (recommended) uncompressed public key P-256 encoded in Base64-URL
        'privateKey' => '~44 chars', // (recommended) in fact the secret multiplier of the private key encoded in Base64-URL
        'pemFile' => 'path/to/pem', // if you have a PEM file and can link to it on your filesystem
        'pem' => 'pemFileContent', // if you have a PEM file and want to hardcode its content
    ],
];

$webPush = new WebPush($auth);
$webPush->sendNotification(...);

为了生成未压缩的公钥和私钥,以 Base64 编码,请在您的 Linux bash 中输入以下内容

$ openssl ecparam -genkey -name prime256v1 -out private_key.pem
$ openssl ec -in private_key.pem -pubout -outform DER|tail -c 65|base64|tr -d '=' |tr '/+' '_-' >> public_key.txt
$ openssl ec -in private_key.pem -outform DER|tail -c +8|head -c 32|base64|tr -d '=' |tr '/+' '_-' >> private_key.txt

如果您无法访问 Linux bash,可以打印 createVapidKeys 函数的输出

var_dump(VAPID::createVapidKeys()); // store the keys afterwards

在客户端,不要忘记使用 VAPID 公钥订阅 applicationServerKey:(urlBase64ToUint8Array 来源 这里

serviceWorkerRegistration.pushManager.subscribe({
  userVisibleOnly: true,
  applicationServerKey: urlBase64ToUint8Array(vapidPublicKey)
})

重复使用 VAPID 标头

VAPID 标头通过 JSON Web Token (JWT) 验证您的身份。该令牌的有效负载包括订阅中包含的端点的协议和主机名以及过期时间戳(通常为 12-24 小时),并使用您的公钥和私钥进行签名。因此,发送给同一推送服务的两个通知将使用相同的令牌,您可以在相同的推送会话中重复使用它们以提高性能。

$webPush->setReuseVAPIDHeaders(true);

通知和默认选项

每个通知都可以有特定的存活时间、紧急程度和主题。您可以使用 setDefaultOptions() 或在构造函数中更改默认选项。

<?php

use Minishlink\WebPush\WebPush;

$defaultOptions = [
    'TTL' => 300, // defaults to 4 weeks
    'urgency' => 'normal', // protocol defaults to "normal"
    'topic' => 'new_event', // not defined by default,
    'batchSize' => 200, // defaults to 1000
];

// for every notifications
$webPush = new WebPush([], $defaultOptions);
$webPush->setDefaultOptions($defaultOptions);

// or for one notification
$webPush->sendNotification($subscription, $payload, $flush, ['TTL' => 5000]);

TTL

存活时间(TTL,以秒为单位)表示推送服务(例如 Mozilla)在用户浏览器尚未可访问的情况下保留推送消息的时间(例如,未连接)。对于重要的通知,您可能希望使用非常长的时间。默认 TTL 为 4 周。然而,如果您发送多个非重要通知,请设置 TTL 为 0:推送通知只有在用户当前连接时才会被投递。对于其他情况,如果您的用户具有多个时区,则应使用至少一天,如果他们没有,则几小时就足够了。

紧急程度

紧急程度可以是“非常低”、“低”、“正常”或“高”。如果浏览器厂商实现了此功能,则会在移动设备上节省电池寿命(参看 协议)。

主题

类似于旧 GCM 服务器上的 collapse_key,此字符串将使供应商仅向用户显示此主题的最后一条通知(参看 协议)。

batchSize

如果您一次发送成千上万的推送通知,由于 Guzzle 中端点的调用方式,您可能会出现内存溢出。为了解决这个问题,WebPush 会分批发送通知。默认大小为 1000。根据您的服务器配置(内存),您可能需要降低此数值。在实例化 WebPush 或调用 setDefaultOptions 时执行此操作。或者,如果您想为特定的推送自定义此值,请将其作为参数提供:$webPush->flush($batchSize)

服务器错误

您可以查看浏览器厂商的服务器在遇到错误(推送订阅过期、参数错误等)时发送回的内容。带有 $flushtruesendNotification()flush() 始终返回一个包含 \GeneratorMessageSentReport 对象的 \Generator,即使您只发送一条通知。只需将其传递到 foreach 中即可循环遍历结果。您还可以使用 iterator_to_array 在调试时检查内容。

<?php

/** @var \Minishlink\WebPush\MessageSentReport $report */
foreach ($webPush->flush() as $report) {
    $endpoint = $report->getEndpoint();

    if ($report->isSuccess()) {
        echo "[v] Message sent successfully for subscription {$endpoint}.";
    } else {
        echo "[x] Message failed to sent for subscription {$endpoint}: {$report->getReason()}";
        
        // also available (to get more info)
        
        /** @var \Psr\Http\Message\RequestInterface $requestToPushService */
        $requestToPushService = $report->getRequest();
        
        /** @var \Psr\Http\Message\ResponseInterface $responseOfPushService */
        $responseOfPushService = $report->getResponse();
        
        /** @var string $failReason */
        $failReason = $report->getReason();
        
        /** @var bool $isTheEndpointWrongOrExpired */
        $isTheEndpointWrongOrExpired = $report->isSubscriptionExpired();
    }
}

请注意:您只能对 \Generator 对象进行一次迭代。

Firefox 错误列在 autopush 文档 中。

有效负载长度、安全性和性能

库会对有效载荷进行加密。理论上的最大有效载荷长度为4078字节(或ASCII字符)。不过,由于兼容性的原因,您的有效载荷长度应小于3052字节。兼容性原因

库默认会对有效载荷进行填充。这更安全,但会降低您服务器和用户设备的性能。

为什么这更安全?

当您加密一定长度的字符串时,生成的字符串长度始终相同,无论您加密原始字符串多少次。这可能会让攻击者猜测有效载荷的内容。为了解决这个问题,该库会在原始有效载荷中添加一些空填充,使得加密过程中所有输入都具有相同的长度。这样,加密过程中所有输出也将具有相同的长度,攻击者将无法猜测您有效载荷的内容。

为什么它会降低性能?

加密更多字节会在您的服务器上消耗更多的时间,并且使用解密会减慢用户设备的速度。此外,发送和接收数据包也会花费更多时间。对有有限数据计划的用户来说也不太友好。

如何禁用或自定义自动填充?

您可以根据需要自定义自动填充。

以下是一些设置建议

  • (默认) Encryption::MAX_COMPATIBILITY_PAYLOAD_LENGTH (3052字节) 为了与Android上的Firefox兼容
  • Encryption::MAX_PAYLOAD_LENGTH (4078字节) 为了最大安全性
  • false 为了最大性能
  • 如果您知道您的有效载荷不会超过 X 字节,那么将其设置为 X 以在安全性和性能之间取得最佳平衡。
<?php

use Minishlink\WebPush\WebPush;

$webPush = new WebPush();
$webPush->setAutomaticPadding(false); // disable automatic padding
$webPush->setAutomaticPadding(512); // enable automatic padding to 512 bytes (you should make sure that your payload is less than 512 bytes, or else an attacker could guess the content)
$webPush->setAutomaticPadding(true); // enable automatic padding to default maximum compatibility length

自定义HTTP客户端

WebPush使用Guzzle。它将使用它找到的最合适的客户端,大多数情况下将是MultiCurl,允许并行发送多个通知。

您可以在实例化WebPush时自定义默认请求选项和超时。

<?php

use Minishlink\WebPush\WebPush;

$timeout = 20; // seconds
$clientOptions = [
    \GuzzleHttp\RequestOptions::ALLOW_REDIRECTS => false,
]; // see \GuzzleHttp\RequestOptions
$webPush = new WebPush([], [], $timeout, $clientOptions);

常见问题

是否有我喜欢的PHP框架的插件/Bundle/扩展?

以下是一些可用的选项

欢迎添加您自己的!

API是否稳定?

直到Push API规范完成之前都不是。

关于安全性?

有效载荷根据《Web Push 消息加密》标准进行加密,使用您可以通过遵循《Web Push API》规范获得的用户公钥和认证密钥。

内部,WebPush 使用 WebToken 框架或 OpenSSL 来处理加密密钥的生成和加密。

如何进行扩展?

以下是一些建议

  1. 升级到 PHP 7.2
  2. 确保您的服务器上可用 MultiCurl
  3. 在安全性和性能之间找到适合您的需求的平衡(见上文)
  4. 找到正确的批量大小(在 defaultOptions 中设置或作为 flush() 的参数)

如何解决 "SSL 证书问题:无法获取本地发行者证书" 问题?

您的安装缺少一些证书。

  1. 下载 cacert.pem
  2. 编辑您的 php.ini:在 [curl] 后面,输入 curl.cainfo = /path/to/cacert.pem

您还可以强制使用不进行对等验证的客户端。

如何解决 "Bad key encryption key length" 或 "Unsupported key type" 问题?

在您的 php.ini 中禁用 mbstring.func_overload

如何解决 "找不到类 'Minishlink\WebPush\WebPush'" 问题?

确保要求 Composer 的 自动加载器

require __DIR__ . '/path/to/vendor/autoload.php';

我必须使用 PHP 5.4 或 5.5,我能做什么?

您将无法发送任何有效载荷,因此您只能使用 sendNotification($subscription)。使用 composer 安装库时使用 --ignore-platform-reqs。获取有效载荷的替代方案是在服务工作者中获取(示例)。

我丢失了我的 VAPID 密钥!

参见 问题 #58

我正在使用 Firebase 推送通知,我如何使用这个库?

这个库不是为 Firebase 推送通知设计的。您仍然可以将其用于您的 Web 项目(对于标准 WebPush 通知),但在使用库时应该忘记与 Firebase 的任何联系。

我需要向原生应用发送通知。(例如,iOS 的 APNS)

WebPush 是用于 Web 应用的。您需要类似 RMSPushNotificationsBundle(Symfony)的东西。

这是 PHP... 我需要 JavaScript!

这个库受到了 Node.js marco-c/web-push 库的启发。

贡献

请参阅 CONTRIBUTING.md

许可证

MIT

赞助商

感谢 JetBrains 通过其 所有产品包 的赞助来支持项目,这是在他们的 免费开源许可证 计划之内。