Продажный программист-фрилансер.
0,0
рейтинг
31 июля 2014 в 15:53

Разработка → Push рассылки на PHP (Android и IOS). Минимальное готовое решение из песочницы

GCM

О рассылке Push уведомлений уже много раз писали на Хабре, например тут и тут, но прямого руководства к действию до сих пор нет. Итак, если интересно, прошу под кат.

Регистрация токенов устройств


В первую очередь разработчик приложения творит магию, в которой указывает адрес регистрации, он может быть таким: htpp://test.ru/secret/android?token=value и htpp://test.ru/secret/ios?token=value.
Самое примечательное что защиты от левых регистраций просто нет, хотя может магия подвела либо была не совсем качественной, если все таки защита есть, отпишите в комментариях, я обязательно дополню статью полезным советом.

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

Пример токена для android:

APA91bFY-2CYrriS-Dt6y9_dGHhkPVwy7njqFpfgpzGYlDT4l0SQeqKr-lc1OM0a2DQ33S3EKwy2YJn-upKxOT6rNwgk350xUM3g8VX65rkGocOQX80Ta34pwXo6fyn-usoaGUAm4lzsqbCL-gkzHZZXRX39kUQfnA


IOS:

628a3f4a28bb994bb7c9a4143950d240c6d5a1dab8621e9ed61a2109a074f832


На этом шаге с регистрацией устройств мы закончили.

Рассылка уведомлений


Apple использует сервис APNS, а Google GCM (C2DM считается устаревшим, учитывайте это) и после прочтения документации можно перейти к любимому делу, а именно к велосипедостроению, но бюджет был ограничен и я начал поиски готовых решений. Самое годное что встретилось это ApnsPHP и GCMMessage, работают как на 5.3+ так и на 5.4+.

При использовании библиотек самое главное получить правильные сертификаты и секретную фразу в случае с APNS и секретный токен для работы с GCM.

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

function fnSendAndroid($tokens, $text, $config)
{
    $sender = new CodeMonkeysRu\GCM\Sender($config['androidTokenAuth']);
    $message = new CodeMonkeysRu\GCM\Message($tokens, array("message" => $text));

    try {
        $response = $sender->send($message);

        if ($response->getFailureCount() > 0) {
            $invalidRegistrationIds = $response->getInvalidRegistrationIds();
            foreach($invalidRegistrationIds as $invalidRegistrationId) {
                //Remove $invalidRegistrationId from DB
                // на входе значение APS91bFY-2CYrriS-Dt6y9_dGHhkPVwy7njqFpfgpzGYlDT4l0SQeqKr-lc1OM0a2DQ33S3EKwy2YJn-upKxOT6rNwgk350xUM3g8VX65rkGocOQX80Ta34pwXo6fyn-usoaGUAm4lzsqbCL-gkzHZZXRX39kUQfnA 
                 fnDeleteToken($invalidRegistrationId);
            }
        }
        if ($response->getSuccessCount()) {
            echo 'отправлено сообщений на ' . $response->getSuccessCount() . ' устройств';
        }
    } catch (CodeMonkeysRu\GCM\Exception $e) {

        switch ($e->getCode()) {
            case CodeMonkeysRu\GCM\Exception::ILLEGAL_API_KEY:
            case CodeMonkeysRu\GCM\Exception::AUTHENTICATION_ERROR:
            case CodeMonkeysRu\GCM\Exception::MALFORMED_REQUEST:
            case CodeMonkeysRu\GCM\Exception::UNKNOWN_ERROR:
            case CodeMonkeysRu\GCM\Exception::MALFORMED_RESPONSE:
                fnLog('Ошибка отправления на андроид ' . $e->getCode() . ' ' . $e->getMessage());
                break;
        }
    }
}



Везде пишут что APNS весьма прост в использовании, в принципе это соответствует действительности, если учитывать что есть сертификаты для теста и продакшена, стоит обратить внимание, нужен сертификат, секретная фраза и корневой сертификат, все это получается в личном кабинете разработчика.

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

function feedback($config)
{
    $feedback = new ApnsPHP_Feedback(
        ApnsPHP_Abstract::ENVIRONMENT_PRODUCTION, $config['apn']['sert']
    );
    $feedback->setProviderCertificatePassphrase($config['apn']['passphrase']);
    $feedback->setRootCertificationAuthority($config['apn']['RootCertificat']);

    $feedback->connect();

    $aDeviceTokens = $feedback->receive();
    if (!empty($aDeviceTokens)) {
        foreach ($aDeviceTokens as $DeviceToken) {
            /**
             * формат
             * [timestamp] => 1406040206
             * [tokenLength] => 32
             * [deviceToken] => 738d005a11bca268e2f1bffbfed88a456e261020b9277883cde14d9c8f47cde0
             */
            //'DELETE LOW_PRIORITY FROM tokens WHERE token=:token';
            fnLog('Feedback - Удален токен ' . $DeviceToken[deviceToken]);
        }
    }

    $feedback->disconnect();
}



Затем сама отсылка:

function fnSendIos($tokens, $text, $config)
{
    $push = new ApnsPHP_Push(
        ApnsPHP_Abstract::ENVIRONMENT_PRODUCTION, $config['apn']['sert']);

    // Set the Provider Certificate passphrase
    $push->setProviderCertificatePassphrase($config['apn']['passphrase']);

    $push->setRootCertificationAuthority($config['apn']['RootCertificat']);

    $message = new ApnsPHP_Message();
    $listTokens = array();
    foreach ($tokens as $token) {
        $message->addRecipient($token);
        $listTokens[] = $token;
    }

    $push->connect();

    $message->setText($text);
    $push->add($message);
    $push->send();
    $push->disconnect();

    $aErrorQueue = $push->getErrors();
    if (!empty($aErrorQueue)) {
        fnLog('Ошибка отправки ios  -  ' . print_r($aErrorQueue, true));
        if (is_array($aErrorQueue)) {
            foreach($aErrorQueue as $error) {
                if (isset($error['ERRORS']) && is_array($error['ERRORS'])) {
                    foreach ($error['ERRORS'] as $m) {
                        if (isset($m['statusMessage']) && $m['statusMessage'] == 'Invalid token') {
                            $arrayID = $m['identifier'] - 1;
                            if (isset($listTokens[$arrayID])) {
                                //DELETE LOW_PRIORITY FROM tokens WHERE token=:token'
                                fnLog('Удален ошибочный токен ' . $listTokens[$arrayID]);
                            }
                        }

                    }
                }
            }
        }
    }
}



Если Вы не напутали с сертификатами, то рассылка пройдет успешно.

Вот такой кондоминимум получился.
Степанов Сергей @ustasby
карма
–1,0
рейтинг 0,0
Продажный программист-фрилансер.
Реклама помогает поддерживать и развивать наши сервисы

Подробнее
Спецпроект

Самое читаемое Разработка

Комментарии (12)

  • 0
    При работе с APNS не нужно каждый раз делать disconnect(). Эпл тоже рекомендует держать соединение.
    • 0
      Да, я читал про это, но боюсь что эта рекомендация в данном случае не уместна, уведомление отправлено на все устройства и скрипт завершает свою работу.
      • 0
        Окей, если так, хотя эта функция fnSendIos($tokens, $text, $config) по идее же может быть вызвана несколько раз, разными частями приложения, в пределах одного запроса, и каждый раз будет реконнект. А держать инстанс клиента, в общем-то, не проблема.

        А вообще, такие штуки обычно в фоне происходят — gearman и т.п. Там демон его держит до последнего, и все довольно быстро происходит. Ну вы не подумайте что я с претензиями, просто по работе постоянно с APNS вожусь, вот и возбухаю :)
        • 0
          В этом Вы правы, но не будут же просто копировать, код больше для тестирования, что все уходит и т.д.
          • +3
            Вы сильно переоцениваете людей. Будут копипастить только в путь.
      • 0
        Как я не пробовал — у меня так не получилось отправить одно уведомление за один push сразу на миллион устройств. Поделитесь информацией — у кого получилось.
        Но, потом это оказалось и не так уж и нужно, так как мы стали пушить персональные обращения (вставляем в шаблон имя каждого пользователя).

        Код обработки FeedBack сервиса работает на отдельном порту, — это в статье указано не было, очевидно это скрыто в недрах библиотеки. Кстати очень полезно для ведения статистики, кто отключился или удалил приложение.

        Пушить можно и на устройства, на которых не запущено приложение Не знаю, есть ли эта возможность в данной либе (не слова ни сказано)

        Как реализовано у нас:
        — сделан отдельный сервис (демон)
        — в сервисе держится в памяти hash-таблица: наш-id- tocken
        — общается с внешним миром по memcached пртоколу
        — set отправляет сообщение на АПНС и GCM сервис, используем только наш id
        — есть разные мониторинговые команды: get stats
        — и служебные add delete

        Принимает данные с разных клиентов: WEB-морда — если поступило новое сообщение, cron script — если наступило запланированное событие или скрипт-рассыльщик для маркетинговых рассылок (по географ признаку).

        и еще отдельная тема расшифровки статусов GCM сервиса. Не всегда он 200Ok
        • 0
          один пуш — один токен — один девайс. Миллион девайсов — миллион пушей. Можно разбить просто на нексколько соединений (как это рекомендует делать Apple).
  • 0
    Для APNS существует довольно неплохая библиотека — Notificato. В частности, насколько я помню, это единственная библиотека где корректно реализована обработка ошибок при массовой отправке пушей. При отпраке пуша, он попадает в буфер и ждет подтверждения о успешной обработке. Если происходит ошибка при отправке сообщения, поскольку APNS гарантирует что после ошибки ничего никуда отправляться не будет, мы можем снова послать содержимое буфера… По сути так же как и в TCP. Так же есть поддержка пула соединений.

    Я уже где-то год не слежу за темой, но на тот момент это была единственная достойная имплементация. Альтернативу мог составить только модуль node-apn для Node.JS, где так же все это реализовано.

    Собственно на тот момент хотел реализовать маленький демон на ReactPHP, который можно было бы реюзать из проекта в проект, но в итоге решили плюнуть на всю эту возьню и испльзовать Amazon SNS. Если бы кто аргументированно смог сказать что это хоть кому-то нужно, я бы мог собрать все наработки по этой теме в библиотечку (APNS, GCM, WNS + хэндлинг девайсов), ибо на данный момент я не вижу необходимости в оном. Слишком много сервисов позволяют использовать пуши без головной боли.
    • 0
      В частности, насколько я помню, это единственная библиотека где корректно реализована обработка ошибок при массовой отправке пушей.

      Вы будете удивлены, но библиотека в примере как раз это и реализует в полном объеме. А насчет сервисов, стоит дополнить что некоторые из них до 7 500 000 уведомлений рассылают на бесплатных тарифах.
      • 0
        ApnsPHP это не совсем библиотека. это полноценное решение (аля бывшая некогда RAPNS). Его можно использовать как stand-alone приложение. Использовал оное года два назад, штука хорошая, но notificato больше подходит для использования именно как «библиотеки». Меня больше прельщает идея иметь библиотеку, которая хорошо справляется с задачей и легко интегрируется в проект.
    • 0
      А как у нее с юникод-6? ApnsPHP не шлет (вернее шлет, но криво) такие смайлы без допилки — пуш либо не приходит вообще, либо вместо emoji приходят вопросы.
      А SNS да, удобная штука.
  • +1
    Мы юзаем parse.com для рассылки пушей. Этот сервис предоставляет унифицированный интерфейс для рассылок на Android и iOS и инструменты для дебага пушей. Этакая очень полезная прослойка между приложением и приемниками пушей, снимает часть проблем.

Только зарегистрированные пользователи могут оставлять комментарии. Войдите, пожалуйста.