amazon-web-servicespush-notificationamazon-snsaws-php-sdkphp-socket

Send large amount of notification using SNS PHP-SDK


I am using amazon PHP SDK V3.13.1, I have to send minimum 10,000 push notification within 30 seconds. Right now i am using publishAsync method it is faster but still i didn't send it within time.
So i have implement socket and send a bunch of 3500 push for each time. Following is my controller function from where i send socket request.

$parts = parse_url(base_url() . "welcome/send_signal");
for ($i = 1; $i <= 10000; $i++) {
    $device_type_ids_arr[$i]['token_id'] = "User token";
    $device_type_ids_arr[$i]['arn'] = "User arn";
    $device_type_ids_arr[$i]['member_id'] = $i;
    if ((count($device_type_ids_arr) == 3500) || $i == 10000) {
        $postData['devices'] = $device_type_ids_arr;
        $postData['pushSet'] = $pushSet;
        $postData['push_content'] = $push_content;
        $post_string = http_build_query($postData);
        $device_type_ids_arr = array();
        $fp = fsockopen($parts['host'], isset($parts['port']) ? $parts['port'] : 80, $errno, $errstr, 600);
        if (!$fp) {
            echo "Some thing Problem";
        }
        $out = "POST " . $parts['path'] . " HTTP/1.1\r\n";
        $out .= "Host: " . $parts['host'] . "\r\n";
        $out .= "User-Agent: " . $_SERVER['HTTP_USER_AGENT'] . "\r\n";
        $out .= "Content-Type: application/x-www-form-urlencoded\r\n";
        $out .= "Content-Length: " . strlen($post_string) . "\r\n";
        $out .= "Connection: Close\r\n\r\n";
        $out .= $post_string;
        fwrite($fp, $out);
        fclose($fp);
    }
}  

Following is my function which is received socket data and send a push notification.

$sns = new Aws\Sns\SnsClient(array(
    'version' => 'latest',
    'key' => "my sns key",
    'secret' => "secret",
    'region' => "region",
    'profile' => "amazon_user_profile",
    'debug' => false,
    'http' => array('verify' => false)
        ));
foreach ($device_id_arr as $device_detail) {
    try {
        $promises[] = $sns->publishAsync(array(
            'Message' => '{ "GCM": "{\"data\": { \"message\": \"Hello user\" } }"}',
            'MessageStructure' => 'json',
            'TargetArn' => "member sns arn"
        ));
    } catch (Exception $e) {
        $errorMsg = $e->getMessage();
    }
}
$results = \GuzzleHttp\Promise\settle($promises)->wait(TRUE);
$fp = fopen("test_parallel.txt", "a+");
fwrite($fp, "result:" . print_r($results, true) . "\r\n");
fclose($fp);  

When i send 10 notification this is working fine but when i sent 3500 push then it is not working and does not give me any response. I also tried this method. Amazon AWS PHP SDK with Guzzle's MultiCurl? but it gives me error Argument 1 passed to Aws\AwsClient::execute() must implement interface Aws\CommandInterface, array given

$sns = new Aws\Sns\SnsClient(array(
    'version' => 'latest',
    'key' => "my sns key",
    'secret' => "secret",
    'region' => "region",
    'profile' => "amazon_user_profile",
    'debug' => false,
    'http' => array('verify' => false)
        ));
foreach ($device_id_arr as $device_detail) {
    try {
        $publishCommands[] = $sns->getCommand('Publish', array(
            "Message" => '{ "GCM": "{\"data\": { \"message\": \"' . $push_content . '\", \"type\": \"' . PUSH_TYPE_SIGNAL . '\" } }"}',
            "MessageStructure" => "json",
            "TargetArn" => $device_detail['arn']
        ));
    } catch (Exception $e) {
        $errorMsg = $e->getMessage();
    }
}
try {
    $successfulCommands = $sns->execute($publishCommands);
    $failedCommands = array();
} catch (\Guzzle\Service\Exception\CommandTransferException $e) {
    $successfulCommands = $e->getSuccessfulCommands();
    $failedCommands = $e->getFailedCommands();
}

foreach ($failedCommands as $failedCommand) {
    $fp = fopen("test_parallel4.txt", "a+");
    fwrite($fp, "result:" . print_r($result, true) . "\r\n");
    fclose($fp);
}

$messageIds = array();
foreach ($successfulCommands as $successfulCommand) {
    $messageIds[] = $successfulCommand->getResult()->get('MessageId');
}

So anyone has a solution for this? My main concern is to send thousands of push notification within 30 seconds.


Solution

  • Finally, i got the solution using multiple curl in php. This is executed parallel, this also depends on your server and internet speed. Following is my controller function.

    public function index() {
        for ($i = 1; $i <= 10000; $i++) {
            $device_type_ids_arr[$i]['member_id'] = $i;
            $device_type_ids_arr[$i]['arn'] = "member arn or empty";
            $device_type_ids_arr[$i]['token_id'] = "member token";
        }
    
        $start = microtime(true);
        /* Chunk members into 500 bunch and crete multiple curl request */
        $_datas = array_chunk($device_type_ids_arr, 500);
        $mh = curl_multi_init();
        $handles = array();
        foreach ($_datas as $batchPart) {
            $ch = curl_init();
            $postData['devices'] = $batchPart; //device array
            $postData['push_content'] = "Push Message";
            $data_string = json_encode($postData);
            curl_setopt($ch, CURLOPT_URL, base_url() . "Welcome/send_push_notification");
            curl_setopt($ch, CURLOPT_CUSTOMREQUEST, "POST");
            curl_setopt($ch, CURLOPT_USERAGENT, 'User-Agent: curl/7.39.0');
            curl_setopt($ch, CURLOPT_POSTFIELDS, $data_string);
            curl_setopt($ch, CURLOPT_HEADER, 0);
            curl_setopt($ch, CURLOPT_RETURNTRANSFER, true);
            curl_setopt($ch, CURLOPT_SSL_VERIFYPEER, false);
            curl_setopt($ch, CURLOPT_FOLLOWLOCATION, false);
            curl_setopt($ch, CURLOPT_HTTPHEADER, array(
                'Content-Type: application/json',
                'Content-Length: ' . strlen($data_string))
            );
            curl_multi_add_handle($mh, $ch);
            $handles[] = $ch;
        }
        // execute requests and poll periodically until all have completed
        $isRunning = null;
        do {
            curl_multi_exec($mh, $isRunning);
            usleep(250000);
        } while ($isRunning > 0);
    
        curl_multi_close($mh);
        printf("Elapsed time: %.2f seconds\n", microtime(true) - $start);
    }  
    

    Following is a function which sends push notifications parallelly. I also explain how to handle Exception and make sure that notifications are sent to All/Maximum users.

     public function send_push_notification() {
            require_once APPPATH . 'libraries/aws-sdk/aws-autoloader.php';
            $pipeArr = json_decode(file_get_contents('php://input'), true);
            $push_content = $pipeArr["push_content"];
            $device_id_arr = $pipeArr["devices"];
            $sns = new Aws\Sns\SnsClient(array(
                'version' => 'latest',
                'key' => "Amazon account key",
                'secret' => "Amazon account secret",
                'region' => "Region",
                'profile' => "Amazon account user profile",
                'debug' => false,
                'http' => array('verify' => false)
            ));
            $appArn = "Application Arn";
            $promises = $results = $retArr = array();
            //start sending loop of 500 members asynchroniously
            foreach ($device_id_arr as $key => $device_detail) {
                $arn = $device_detail['arn'];
                $token = $device_detail['token_id'];
                $userid = $device_detail['member_id'];
                //If member arn is empty then register it on amazon and then update into databse
                if (empty($arn)) {
                    try {
                        $updatedArn = $sns->createPlatformEndpoint(array('PlatformApplicationArn' => $appArn, 'Token' => $token));
                        $arn = isset($updatedArn['EndpointArn']) ? $updatedArn['EndpointArn'] : "";
                        //update member detail with new arn into database
                        ($arn != "") ?/* DB query goes here */ : "";
                    } catch (Exception $e) {
                        $errorMsg = $e->getMessage();
                    }
                }
                if (!empty($arn)) {
                    try {
                        $promises[$userid] = $sns->publishAsync(array(
                            'Message' => '{ "GCM": "{\"data\": { \"message\": \"' . $push_content . '\"}}"}',
                            'MessageStructure' => 'json',
                            'TargetArn' => $arn
                        ));
                        $promises[$userid]->arn = $arn;
                        $promises[$userid]->token = $token;
                    } catch (Exception $e) {
                        $errorMsg = $e->getMessage();
                    }
                }
            }
            //This is Async method to send push and get result of each member with message Id
            $results = \GuzzleHttp\Promise\settle($promises)->wait(TRUE);
    
            /* Handle result and get message Id */
            foreach ($results as $key => $value) {
                /* If any of push notification is fail then check for error and fix it */
                if (isset($value['reason'])) {
                    $message = $value['reason']->getMessage();
                    $token = (isset($promises[$key]->token)) ? $promises[$key]->token : "";
                    $arn = (isset($promises[$key]->arn)) ? $promises[$key]->arn : "";
                    if (strpos($message, "Endpoint is disabled") !== false && $token != "" && $arn != "") {
                        try {
                            $res = $sns->setEndpointAttributes(array(
                                'Attributes' => array("Token" => $promises[$key]->token, "Enabled" => "true"),
                                'EndpointArn' => $arn
                            ));
                        } catch (Exception $e) {
                            $errorMsg = $e->getMessage();
                        }
                    }
                    if (strpos($message, "No endpoint found for the target arn specified") !== false && $token != "") {
                        try {
                            $updatedArn = $sns->createPlatformEndpoint(array('PlatformApplicationArn' => $appArn, 'Token' => $token));
                            $arn = isset($updatedArn['EndpointArn']) ? $updatedArn['EndpointArn'] : "";
                            //update member detail with new arn into database
                            ($arn != "") ?/* DB query goes here */ : "";
                        } catch (Exception $e) {
                            $errorMsg = $e->getMessage();
                        }
                    }
                    if (!empty($arn)) {
                        try {
                            $publishRes = $sns->publish(array(
                                'Message' => '{ "GCM": "{\"data\": { \"message\": \"' . $push_content . '\", \"type\": \"' . PUSH_TYPE_SIGNAL . '\" } }"}',
                                'MessageStructure' => 'json',
                                'TargetArn' => $arn
                            ));
                            $retArr[$key] = $publishRes->get("MessageId");
                        } catch (Exception $e) {
                            $errorMsg = $e->getMessage();
                            /* Final error of push notification not sent */
                            $newFile = fopen("error_push_not_sent.txt", "a+");
                            fwrite($newFile, "Member Id:" . $key . "\r\nARN:" . $arn . "\r\nToken:" . $token . "\r\n" . $errorMsg . "\r\n");
                            fclose($newFile);
                        }
                    }
                } else {
                    $retArr[$key] = $results[$key]['value']->get("MessageId");
                }
            }
    
            /* Get message Id of amazon and insert record into database */
            if (isset($retArr) && !empty($retArr)) {
                foreach ($retArr as $member_id => $message_id) {
                    /* DB query goes here OR you can generate batch query here and then execute */
                }
            }
        }