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.");
}
Related
i have a problem with Notifications in ios13. If i use gateway.sandbox i can see the notification, but i'have downloaded the app directly from AppStore. Instead using gateway.apple i see 'Delivered Message to APNS', but nothing on my device.
I also tried to recreate the Certificate, .p12 and .pem file.
Any idea? Thank you
This is my php
public function iOS($data, $devicetoken)
{
// $tHost = 'gateway.sandbox.push.apple.com';
$tHost = 'gateway.push.apple.com';
$tPort = 2195;
$tCert = 'pushcert.pem';
$tPassphrase = 'pushcertpsw12';
$tToken = $devicetoken;
$tSound = 'default';
$tPayload = 'APNS payload';
$tBody['aps'] = array(
'apns-priority' => 10,
'badge' => +1,
'alert' => $data['mtitle'] .' ' .$data['mdesc'],
'sound' => 'default'
);
$tBody ['payload'] = $tPayload;
$tBody = json_encode ($tBody);
$tContext = stream_context_create ();
stream_context_set_option ($tContext, 'ssl', 'local_cert', $tCert);
stream_context_set_option ($tContext, 'ssl', 'passphrase', $tPassphrase);
$tSocket = stream_socket_client ('ssl://'.$tHost.':'.$tPort, $error, $errstr, 30, STREAM_CLIENT_CONNECT|STREAM_CLIENT_PERSISTENT, $tContext);
if (!$tSocket)
exit ('APNS Connection Failed:' .$error. ' ' .$errstr . PHP_EOL);
$tMsg = chr (0) . chr (0) . chr (32) . pack ('H*', $tToken) . pack ('n', strlen ($tBody)) . $tBody;
// Send the Notification to the Server.
$tResult = fwrite ($tSocket, $tMsg, strlen ($tMsg));
if ($tResult)
echo 'Delivered Message to APNS' . PHP_EOL;
else
echo 'Could not Deliver Message to APNS' . PHP_EOL;
// Close the Connection to the Server.
fclose ($tSocket);
There are two different environments of APNS tokens; Sandbox and Production. They require completely separate setups, permissions, registration, and push setups. Pushing a Sandbox APNS notification to a Production token will not work, the same with Production APNS to Sandbox token.
If you have verified in Sandbox but not in production, you probably need to dupe all your permissions and certificates for production.
Also double check where you're pushing to with your PHP code and ensure it's the correct environment (Sandbox vs Prod).
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..
I am trying to send iOS push notifications from a PHP App Engine backend but I am receiving the following errors. I am not sure if there is a problem with the certificate, the way I'm doing it, or something specific to App Engine. This is my first time sending push notifications to iOS.
Here is my code :
$ctx = stream_context_create();
stream_context_set_option($ctx, 'ssl', 'local_cert', 'ck.pem');
$fp = stream_socket_client(
'ssl://gateway.sandbox.push.apple.com:2195', $err,
$errstr, 60, STREAM_CLIENT_CONNECT|STREAM_CLIENT_PERSISTENT, $ctx);
if (!$fp)
{
//Handle Error
}
$body['aps'] = array(
'alert' => $data["message"],
'sound' => 'default',
);
$body["postID"] = $data["postID"];
$body["groupID"] = $data["groupID"];
$body["type"] = $data["type"];
$payload = json_encode($body);
foreach ($registrationIds as $registrationID)
{
$deviceToken = $registrationID;
$msg = chr(0) . pack('n', 32) . pack('H*', $deviceToken) . pack('n', strlen($payload)) . $payload;
$result = fwrite($fp, $msg, strlen($msg));
}
fclose($fp);
Here is the error I get :
PHP Warning: stream_socket_client(): SSL operation failed with code 1. OpenSSL
Error messages: error:14094410:SSL routines:SSL3_READ_BYTES:sslv3 alert handshake
failure in fakefile.php
I have no idea what the cause is. Thanks in Advance
UPDATE :
That is no longer the error I get. Now I get this :
PHP Warning: stream_socket_client(): unable to connect to ssl://gateway.sandbox.push.apple.com:2195 (Unknown error)
May be your port 2195 and 2196 are closed from server side that are blocking push notifications.
In stream_context_set_option you did not include the full path to the ck.pem file . After giving the full path there was no error.
Following is code may help to solve your problem.
$ctx = stream_context_create();
stream_context_set_option($ctx, 'ssl', 'local_cert', '/Users/Development/Dev/ck.pem');
Following code for checking port is enable for Push notification.
<?php
chkServer('gateway.sandbox.push.apple.com', 2195);
//chkServer('gateway.push.apple.com',2195);
function chkServer($host, $port)
{
$hostip = #gethostbyname($host);
if ($hostip == $host) {
echo "Server is down or does not exist";
} else {
if (!$x = #fsockopen($hostip, $port, $errno, $errstr, 5)) {
echo "Port $port is closed.";
} else {
echo "Port $port is open.";
if ($x) {
#fclose($x);
}
}
}
}
?>
Happy coding.
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'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.