Уведомление об ошибке чтения с уведомлением PHP Apple Enhanced Push Notification

В PHP, как вы используете fread(), чтобы проверить, есть ли ответ об ошибке при отправке расширенных push-уведомлений?

Я прочитал документы Apple, пару неопределенных сообщений через Google и пару вопросов/ответов здесь, на SO, но это все еще было очень запутанным.

Вот что я посмотрел: http://developer.apple.com/library/mac/#documentation/NetworkingInternet/Conceptual/RemoteNotificationsPG/IPhoneOSClientImp/IPhoneOSClientImp.html Ошибка чтения с расширенным уведомлением Apple с помощью PHP Уведомление о Push iPhone - проблема с ответом на ошибку

Я собираюсь ответить на мой собственный вопрос ниже, исходя из того факта, что: (1) я нашел это очень запутанной темой, и (2) мне пришлось собрать информацию вместе с большим количеством проб и ошибок, чтобы получить ее (3) этот пост в блоге, в котором говорится, что он приветствуется: http://blog.stackoverflow.com/2011/07/its-ok-to-ask-and-answer-your-own-questions/

Ответы

Ответ 1

Когда вы отправляете push-уведомление, есть несколько проблем:

  • Если есть проблема, Apple отключит вас, но вы об этом не знаете. Когда вы используете базовые уведомления, нет способа узнать, все ли они отправлены или нет. РЕШЕНИЕ: В этом весь смысл использования расширенного уведомления, а затем проверка на ответ об ошибке. Обратите внимание, что мы будем использовать "ORDER BY id" в запросе базы данных, а затем использовать идентификатор в качестве идентификатора, который мы отправляем в уведомлении. Таким образом, если есть проблема, мы точно знаем, какая строка в db вызвала проблему (и, следовательно, мы знаем, когда Apple отключило нас и прекратила отправку уведомлений). Затем мы можем продолжить отправку Push-уведомлений во все строки после строки, вызвавшей проблему, без необходимости повторной отправки тех, которые мы уже отправили.

  • Apple НЕ отправляет ответ, если все в порядке, поэтому это может привести к тому, что ваш script будет приостанавливаться и ждать навсегда, пока fread() не ждет данных, которые не будут появляться. РЕШЕНИЕ: нужно установить stream_set_blocking в 0, чтобы fread всегда возвращался сразу. Обратите внимание, что это вызывает еще одну незначительную проблему, которую fread может вернуть до получения ответа об ошибке, но см. Обходной путь в коде, который нужно просто приостановить в течение 1/2 секунды после завершения всей вашей отправки, а затем еще раз проверить fread.

  • Вы можете отправлять несколько push-уведомлений намного быстрее, чем принимать ответ об ошибке, чтобы вернуться к вам. РЕШЕНИЕ: Опять же, это одно и то же обходное решение, упомянутое выше... пауза в течение 1/2 секунды. ПОСЛЕ того, как все ваши отправки завершены, а затем еще раз проверьте fread.

Вот мое решение с использованием PHP, в котором рассматриваются все мои проблемы, с которыми я столкнулся. Его довольно простой, но выполняет свою работу. Я протестировал его, отправив сразу несколько уведомлений, а также отправил 120 000 за один раз.

<?php
/*
 * Read Error Response when sending Apple Enhanced Push Notification
 *
 * This assumes your iOS devices have the proper code to add their device tokens
 * to the db and also the proper code to receive push notifications when sent.
 *
 */

//database
$host = "localhost";
$user = "my_db_username";
$pass = "my_db_password";
$dbname = "my_db_name";
$con = mysql_connect($host, $user, $pass);
if (!$con) {
    die('Could not connect to database: ' . mysql_error());
} else {
    mysql_select_db($dbname, $con);
}

// IMPORTANT: make sure you ORDER BY id column
$result = mysql_query("SELECT id,token FROM `device_tokens` ORDER BY id");

//Setup notification message
$body = array();
$body['aps'] = array('alert' => 'My push notification message!');
$body['aps']['notifurl'] = 'http://www.myexampledomain.com';
$body['aps']['badge'] = 1;

//Setup stream (connect to Apple Push Server)
$ctx = stream_context_create();
stream_context_set_option($ctx, 'ssl', 'passphrase', 'password_for_apns.pem_file');
stream_context_set_option($ctx, 'ssl', 'local_cert', 'apns.pem');
$fp = stream_socket_client('ssl://gateway.push.apple.com:2195', $err, $errstr, 60, STREAM_CLIENT_CONNECT, $ctx);
stream_set_blocking ($fp, 0); //This allows fread() to return right away when there are no errors. But it can also miss errors during last seconds of sending, as there is a delay before error is returned. Workaround is to pause briefly AFTER sending last notification, and then do one more fread() to see if anything else is there.

if (!$fp) {
    //ERROR
    echo "Failed to connect (stream_socket_client): $err $errstrn";
} else {
    $apple_expiry = time() + (90 * 24 * 60 * 60); //Keep push alive (waiting for delivery) for 90 days

    //Loop thru tokens from database
    while($row = mysql_fetch_array($result)) {
        $apple_identifier = $row["id"];
        $deviceToken = $row["token"];
        $payload = json_encode($body);
        //Enhanced Notification
        $msg = pack("C", 1) . pack("N", $apple_identifier) . pack("N", $apple_expiry) . pack("n", 32) . pack('H*', str_replace(' ', '', $deviceToken)) . pack("n", strlen($payload)) . $payload;
        //SEND PUSH
        fwrite($fp, $msg); 
        //We can check if an error has been returned while we are sending, but we also need to check once more after we are done sending in case there was a delay with error response.
        checkAppleErrorResponse($fp);
    }

    //Workaround to check if there were any errors during the last seconds of sending.
    usleep(500000); //Pause for half a second. Note I tested this with up to a 5 minute pause, and the error message was still available to be retrieved

    checkAppleErrorResponse($fp);

    echo 'DONE!';

    mysql_close($con);
    fclose($fp);
}

//FUNCTION to check if there is an error response from Apple
//         Returns TRUE if there was and FALSE if there was not
function checkAppleErrorResponse($fp) {

   //byte1=always 8, byte2=StatusCode, bytes3,4,5,6=identifier(rowID). Should return nothing if OK.
   $apple_error_response = fread($fp, 6);
   //NOTE: Make sure you set stream_set_blocking($fp, 0) or else fread will pause your script and wait forever when there is no response to be sent.

   if ($apple_error_response) {
        //unpack the error response (first byte 'command" should always be 8)
        $error_response = unpack('Ccommand/Cstatus_code/Nidentifier', $apple_error_response);

        if ($error_response['status_code'] == '0') {
            $error_response['status_code'] = '0-No errors encountered';
        } else if ($error_response['status_code'] == '1') {
            $error_response['status_code'] = '1-Processing error';
        } else if ($error_response['status_code'] == '2') {
            $error_response['status_code'] = '2-Missing device token';
        } else if ($error_response['status_code'] == '3') {
            $error_response['status_code'] = '3-Missing topic';
        } else if ($error_response['status_code'] == '4') {
            $error_response['status_code'] = '4-Missing payload';
        } else if ($error_response['status_code'] == '5') {
            $error_response['status_code'] = '5-Invalid token size';
        } else if ($error_response['status_code'] == '6') {
            $error_response['status_code'] = '6-Invalid topic size';
        } else if ($error_response['status_code'] == '7') {
            $error_response['status_code'] = '7-Invalid payload size';
        } else if ($error_response['status_code'] == '8') {
            $error_response['status_code'] = '8-Invalid token';
        } else if ($error_response['status_code'] == '255') {
            $error_response['status_code'] = '255-None (unknown)';
        } else {
            $error_response['status_code'] = $error_response['status_code'] . '-Not listed';
        }

        echo '<br><b>+ + + + + + ERROR</b> Response Command:<b>' . $error_response['command'] . '</b>&nbsp;&nbsp;&nbsp;Identifier:<b>' . $error_response['identifier'] . '</b>&nbsp;&nbsp;&nbsp;Status:<b>' . $error_response['status_code'] . '</b><br>';
        echo 'Identifier is the rowID (index) in the database that caused the problem, and Apple will disconnect you from server. To continue sending Push Notifications, just start at the next rowID after this Identifier.<br>';

        return true;
   }
   return false;
}
?>

Ответ 2

Не уверен, что содержимое вашего кода, но вы должны попробовать ApnsPHP, он хорошо протестирован, работает отлично, и он способен для обработки всех возможных исключений и ошибок для вас.

Другие альтернативы

https://github.com/sebastianborggrewe/PHP-Apple-Push-Notification-Server https://github.com/bortuzar/PHP-Mysql---Apple-Push-Notification-Server

Протестировали 2 из 3-х примеров и не имели проблемы с внедрением и управлением ошибками.

Спасибо

:)