APNS not sending response nor sending push notification - php

Apple's docs say that:
"If you send a notification and APNs finds the notification malformed or otherwise unintelligible, it returns an error-response packet prior to disconnecting. (If there is no error, APNs doesn’t return anything.) Figure 5-3 depicts the format of the error-response packet."
This leads me to believe that the only reason that APNS would not send something back is if what I sent them was in the correct format. However when I try to fread for their response I get a string of 0 length and when unpacked becomes null, which I assume means nothing has been written back to me.
I the stream was opened by stream_socket_client() and did not return false or throw an exception. I know that my fwrite successfully wrote 154 bytes to that stream as well. Why would there be no response from Apple?
Here is the code for connecting to APNS:
function openConnection() {
$streamContext = stream_context_create();
stream_context_set_option($streamContext, 'ssl', 'local_cert', $this->APNS_CERT);
stream_context_set_option($streamContext, 'ssl', 'passphrase', $this->Password);
$apns = stream_socket_client('ssl://' . $this -> APNS_HOST . ':' . $this -> APNS_PORT, $error, $errorString, 60, STREAM_CLIENT_CONNECT, $streamContext);
return $apns;
}
Then, in the next method after calling openConnection:
//open the connection to use with apple.
$apns = $this->openConnection();
$alert = $message['alert'];
$badge = $message['badge'];
$deviceToken = $message['deviceToken'];
$payload['aps'] = array(
'alert' => $message['alert'],
'badge' => $message['badge'],
'sound' => $message['sound']
);
if ($message['extraPayload'] != null) {
$payload['acme'] = $message['extraPayload'];
}
$encodedString = json_encode($payload);
//create message
$apnsMessage = chr(1) . pack("N", $message['identifier']) . pack("N", $message['expire']) . pack("n", 32) . pack('H*', str_replace(' ', '', $message['deviceToken'])) . pack("n",strlen($encodedString)) . $encodedString;
$write = fwrite($apns, $apnsMessage);
echo $write;
//the echo was just to see if it wrote.
if (!$apns) {
socket_close($apns);
fclose($apns);
echo "connection to APNS was lost.";
}
//look for changes. $null=null because some bug doesn't just let you pass null.
$null = null;
$changedStreams = stream_select($streamArray, $null, $null, 0, 1000000);
//check if it is actually false
if ($changedStreams === false) {
//close stream when done.
socket_close($apns);
fclose($apns);
echo "No response from APNs";
} elseif ($changedStreams > 0) {
//then check if what they sent back is an error and grab the error packet
$responseBinary = fread($apns, 6);
var_dump($responseBinary);
//check that it's the right thing
if ($responseBinary != false || strlen($responseBinary) == 6) {
//convert it from it's binary stream state and print.
$response = unpack('Ccommand/Cstatus_code/Nidentifier', $responseBinary);
var_dump($response);
//close stream when done.
socket_close($apns);
fclose($apns);
}
} else {
echo "Apple failed to respond, message was not sent.";
}
The var_dump at the end is NULL.
Edit:
Turns out it was an error with conflicting credentials. It was solved by creating a new pem file.

From that page:
You should regularly connect with the feedback web server and fetch the current list of those devices that have repeatedly reported failed-delivery attempts. Then you should cease sending notifications to the devices associated with those applications. See “The Feedback Service” for more information.
Feedback Service
Access to the feedback service takes place through a binary interface similar to that used for sending push notifications. You access the production feedback service via feedback.push.apple.com, port 2196; you access the sandbox feedback service via feedback.sandbox.push.apple.com, port 2196. As with the binary interface for push notifications, you must use TLS (or SSL) to establish a secured communications channel. The SSL certificate required for these connections is the same one that is provisioned for sending notifications. To establish a trusted provider identity, you should present this certificate to APNs at connection time using peer-to-peer authentication.

Related

APNS not working, SSL Broken Pipe & bad write retry

I'm developing apple application for my company,
I'm working on server side using PHP, laravel framework to send message to APNS.
For testing, I use hardcode device token.
I get it work on one device that I hardcode.
so I'm advance to function that send apns, a device token that requested in function.
so when I call this function, it get device token from database and send to apns.
I get error
fwrite(): SSL operation failed with code 1. OpenSSL Error messages: error:1409F07F:SSL routines:SSL3_WRITE_PENDING:bad write retry
and
fwrite(): SSL: Broken pipe
I change the code back to first testing, but it does not work like before.
anyone have idea what I made wrong or did I made a mistake in my request?
for note this is my code
$apnsServer = 'ssl://gateway.sandbox.push.apple.com:2195';
$privateKeyPassword = 'my_private_key';
$id = $request->input('id');
$message = "message_here";
$userSelected = User::select('device_token')->where('id', $id)->first();
$deviceToken = str_replace(' ', '', $userSelected['device_token']);
$pushCertAndKeyPemFile = $_SERVER['DOCUMENT_ROOT'].'my_certificate.pem';
$stream = stream_context_create();
stream_context_set_option($stream, 'ssl', 'passphrase', $privateKeyPassword);
stream_context_set_option($stream, 'ssl', 'local_cert', $pushCertAndKeyPemFile);
$connectionTimeout = 30;
$connectionType = STREAM_CLIENT_CONNECT | STREAM_CLIENT_PERSISTENT;
$connection = stream_socket_client($apnsServer, $errorNumber, $errorString, $connectionTimeout, $connectionType, $stream);
if (!$connection){
echo "Failed to connect to the APNS server. Error = $errorString <br/>";
exit;
}else{
echo "Successfully connected to the APNS. Processing...</br>";
}
$messageBody['aps'] = array('alert' => $message, 'sound' => 'default', 'badge' => 1);
$payload = json_encode($messageBody);
$notification = chr(0) . pack('n', 32) . pack('H*', $deviceToken) . pack('n', strlen($payload)) . $payload;
$wroteSuccessfully = fwrite($connection, $notification, strlen($notification));
if (!$wroteSuccessfully){
echo "Could not send the message<br/>";
} else {
echo "Successfully sent the message<br/>";
}
After some research, i've found my problem.
When our server send PUSH message to APNS, there is response error need to check for every request.
After check error, i get error message 8, invalid token
When using development certificate, and there is user register using development application, they get device token
but when you change to production, you have to change production certificate and registered user need to get new device token to make PUSH notification work
because device token you get in development apps, will not work with production certificate..
it's totally my mistake to miss important things..

Forbidden access when connecting to Apple Push Notification service

I have some troubleshooting when trying to push a notification to my iPhone using the APNs. I use a PHP code that I keep seeing over and over on the Internet so I don't think the problem is about it:
<?php
// Put your device token here (without spaces):
$deviceToken = 'mytoken'; // Of course this is my correct token here
// Put your private key's passphrase here:
$passphrase = 'mypassphrase'; // Same here
$message = $argv[1];
$url = $argv[2];
if (!$message || !$url)
exit('Example Usage: $php newspush.php \'Breaking News!\' \'https://raywenderlich.com\'' . "\n");
////////////////////////////////////////////////////////////////////////////////
$ctx = stream_context_create();
stream_context_set_option($ctx, 'ssl', 'local_cert', __DIR__.'/cert.pem');
stream_context_set_option($ctx, 'ssl', 'passphrase', $passphrase);
// Open a connection to the APNS server
$fp = stream_socket_client(
'gateway.sandbox.push.apple.com:2195', $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',
'link_url' => $url,
);
// 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;
stream_set_blocking($fp, 0);
// 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);
I have the output "Message successfully delivered", so the connection has been successfully established, idem for the sent message. However, the device does not receive anything.
I used the feedback feature to try to know what's wrong, but the result is empty (the array is literally empty):
<?php
function send_feedback_request() {
//connect to the APNS feedback servers
//make sure you're using the right dev/production server & cert combo!
$stream_context = stream_context_create();
stream_context_set_option($stream_context, 'ssl', 'local_cert', __DIR__.'/cert3.pem');
stream_context_set_option($stream_context, 'ssl', 'passphrase', 'meteor0405');
$apns = stream_socket_client('ssl://feedback.sandbox.push.apple.com:2196', $errcode, $errstr, 60, STREAM_CLIENT_CONNECT, $stream_context);
if(!$apns) {
echo "ERROR $errcode: $errstr\n";
return;
}
$feedback_tokens = array();
//and read the data on the connection:
while(!feof($apns)) {
$data = fread($apns, 38);
if(strlen($data)) {
$feedback_tokens[] = unpack("N1timestamp/n1length/H*devtoken", $data);
}
}
fclose($apns);
return $feedback_tokens;
}
var_dump(send_feedback_request());
Would you have any idea on what I can do to figure out what's wrong? Can it be due to the certificate? A .cer file has been generated from the Apple website and been imported into Keychain Access to make a .p12 file. Then, I converted it to .pem format thanks to an openssl command, and this is the one I use in files below.
Thank you!
If you are getting the successful message then it means your PHP code is correct but something is missing from the pem file.Checkout the pem file creation steps here https://www.digicert.com/ssl-support/pem-ssl-creation.htm and try it again.Hope it will help.
The problem was indeed coming from my PEM file. To anyone having the same issue, here is what I did.
After having downloaded the certificate and private key from the iPhone Developper Connection Portal in the .p12 format, I had to convert them into .pem format. After that, I had to merge the two files (put the content of the certificate first, then the private key). This merge has to be put in a new .pem file, and this is the file to use in the PHP script.
Here is the full tutorial I followed: https://blog.serverdensity.com/how-to-build-an-apple-push-notification-provider-server-tutorial/
Moreover, the PHP code is not exactly the same. The one I used in the first post was not correct, use the tutorial's one instead.

How to send 1000 push notification at same time to ios devices without using the loop in php

How to implpement on below code for send 1000 push notification at same time server to ios deivce using php.
function push_iphone($deviceTokenid,$message){
$passphrase = '12345';
$ctx = stream_context_create();
stream_context_set_option($ctx, 'ssl', 'local_cert', 'ck.pem');
stream_context_set_option($ctx, 'ssl', 'passphrase', $passphrase);
$fp = stream_socket_client(
'ssl://gateway.sandbox.push.apple.com:2195', $err,
$errstr, 60, STREAM_CLIENT_CONNECT|STREAM_CLIENT_PERSISTENT, $ctx);
if (!$fp)
return false;
echo 'Connected to APNS' . PHP_EOL;
// Create the payload body
$body['aps'] = array(
'alert' => $message,
'message' => $message,
'sound' => 'default',
'flag' => '0'
);
// Encode the payload as JSON
$payload = json_encode($body);
//echo $device_token_id;
// Build the binary notification
$msg = chr(0) . pack('n', 32) . pack('H*', $deviceTokenid) . pack('n', strlen($payload)) . $payload;
// Send it to the server
$result = fwrite($fp, $msg, strlen($msg));
if(!$result){
echo "tst";
return true;
}else{ echo "test ";
return false;
}
fclose($fp);
}
Can any body help me about it.
Apple has an answer to this in their documentation:
You may establish multiple connections to the same gateway or to
multiple gateway instances. If you need to send a large number of
remote notifications, spread them out over connections to several
different gateways. This improves performance compared to using a
single connection: it lets you send the remote notifications faster,
and it lets APNs deliver them faster.
Keep your connections with APNs open across multiple notifications;
don’t repeatedly open and close connections. APNs treats rapid
connection and disconnection as a denial-of-service attack. 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 ok to use a new connection each day.
By threading your php application you can achieve this at (almost) the same time. Take a look at Patterns for PHP multi processes? for a direction which to take to setup forks.

Apple Push Notifications System Not Receving Message! (PHP)

This is the code I wrote to send apple push notifications.
in the device_tokens i'm sending an array with all of the tokens I want to send message to.
For some reason, I stopped recieving messages from this system.
and it used to work before!
private function send_iphone_notification($device_tokens, $message)
{
define("PRODUCTION_MODE", true);
$apnsPort = 2195;
// Choose dev or production apns host and certificate
if(PRODUCTION_MODE) {
$apnsHost = 'gateway.push.apple.com';
$apnsCert = $_SERVER['DOCUMENT_ROOT'].'/ant/www/apns/PushIphone.pem';
} else {
$apnsHost = 'gateway.sandbox.push.apple.com';
$apnsCert = $_SERVER['DOCUMENT_ROOT'].'/ant/www/apns/CertificatesDev.pem';
}
// Notification content
$payload = json_encode(array('notification_from_panel' => 1, 'aps' => array('alert' => $message, 'badge' => 1, 'sound' => 'default')));
$streamContext = stream_context_create();
stream_context_set_option($streamContext, 'ssl', 'local_cert', $apnsCert);
stream_context_set_option($streamContext, 'ssl', 'passphrase', "");
// loop all devices tokens
foreach ($device_tokens as $device_token)
{
try {
$apns = stream_socket_client('ssl://' . $apnsHost . ':' . $apnsPort, $error, $errorString, 2, STREAM_CLIENT_CONNECT, $streamContext);
} catch(Exception $e) {
echo $e->getMessage();
continue;
}
$deviceToken = str_replace(" ","",substr($device_token,1,-1));
//echo $deviceToken;
$apnsMessage = chr(0) . chr(0) . chr(32) . pack('H*', str_replace(' ', '', $deviceToken)) . chr(0) . chr(mb_strlen($payload)) . $payload;
fwrite($apns, $apnsMessage);
//socket_close($apns);
fclose($apns);
}
}
I have some experience with Apple's push service and i have to say that apple seems to have soem restrictions in place.
First of all you dont need to close and open the socket connection for every message. That will save you a lot of time. On the other hand you have to check if the connection is broken before you send the next message.
Anyway, it seemed that apple does not like it much when you connect a hundred times a second and send messages. Apples has blocked me for that as well in the passed.
But when you send the message with a already established connection, and at some point the connection is returning an error/ is disconnected, you dont know at what message he stopped. But you can define a identifier for each message and check the error returned when the connection is broken.
I noticed that apples server returns the error with a couple of seconds delay. That makes it hard do deal with it the easy way.
I have written a class to handle all of that but did not jet have time to publish it! If you are interested in it, check the github project for it at https://github.com/tinned-software/PHP-Tinned-MobilePush
I hope to finish the documentation soon.

Apple Push Notification - PHP - SSL operation failed with code 1

For the last few days, we are experiencing some strange behaviour with PHP when using sockets to connect to APN servers on our production server.
For most of the times, the payload is pushed without any errors and the client receives the notification. However on some cases we start receiving a PHP error (even though we are receiving an error, sometimes the notification is pushed). When we start to see this error, it continues for hours then disappears and PHP continues to work like nothing has happened.
Another strange thing is that, running the same PHP code from shell produces no errors whatsoever. Running it from web (nginx / php-fpm) does... PHP running on shell and web have the same configuration and share the same php.ini. The only difference is web is running on php-fpm.
Also, the same code + certificate runs on our staging server without any errors. Production server is a copy of the staging server, so every configuration is same.
We were able to find a few answers to what might be causing this error, including answers from stackoverflow.com, but we were not able to find a solution or solve it.
Notifications to Apple servers are sent one by one, and not as a bundle. But we are not making too many connections (a thousand a day maybe). There is no queue system.
So, in short
We are sometimes receiving a PHP error while sending our notifications, but not always.
Sending notifications from shell via same PHP does not produce any errors
Sending notifications from staging server does not produce any errors
We tried these
Recreating the certificate and key
Recreating the PEM file
Changing ssl:// to sslv3://
Using stream_socket_client
Using fsockopen
Changing/removing certificate password
The error is:
2012/08/28 12:18:09 [error] 4282#0: *225858 FastCGI sent in stderr:
"PHP message: PHP Warning: fwrite() [<a href='function.fwrite'>function.fwrite</a>]:
SSL operation failed with code 1. OpenSSL Error messages:
error:1409F07F:SSL routines:func(159):reason(127) in
/usr/local/nginx/html/play/classes/PushNotification.php on line 283"
while reading response header from upstream, client: 94.---.---.---,
server: play.--------.com, request: "POST /game_request_random.php HTTP/1.1",
upstream: "fastcgi://unix:/var/run/phpfpm.sock:",
host: "play.--------.com", referrer: "http://--------.com/"
The code connecting and sending the payload from the php is actually part of a class, this part is what makes the connection and sends the payload:
private function ConnectAndSend ( $msg = false ) {
$ctx = stream_context_create();
stream_context_set_option( $ctx, 'ssl', 'local_cert', $this->certificate );
stream_context_set_option( $ctx, 'ssl', 'passphrase', $this->certificatepass );
// Open a connection to the APNS server
$fp = stream_socket_client( APN_SERVER, $err, $errstr, 60, STREAM_CLIENT_CONNECT | STREAM_CLIENT_PERSISTENT, $ctx );
if ( !$fp ) {
errorlog( "Push notification error : $err $errstr" );
$this->error = "$err $errstr";
return;
}
// Build the notification
if ( !$msg ) {
$msg = chr( 0 ) . pack( 'n', 32 ) . pack( 'H*', $this->devicetoken ) . pack( 'n', strlen( $this->payload ) ) . $this->payload;
}
// Send it to the server
if ( !($result = fwrite( $fp, $msg, strlen( $msg ) )) ) {
// Could not send
$this->error = 'Notification could not be send';
errorlog( "Push notification error : {$this->error}" );
} else {
// Notification sent
$this->error = false;
errorlog( "Push notification sent" );
}
fclose($fp);
// Reset the content
$this->devicetoken = false;
$this->message = false;
$this->command = false;
$this->badge = 0;
$this->payload = false;
$this->sound = false;
}
stream_socket_connection is the 283rd line appearing in the error message
We are not using the sandbox (sslv3://gateway.push.apple.com:2195)
PHP version is 5.3.15
Is this a PHP or OpenSSL bug that we don't know of? Any ideas what and where to check?
Does Apple have a site where we can check the current health of the APN network?
Any help is greatly appreciated...
Thanks
Check out this code to send multiple messages
$i = 0;
while($res = mysql_fetch_array( $result )) {
$deviceTokens[$i] = $res['token'];
$i++;
}
// APNs Push testen auf Token
//$deviceToken = $token; // Hier das Device-Token angeben, ist 64-stellig
// Payload erstellen und JSON codieren
$message = $_POST['message'];
$message = utf8_encode($message);
$payload['aps'] = array('alert' => 'Neuer Artikel in Aktuelles', 'badge' => +1, 'sound' => 'default');
if (trim($message) != '') {
$payload['aps'] = array('alert' => "$message", 'badge' => 1, 'sound' => 'default');
}
$payload = json_encode($payload);
//Development: $apnsHost = 'gateway.sandbox.push.apple.com';
$apnsHost = 'gateway.push.apple.com';
$apnsPort = 2195;
//Development: $apnsCert = 'apsDevBundle.pem';
$apnsCert = 'apns-dev.pem';
// Stream erstellen
$streamContext = stream_context_create();
stream_context_set_option($streamContext, 'ssl', 'local_cert', $apnsCert);
$apns = stream_socket_client('ssl://' . $apnsHost . ':' . $apnsPort, $error, $errorString, 2, STREAM_CLIENT_CONNECT, $streamContext);
if ($error==0)
{
for($i = 0; $i<count($deviceTokens); $i++) {
// Build the binary notification
$apnsMessage = chr(0) . chr(0) . chr(32) . pack('H*', str_replace(' ', '', $deviceTokens[$i])) . chr(0) . chr(strlen($payload)) . $payload;
fwrite($apns, $apnsMessage);
}
// Verbindung schliessen
fclose($apns);
}
else
{
var_dump($error);
var_dump($errorString);
die("Fehler aufgetreten.");
}

Categories