Skip to content

Commit 1d39285

Browse files
authored
Scaling and sending in batches (#92)
* Send notifications in batches * Update README for scaling * Fix formatting
1 parent 28c01ae commit 1d39285

File tree

3 files changed

+70
-38
lines changed

3 files changed

+70
-38
lines changed

README.md

+8
Original file line numberDiff line numberDiff line change
@@ -258,6 +258,14 @@ local public and private keys and compute the shared secret.
258258
Then, if you have a PHP >= 7.1, WebPush uses `openssl` in order to encrypt the payload with the encryption key.
259259
Otherwise, if you have PHP < 7.1, it uses [Spomky-Labs/php-aes-gcm](https://github.com/Spomky-Labs/php-aes-gcm), which is slower.
260260

261+
### How do I scale?
262+
Here are some ideas:
263+
264+
1. Upgrade to PHP 7.1
265+
2. Make sure MultiCurl is available on your server
266+
3. Find the right balance for your needs between security and performance (see above)
267+
4. Find the right batch size (set it in `defaultOptions` or as parameter to `flush()`)
268+
261269
### How to solve "SSL certificate problem: unable to get local issuer certificate"?
262270
Your installation lacks some certificates.
263271

src/WebPush.php

+45-38
Original file line numberDiff line numberDiff line change
@@ -29,7 +29,7 @@ class WebPush
2929
/** @var array Array of array of Notifications */
3030
private $notifications;
3131

32-
/** @var array Default options : TTL, urgency, topic */
32+
/** @var array Default options : TTL, urgency, topic, batchSize */
3333
private $defaultOptions;
3434

3535
/** @var int Automatic padding of payloads, if disabled, trade security for bandwidth */
@@ -39,7 +39,7 @@ class WebPush
3939
* WebPush constructor.
4040
*
4141
* @param array $auth Some servers needs authentication
42-
* @param array $defaultOptions TTL, urgency, topic
42+
* @param array $defaultOptions TTL, urgency, topic, batchSize
4343
* @param int|null $timeout Timeout of POST request
4444
* @param array $clientOptions
4545
*/
@@ -113,54 +113,60 @@ public function sendNotification($endpoint, $payload = null, $userPublicKey = nu
113113
/**
114114
* Flush notifications. Triggers the requests.
115115
*
116+
* @param int $batchSize Defaults the value defined in defaultOptions during instanciation (which defaults to 1000).
116117
* @return array|bool If there are no errors, return true.
117118
* If there were no notifications in the queue, return false.
118-
* Else return an array of information for each notification sent (success, statusCode, headers, content)
119-
*
120-
* @throws \ErrorException
119+
* Else return an array of information for each notification sent (success, statusCode, headers, content)
121120
*/
122-
public function flush()
121+
public function flush($batchSize = null)
123122
{
124123
if (empty($this->notifications)) {
125124
return false;
126125
}
127126

128-
// for each endpoint server type
129-
$requests = $this->prepare($this->notifications);
130-
$promises = [];
131-
foreach ($requests as $request) {
132-
$promises[] = $this->client->sendAsync($request);
127+
if (!isset($batchSize)) {
128+
$batchSize = $this->defaultOptions['batchSize'];
133129
}
134-
$results = Promise\settle($promises)->wait();
135130

131+
$batches = array_chunk($this->notifications, $batchSize);
136132
$return = array();
137133
$completeSuccess = true;
138-
foreach ($results as $result) {
139-
if ($result['state'] === "rejected") {
140-
/** @var RequestException $reason **/
141-
$reason = $result['reason'];
142-
143-
$error = array(
144-
'success' => false,
145-
'endpoint' => "".$reason->getRequest()->getUri(),
146-
'message' => $reason->getMessage(),
147-
);
148-
149-
$response = $reason->getResponse();
150-
if ($response !== null) {
151-
$statusCode = $response->getStatusCode();
152-
$error['statusCode'] = $statusCode;
153-
$error['expired'] = in_array($statusCode, array(400, 404, 410));
154-
$error['content'] = $response->getBody();
155-
$error['headers'] = $response->getHeaders();
134+
foreach ($batches as $batch) {
135+
// for each endpoint server type
136+
$requests = $this->prepare($batch);
137+
$promises = [];
138+
foreach ($requests as $request) {
139+
$promises[] = $this->client->sendAsync($request);
140+
}
141+
$results = Promise\settle($promises)->wait();
142+
143+
foreach ($results as $result) {
144+
if ($result['state'] === "rejected") {
145+
/** @var RequestException $reason **/
146+
$reason = $result['reason'];
147+
148+
$error = array(
149+
'success' => false,
150+
'endpoint' => "".$reason->getRequest()->getUri(),
151+
'message' => $reason->getMessage(),
152+
);
153+
154+
$response = $reason->getResponse();
155+
if ($response !== null) {
156+
$statusCode = $response->getStatusCode();
157+
$error['statusCode'] = $statusCode;
158+
$error['expired'] = in_array($statusCode, array(400, 404, 410));
159+
$error['content'] = $response->getBody();
160+
$error['headers'] = $response->getHeaders();
161+
}
162+
163+
$return[] = $error;
164+
$completeSuccess = false;
165+
} else {
166+
$return[] = array(
167+
'success' => true,
168+
);
156169
}
157-
158-
$return[] = $error;
159-
$completeSuccess = false;
160-
} else {
161-
$return[] = array(
162-
'success' => true,
163-
);
164170
}
165171
}
166172

@@ -293,12 +299,13 @@ public function getDefaultOptions()
293299
}
294300

295301
/**
296-
* @param array $defaultOptions Keys 'TTL' (Time To Live, defaults 4 weeks), 'urgency', and 'topic'
302+
* @param array $defaultOptions Keys 'TTL' (Time To Live, defaults 4 weeks), 'urgency', 'topic', 'batchSize'
297303
*/
298304
public function setDefaultOptions(array $defaultOptions)
299305
{
300306
$this->defaultOptions['TTL'] = array_key_exists('TTL', $defaultOptions) ? $defaultOptions['TTL'] : 2419200;
301307
$this->defaultOptions['urgency'] = array_key_exists('urgency', $defaultOptions) ? $defaultOptions['urgency'] : null;
302308
$this->defaultOptions['topic'] = array_key_exists('topic', $defaultOptions) ? $defaultOptions['topic'] : null;
309+
$this->defaultOptions['batchSize'] = array_key_exists('batchSize', $defaultOptions) ? $defaultOptions['batchSize'] : 1000;
303310
}
304311
}

tests/WebPushTest.php

+17
Original file line numberDiff line numberDiff line change
@@ -118,6 +118,23 @@ public function testSendNotification($endpoint, $payload, $userPublicKey, $userA
118118
$this->assertTrue($res);
119119
}
120120

121+
public function testSendNotificationBatch()
122+
{
123+
$batchSize = 10;
124+
$total = 50;
125+
126+
$notifications = $this->notificationProvider();
127+
$notifications = array_fill(0, $total, $notifications[0]);
128+
129+
foreach ($notifications as $notification) {
130+
$this->webPush->sendNotification($notification[0], $notification[1], $notification[2], $notification[3]);
131+
}
132+
133+
$res = $this->webPush->flush($batchSize);
134+
135+
$this->assertTrue($res);
136+
}
137+
121138
public function testSendNotificationWithTooBigPayload()
122139
{
123140
$this->setExpectedException('ErrorException', 'Size of payload must not be greater than 4078 octets.');

0 commit comments

Comments
 (0)