Should the connection to APNs be closed after voip push notification is sent?
Asked Answered
I

1

7

I am using a simplepush.php script to send voip pushes from user to user. My app can potentially make many of these push requests depending on how many users it acquires. Every example of simplepush.php that I found seems to explicitly close the connection at the end - here is my script (see last line):

$ctx = stream_context_create();
stream_context_set_option($ctx, 'ssl', 'local_cert', 'voip.pem');
stream_context_set_option($ctx, 'ssl', 'passphrase', $passphrase);

// Open a connection to the APNS server
$fp = stream_socket_client($apnsUrl, $err,
    $errstr, 60, STREAM_CLIENT_CONNECT|STREAM_CLIENT_PERSISTENT, $ctx);

if (!$fp)
    exit("Failed to connect: $err $errstr" . PHP_EOL);

echo 'Connected to APNS' . PHP_EOL;

// Create the payload body
$body['aps'] = array(
    'alert' => $message,
    'sound' => 'default'
    );

$body['info'] = array(
  'roomname' => $roomName,
  'uuid' => $uuid
  );

// Encode the payload as JSON
$payload = json_encode($body);

// Build the binary notification
$msg = chr(0) . pack('n', 32) . pack('H*', $deviceToken) . pack('n', strlen($payload)) . $payload;

// Send it to the server
$result = fwrite($fp, $msg, strlen($msg));

if (!$result)
    echo 'Message not delivered' . PHP_EOL;
else
    echo 'Message successfully delivered' . PHP_EOL;

// Close the connection to the server
fclose($fp);

Please note: I am using the legacy APNs binary interface to send notifications instead of an HTTP/2 request because all the simplepush scripts used it. I am not well versed in PHP, but it seems like the script is closing the connection at the end of every call: fclose($fp);

But according to Apple I should leave the connection open:

Best Practices for Managing Connections Keep your connections with APNs open across multiple notifications; do not repeatedly open and close connections. APNs treats rapid connection and disconnection as a denial-of-service attack. https://developer.apple.com/library/content/documentation/NetworkingInternet/Conceptual/RemoteNotificationsPG/CommunicatingwithAPNs.html#//apple_ref/doc/uid/TP40008194-CH11-SW6

But since I'm using the legacy binary interface, should I actually be closing the connection after every call? Or am I misunderstanding the fclose($fp); function here? Any clarity on the appropriate way to handle the connection when using this binary would be much appreciated!

Intransigent answered 16/3, 2018 at 19:37 Comment(0)
G
3

Explanation

As far as I can tell, the recommendation not to close the connection for each notification comes from an area of bulk notification delivery, where many users are delivered the same notification.

Apart from possibly being interpreted as an attack, closing and reopening all the time is very inefficient and would cause huge delivery delays. Just writing the binary message on the stream is very much faster than opening and closing for each notification. See this example:

// Open a connection to the apns server (this code is the same as a few lines below, so if changed here, also change there)
$stream_context = stream_context_create();
stream_context_set_option($stream_context, 'ssl', 'local_cert', 'voip.pem');
stream_context_set_option($stream_context, 'ssl', 'passphrase', 'secret_pass');
$apns = stream_socket_client($url, $err, $errstr, 60, STREAM_CLIENT_CONNECT|STREAM_CLIENT_PERSISTENT, $stream_context);

// Return if connection is impossible
if (!$apns) { return; }

// Create payload body
$body['aps'] = array(
    'alert' => $title,
    'badge' => 1
);

// Encode the payload as json
$payload = json_encode($body);

// Iterate through token array
foreach($tokens as $token) {    
    $user_id = 2 // Random id ;)

    // Build binary notification
    $msg = pack("C", 1);
    $msg .= pack("N", $user_id);
    $msg .= pack("N", $notification_expiration_date);
    $msg .= pack("n", 32);
    $msg .= pack('H*', $token);
    $msg .= pack("n", strlen($payload));
    $msg .= $payload;

    // Send to the server
    $fwrite_result = fwrite($apns, $msg, strlen($msg));
}

fclose($apns);

See how it opens one connection, writes for each token in the array, and closes afterwards, instead of opening, writing and closing each time.

Having considered a bulk push application as an example, it's now your design decision whether the connection should be kept. If you send out notification just every hour, it would be suitable to open and close for each notification in my opinion. However, if you have a throughput of multiple notification per minute you will need to keep the connection open.

Suggestion

One approach you could take is looking for any new notifications to be delivered after finishing the existing queue. If there are none, you could wait for another few minutes, then check again and close the connection if still nothing new is there, if there are some, keep it open and send the new notifications using the same connection.

This would be in-line with Apple's recommendations:

You should leave a connection open unless you know it will be idle for an extended period of time—for example, if you only send notifications to your users once a day, it is acceptable practice to use a new connection each day.

Warning

One important thing to consider when using one single connection is error handling: Invalid tokens (production / sandbox mode confused) can cause the connection to close without you noticing but there are other posts discussing that further.

Gasaway answered 21/3, 2018 at 20:34 Comment(0)

© 2022 - 2024 — McMap. All rights reserved.