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..
Related
I am an iOS developer and i was asked to what type(whole i.e header as well as body) of request to hit APN to get notification to device.
I read many tutorial for setup of server for APN but I am unable to understand as i have no knowledge about PhP and Node Js. After reading apple document , I came to know that it uses http/2 and other various tag and value. But i am unable to construct full request.
Any help is highly appreciated.
We just use plain CURL to do http2 requests to APNS
Pre-requisites: You have a valid SSL cert converted to a .PEM from Developer console
/usr/local/Cellar/curl/7.50.0/bin/curl -v \
-d '{"aps":{"alert":"Hello","content-available": 1, "sound": ""}}' \
-H "apns-topic: com.yourapp.bundleid" \
-H "apns-expiration: 1" \
-H "apns-priority: 10" \
--http2 \
--cert /Users/PATHTOPEM/key.pem:YOURPASSWORD \
https://api.push.apple.com/3/device/YOURDEVICETOKEN
Or if you are wary of using the terminal try this MacOS app to send push notifications, it is so easy.
Pre-requisites: You need to have the cert signing authority and the private SSL cert in your keychain.
https://github.com/noodlewerk/NWPusher
To send an APNs request using PHP, you need these requirements:
A .pem certificate which should exist in the same path of your php script.
The Device Token, which is needed to send the notification to a specific device.
Then you can try the following code:
<?php
$apnsServer = 'ssl://gateway.push.apple.com:2195';
$privateKeyPassword = '1234'; // your .pem private key password
$message = 'Hello world!';
$deviceToken = 'YOUR_DEVICE_TOKEN_HERE';
$pushCertAndKeyPemFile = 'PushCertificateAndKey.pem'; // Your .pem certificate
$stream = stream_context_create();
stream_context_set_option($stream,
'ssl',
'passphrase',
$privateKeyPassword);
stream_context_set_option($stream,
'ssl',
'local_cert',
$pushCertAndKeyPemFile);
$connectionTimeout = 20;
$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 no = $errorNumber<br/>";
exit;
} else {
echo "Successfully connected to the APNS...";
}
$messageBody['aps'] = array('alert' => $message,
'sound' => 'default',
'badge' => 2,
);
$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.";
}
else {
echo "Successfully sent the message.";
}
fclose($connection);
?>
Refer to this link for more details.
My PNS is working good in development mode. I have done this using raywendelich blog. same way i have created certificates in production mode and run same script from server but not receiving any notification.
Which additional step needed when we are testing in production mode from our server.Its very urgent need. plase help what to do for production mode.
our PHP code
<?php
// Put your device token here (without spaces):
$deviceToken = 'd5d89cab86e6f1a3cfa66dd853f3f4d7dd932c4a6da793cb9c86d31e9cfcb31f';
// Put your private key's passphrase here:
$passphrase = '*******';
// Put your alert message here:
$message = '****';
$ctx = stream_context_create();
stream_context_set_option($ctx, 'ssl', 'local_cert', 'ckm.pem');
stream_context_set_option($ctx, 'ssl', 'passphrase', $passphrase);
// Open a connection to the APNS server
$fp = stream_socket_client(
'ssl://gateway.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'
);
// 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;
// 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);
You need to create the production APNS certificate (very similar to the way you created the developer certificate) and use that to connect to Apple instead. The development and production pushtokens are different so in order to get your production push token, you'll have to build an AdHoc version of your app. When you install the ad-hoc version on your device, it should ask you if you want to recieve push tokens. Accept and your code should send your production push token to your server (I'm assuming you save all pushtokens on a server somewhere). This is the token you need to use to test your production push code.
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.
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.");
}