APN/iOS: when do device tokens become invalid? - php

My iOS app should support push notifications through APN and I have uninstalled and re-installed my app into my iPhone 6S three times which means that I have three different couples <device-id, device-token> in an apposite MySQL table of which only the last (the maximum id) is the most recent and thus the valid one. Then, I wrote this PHP script which I found here in StackOverflow which should return the invalid tokens to be deleted from the database:
<?php
$cert_file = './superbar-development.pem';
$stream_context = stream_context_create();
stream_context_set_option($stream_context, 'ssl', 'local_cert', $cert_file);
$apns_connection = stream_socket_client('feedback.sandbox.push.apple.com:2196', $error_code, $error_message, 60, STREAM_CLIENT_CONNECT, $stream_context);
if($apns_connection === false) {
apns_close_connection($apns_connection);
error_log ("APNS Feedback Request Error: $error_code - $error_message", 0);
}
$feedback_tokens = array();
while(!feof($apns_connection)) {
$data = fread($apns_connection, 38);
if(strlen($data)) {
$feedback_tokens[] = unpack("N1timestamp/n1length/H*devtoken", $data);
}
}
fclose($apns_connection);
print_r($feedback_tokens);
?>
But I always get and empty array which means that the first two couples <device-id, device-token> have not been detected as invalid yet. How much time does it take to do that? I am asking this because I have seen that connecting to and reading from feedback.sandbox.push.apple.com:2196 is very very slow so I wouldn't like to cron to do it every time it has to send push notifications. Do you know anything about this? Thank you.

Related

Send a bulk SMS using Twilio PHP library to 2,000+ numbers

I have an integration with Twilio's PHP library to bulk send SMS messages. This is all working fine with 5-10 numbers, however, I keep getting a 504 timeout error after 60 seconds when sending to hundreds of numbers. I have created a simple form that you can type a message into which submits to Twilio's API. My integration opens a CSV which contains all the numbers and sends the message to each number. The issue I have is that after 60 seconds I hit a 504 timeout error and therefore only 200-300 numbers receive my SMS. How can I avoid this? Is there a way to break down the nubmers into batches?
<?php
require __DIR__ . '/src/Twilio/autoload.php';
use Twilio\Rest\Client;
// Your Account SID and Auth Token from twilio.com/console
$sid = 'xxxxxxxxxxxxxxx';
$token = 'xxxxxxxxxxxxx';
$notify_sid = 'xxxxxxxxxxxx';
$client = new Client($sid, $token);
$row = 1;
if (($handle = fopen("test.csv", "r")) !== FALSE) {
while (($data = fgetcsv($handle, 100, ",")) !== FALSE) {
$num = count($data);
$row++;
for ($c=0; $c < $num; $c++) {
$numbersFinal = "'".$data[$c] . "',";
if ($_SERVER["REQUEST_METHOD"] == "POST") {
// Use the client to do fun stuff like send text messages!
$message = $_POST['message'];
$phoneNumbers = array($numbersFinal);
$to = array();
foreach ($phoneNumbers as $phoneNumber) {
$to[] = '{"binding_type":"sms", "address":"'.$phoneNumber.'"}';
}
$request_data = [
"toBinding" => $to,
'body' => $message
];
// Create a notification
$notification = $client
->notify->services($notify_sid)
->notifications->create($request_data);
}
}
}
fclose($handle);
}
?>
504 is a timeout error. This error occurs because of your php.ini settings. Please check the phpinfo() output. Look for max_execution_time variable. It's probably 60 seconds. While you run the file, server terminates the process after this time exceeded.
To handle this problem, you can follow several ways:
Ask your service provider to raise this limit. Usually, they reject those requests.
You can run this file as a cron task. When you click send button, you can save it as a cronjob. You can figure it out.
When you click send, you can send sms by 100, 100, ... send_sms.php?sent=100 could send 100 sms then redirect to send_sms.php?sent=200... until they end.
Tell me if any of these solutions work for you.

APNS fails to deliver message after inactivity

I have created a client-server solution for APNS in PHP.
The "server" is a continously running script that waits for connections and copies the content to APNS. This means it keeps the connection to APNS open all the time. I use fwrite and stream context as such. This is the part that accepts incoming connections:
if ($client = #stream_socket_accept($this->socket, -1)) {
$this->writeLine("Received connection.");
$this->inputBuffer = stream_get_contents($client);
}
It runs in a while-loop, so that next iteration copies the contents of inputBuffer to APNS as such:
$writtenBytes = #fwrite($this->connectionAPNS, $this->inputBuffer, strlen($this->inputBuffer));
I then check for bytes written like:
if ($writtenBytes > 0) {
$this->writeLine("Copied $writtenBytes bytes to APNS.");
return true;
}
Now, the problem arises when the connection has been sitting idle for some time. The first push notification WILL return the number of bytes written to APNS - such as "Copied 157 bytes to APNS.", indicating that the fwrite works, but the message is not delivered.
The next notification then returns 0 bytes written, reconnects automatically (part of my script) and writes the bytes that failed - then it works.
Why do I get bytes written returned if the connection actually failed to write or if APNS did not accept them?
I use the enhanced notification format and I check for errors returned, which seem to be none.
I use this code for that, which I found on a blog, where I pass the connection handle to the function after writing to APNS:
function checkAppleErrorResponse($fp)
{
$apple_error_response = fread($fp, 6);
if ($apple_error_response) {
error_response = unpack('Ccommand/Cstatus_code/Nidentifier', $apple_error_response);
if ($error_response['status_code'] == '0') {
$error_response['status_code'] = '0-No errors encountered';
} else {
// Here is some fancy if/else error handling, but I never end in this else , so I excluded it.
}
}
}
The 'client' part that keeps the connection APNS open looks like this, and is called whenever writtenBytes returns 0 (error) or when the script first loads:
function connectToAPNS()
{
$ctx = stream_context_create();
stream_context_set_option($ctx, 'ssl', 'local_cert', $this->certpath);
stream_context_set_option($ctx, 'ssl', 'passphrase', $this->passphrase);
// Open a connection to the APNS server
$error = "";
$errno = 0;
$this->connectionAPNS = stream_socket_client(
'ssl://gateway.push.apple.com:2195',
$errno,
$error,
60,
STREAM_CLIENT_CONNECT | STREAM_CLIENT_PERSISTENT,
$ctx
);
if ($this->connectionAPNS) {
stream_set_blocking($this->connectionAPNS, 0);
$this->writeLine("Connected to APNS!");
return true;
}
}
Is your app a release or a debug version? In each case you have to communicate with a different web-service and use different certificates.
In sandbox/debug mode communicate with ssl://gateway.sandbox.push.apple.com:2195
In production/relase mode communicate with ssl://gateway.push.apple.com:2195
The notification will be silently dropped, if you communicate with the wrong service.

AWS S3 Download counter

I have a file uploaded in AWS s3 bucket and set that file to public permission . i want to share that file in my Facebook .. the thing is i can just copy that public link and share it . but i also want the count of the downloads to stored .. in other way i want to host a php file in my web hosting where there will be a tab like bar in which that file name,file size, download link and total download count will be there . Please help me with the code
I tried the following code which i got from google search but no use
<?php
$aws_key = '_YOUR_AWS_KEY_000000';
$aws_secret = '_your_aws_secret_00000000000000000000000';
$aws_bucket = 'anyexample-test'; // AWS bucket
$aws_object = 'test.png'; // AWS object name (file name)
if (strlen($aws_secret) != 40) die("$aws_secret should be exactly 40 bytes long");
$dt = gmdate('r'); // GMT based timestamp
// preparing string to sign
$string2sign = "GET
{$dt}
/{$aws_bucket}/{$aws_object}";
// preparing HTTP query
$query = "GET /{$aws_bucket}/{$aws_object} HTTP/1.1
Host: s3.amazonaws.com
Connection: close
Date: {$dt}
Authorization: AWS {$aws_key}:".amazon_hmac($string2sign)."\n\n";
echo "Downloading: http://s3.amazonaws.com/{$aws_bucket}/{$aws_object}\n";
list($header, $resp) = downloadREST($fp, $query);
echo "\n\n";
if (strpos($header, '200 OK') === false) // checking for error
die($header."\r\n\r\n".$resp);
$aws_object_fs = str_replace('/', '_', $aws_object);
// AWS object may contain slashes. We're replacing them with underscores
#$fh = fopen($aws_object_fs, 'wb');
if ($fh == false)
die("Can't open file {$aws_object_fs} for writing. Fatal error!\n");
echo "Saving data to {$aws_object_fs}...\n";
fwrite($fh, $resp);
fclose($fh);
// Sending HTTP query, without keep-alive support
function downloadREST($fp, $q)
{
// opening HTTP connection to Amazon S3
// since there is no keep-alive we open new connection for each request
$fp = fsockopen("s3.amazonaws.com", 80, $errno, $errstr, 30);
if (!$fp) die("$errstr ($errno)\n"); // connection failed, pity
fwrite($fp, $q); // sending query
$r = ''; // buffer for result
$check_header = true; // header check flag
$header_end = 0;
while (!feof($fp)) {
$r .= fgets($fp, 256); // reading response
if ($check_header) // checking for header
{
$header_end = strpos($r, "\r\n\r\n"); // this is HTTP header boundary
if ($header_end !== false)
$check_header = false; // We've found it, no more checking
}
}
fclose($fp);
$header_boundary = $header_end+4; // 4 is length of "\r\n\r\n"
return array(substr($r, 0, $header_boundary), substr($r, $header_boundary));
}
// hmac-sha1 code START
// hmac-sha1 function: assuming key is global $aws_secret 40 bytes long
// http://en.wikipedia.org/wiki/HMAC
// warning: key is padded to 64 bytes with 0x0 after first function call
// hmac-sha1 function
function amazon_hmac($stringToSign)
{
if (!function_exists('binsha1'))
{ // helper function binsha1 for amazon_hmac (returns binary value of sha1 hash)
if (version_compare(phpversion(), "5.0.0", ">=")) {
function binsha1($d) { return sha1($d, true); }
} else {
function binsha1($d) { return pack('H*', sha1($d)); }
}
}
global $aws_secret;
if (strlen($aws_secret) == 40)
$aws_secret = $aws_secret.str_repeat(chr(0), 24);
$ipad = str_repeat(chr(0x36), 64);
$opad = str_repeat(chr(0x5c), 64);
$hmac = binsha1(($aws_secret^$opad).binsha1(($aws_secret^$ipad).$stringToSign));
return base64_encode($hmac);
}
// hmac-sha1 code END
?>
I would suggest using the official AWS SDK for PHP, because it has all of the request signing and handling logic implemented for you. Here is an article by one of the SDK's developers that is relevant to what you are doing: Streaming Amazon S3 Objects From a Web Server
Infact if you just need to see the number of downloads, you can achieve this without running yourown server with php.
This info is already available in the S3 bucket logs, if you enable. This will be more accurate, since the in the PHP approach there is no way to track download, if the user take the S3 link directly and share/download.
These logs are little difficult to parse though, but the services like https://qloudstat.com and http://www.s3stat.com/ help here.
Another point: Downloads will be considerably faster, if you enable CDN - Cloudfront in front of the S3 bucket.

APNS SSL operation failed with code 1

EDIT - Using the enhanced binary format
Turns out I wasn't using the enhanced binary format so I changed my code.
<?php
$message = $_POST['message'];
$passphrase = $_POST['pass'];
//Connect to db
if ($db_found) {
// Create the payload body
$body['aps'] = array(
'alert' => $message,
'sound' => 'default'
);
$streamContext = stream_context_create();
stream_context_set_option($streamContext, 'ssl', 'local_cert', 'x.pem');
stream_context_set_option($streamContext, 'ssl', 'passphrase', $passphrase);
$fp = stream_socket_client('ssl://gateway.push.apple.com:2195', $error, $errorString, 15, STREAM_CLIENT_CONNECT, $streamContext);
stream_set_blocking ($fp, 0);
if (!$fp)
exit("Failed to connect: $err $errstr" . PHP_EOL);
echo 'Connected to APNS for Push Notification' . PHP_EOL;
// Keep push alive (waiting for delivery) for 90 days
$apple_expiry = time() + (90 * 24 * 60 * 60);
$tokenResult = //SQL QUERY TO GET TOKENS
while($row = mysql_fetch_array($tokenResult)) {
$apple_identifier = $row["id"];
$deviceToken = $row['device_id'];
$payload = json_encode($body);
// Enhanced Notification
$msg = pack("C", 1) . pack("N", $apple_identifier) . pack("N", $apple_expiry) . pack("n", 32) . pack('H*', str_replace(' ', '', $deviceToken)) . pack("n", strlen($payload)) . $payload;
// SEND PUSH
fwrite($fp, $msg);
// We can check if an error has been returned while we are sending, but we also need to
// check once more after we are done sending in case there was a delay with error response.
checkAppleErrorResponse($fp);
}
// Workaround to check if there were any errors during the last seconds of sending.
// Pause for half a second.
// Note I tested this with up to a 5 minute pause, and the error message was still available to be retrieved
usleep(500000);
checkAppleErrorResponse($fp);
echo 'Completed';
fclose($fp);
// SIMPLE BINARY FORMAT
/*for($i = 0; $i<count($deviceToken); $i++) {
// Encode the payload as JSON
$payload = json_encode($body);
// Build the binary notification
$msg = chr(0) . pack('n', 32) . pack('H*', $deviceToken[$i]) . pack('n', strlen($payload)) . $payload;
// Send it to the server
$result = fwrite($fp, $msg, strlen($msg));
$bodyError .= 'result: '.$result.', devicetoken: '.$deviceToken[$i].'';
if (!$result) {
$errCounter = $errCounter + 1;
echo 'Message not delivered' . PHP_EOL;
}
else
echo 'Message successfully delivered' . PHP_EOL;
}*/
// Close the connection to the server
//fclose($fp);
//Insert message into database
mysql_close($db_handle);
}
else {
print "Database niet gevonden ";
mysql_close($db_handle);
}
// FUNCTION to check if there is an error response from Apple
// Returns TRUE if there was and FALSE if there was not
function checkAppleErrorResponse($fp) {
//byte1=always 8, byte2=StatusCode, bytes3,4,5,6=identifier(rowID).
// Should return nothing if OK.
//NOTE: Make sure you set stream_set_blocking($fp, 0) or else fread will pause your script and wait
// forever when there is no response to be sent.
$apple_error_response = fread($fp, 6);
if ($apple_error_response) {
// unpack the error response (first byte 'command" should always be 8)
$error_response = unpack('Ccommand/Cstatus_code/Nidentifier', $apple_error_response);
if ($error_response['status_code'] == '0') {
$error_response['status_code'] = '0-No errors encountered';
} else if ($error_response['status_code'] == '1') {
$error_response['status_code'] = '1-Processing error';
} else if ($error_response['status_code'] == '2') {
$error_response['status_code'] = '2-Missing device token';
} else if ($error_response['status_code'] == '3') {
$error_response['status_code'] = '3-Missing topic';
} else if ($error_response['status_code'] == '4') {
$error_response['status_code'] = '4-Missing payload';
} else if ($error_response['status_code'] == '5') {
$error_response['status_code'] = '5-Invalid token size';
} else if ($error_response['status_code'] == '6') {
$error_response['status_code'] = '6-Invalid topic size';
} else if ($error_response['status_code'] == '7') {
$error_response['status_code'] = '7-Invalid payload size';
} else if ($error_response['status_code'] == '8') {
$error_response['status_code'] = '8-Invalid token';
} else if ($error_response['status_code'] == '255') {
$error_response['status_code'] = '255-None (unknown)';
} else {
$error_response['status_code'] = $error_response['status_code'].'-Not listed';
}
echo '<br><b>+ + + + + + ERROR</b> Response Command:<b>' . $error_response['command'] . '</b> Identifier:<b>' . $error_response['identifier'] . '</b> Status:<b>' . $error_response['status_code'] . '</b><br>';
echo 'Identifier is the rowID (index) in the database that caused the problem, and Apple will disconnect you from server. To continue sending Push Notifications, just start at the next rowID after this Identifier.<br>';
return true;
}
return false;
}
?>
While using this new code I still can't send more than 300+ messages because of this error:
Warning: fwrite() [function.fwrite]: SSL operation failed with code 1. OpenSSL Error messages: error:1409F07F:SSL routines:SSL3_WRITE_PENDING:bad write retry in PATH_TO_SCRIPT.php on line NUMBER
this code works fine when sending just a few push messages.
OLD QUESTION with simple binary format
So I integrated Push Notifications a long time ago and it was working fine for messages sent to less than 500 people. Now I'm trying to send a push notification to more than 1000 people but then i get the broken error
Warning: fwrite() [function.fwrite]: SSL: Broken pipe in PATH_TO.PHP on line x
I've read the apple docs and I know that invalid tokens can cause the socket to disconnect. Some solutions online recommend on detecting disconnections and reconnect like this one:
Your server needs to detect disconnections and reconnect if necessary. Nothing is
"instant" when networking is involved; there's always some latency and code needs to take
that into account. Also, consider using the enhanced binary interface so you can check the
return response and know why the connection was dropped. The connection can also be
dropped as a result of TCP keep-alive, which is outside of Apple's control.
I'm also running a Feedback Service which detects Invalid tokens (Users who wanted Push Notifications but deleted the application) and that just works fine. That php script echos the deleted ID's and I can confirm that those tokens are deleted from our MySQL database.
How can I be able to detect a disconnect or broken pipe and react to that so my push notifications can reach more than 1000 people?
Currently I'm using this simple push.php script.
<?php
$message = $_POST['message'];
$passphrase = $_POST['pass'];
//Connect to database stuff
if ($db_found) {
$streamContext = stream_context_create();
stream_context_set_option($streamContext, 'ssl', 'local_cert', 'x.pem');
stream_context_set_option($streamContext, 'ssl', 'passphrase', $passphrase);
$fp = stream_socket_client('ssl://gateway.push.apple.com:2195', $error, $errorString, 15, STREAM_CLIENT_CONNECT, $streamContext);
if (!$fp)
exit("Failed to connect: $err $errstr" . PHP_EOL);
echo 'Connected to APNS for Push Notification' . PHP_EOL;
$deviceToken[] = //GET ALL TOKENS FROM DATABASE AND STORE IN ARRAY
for($i = 0; $i<count($deviceToken); $i++) {
// 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[$i]) . pack('n', strlen($payload)) . $payload;
// Send it to the server
$result = fwrite($fp, $msg, strlen($msg));
$bodyError .= 'result: '.$result.', devicetoken: '.$deviceToken[$i].'';
if (!$result) {
$errCounter = $errCounter + 1;
echo 'Message not delivered' . PHP_EOL;
}
else
echo 'Message successfully delivered' . PHP_EOL;
}
echo $bodyError;
// Close the connection to the server
fclose($fp);
//CODE TO SAVE MESSAGE TO DATABSE HERE
if (!mysql_query($SQL,$db_handle)) {
die('Error: ' . mysql_error());
}
}
else {
print "Database niet gevonden ";
mysql_close($db_handle);
}
?>
Also fwrite returns 0 written bytes when the SLL Broken Pipe error occurs.
I must also mention that I'm no PHP or web developer but an app developer so my php skills aren't that good.
When you do:
fwrite($fp, $msg);
you are trying to write to the socket. If something goes wrong, fwrite will return false or 0 (depending on the php version) as the return value. When it happens, you must manage it.
You have two possibilities:
discard the entire operation
try again the last write operation
if you choose the second option, you have to do a new fwrite($fp, $msg) with THE SAME $fp and $msg of the failed fwrite() operation.
If you change the parameters, a 1409F07F:SSL error is returned
Moreover, there are situations where the fwrite fails at writing only "some bytes", you should manage even this situation, comparing the returned value with the lenght of $msg.
In this case, you should send the remaining part of the message, but in some situations you have to send the whole message again (according to this link).
Have a look at fwrite reference and the comments: Link
I can't give you actual PHP code, since I don't know PHP, but here's the logic you should use (according to Apple) :
Push Notification Throughput and Error Checking
If you're seeing throughput lower than 9,000 notifications per second, your server might benefit from improved error handling logic.
Here's how to check for errors when using the enhanced binary interface. Keep writing until a write fails. If the stream is ready for writing again, resend the notification and keep going. If the stream isn't ready for writing, see if the stream is available for reading.
If it is, read everything available from the stream. If you get zero bytes back, the connection was closed because of an error such as an invalid command byte or other parsing error. If you get six bytes back, that's an error response that you can check for the response code and the ID of the notification that caused the error. You'll need to send every notification following that one again.
Once everything has been sent, do one last check for an error response.
It can take a while for the dropped connection to make its way from APNs back to your server just because of normal latency. It's possible to send over 500 notifications before a write fails because of the connection being dropped. Around 1,700 notifications writes can fail just because the pipe is full, so just retry in that case once the stream is ready for writing again.
Now, here's where the tradeoffs get interesting. You can check for an error response after every write, and you'll catch the error right away. But this causes a huge increase in the time it takes to send a batch of notifications.
Device tokens should almost all be valid if you've captured them correctly and you're sending them to the correct environment. So it makes sense to optimize assuming failures will be rare. You'll get way better performance if you wait for write to fail or the batch to complete before checking for an error response, even counting the time to send the dropped notifications again.
None of this is really specific to APNs, it applies to most socket-level programming.
If your development tool of choice supports multiple threads or interprocess communication, you could have a thread or process waiting for an error response all the time and let the main sending thread or process know when it should give up and retry.
This is taken from Apple's Tech Note: Troubleshooting Push Notifications.
EDIT
I don't know how you detect in PHP that the write failed, but when it does, you should attempt to write the failed notification once again, and if it fails again, try to read the error response and close the connection.
If you manage to read the error response, you will know which notification failed and you'll know the error type (the most likely error is 8 - invalid device token). If after writing 100 messages you get an error response for the 80th message, you must resend messages 81 to 100, since Apple never received them. In my case (Java server), I don't always manage to read the error response (sometimes I get an error when trying to read the response from the socket). In that case I can only move on an send the next notifications (and have no way of knowing which notifications were actually received by Apple). That's why it's important to keep your database clean of invalid tokens.
Anyway, you shouldn't be stuck in an infinite loop, since when getting an error after sending N notifications, you are not going to resend these N notifications. Unless you manage to read an error response from Apple (in which case you know exactly what to resend), you'll only resend the last notification, and even if that notification happens to be the one with the invalid token, you'll probably get the next error after sending more notifications (which is unfortunate, since it would have been much easier to detect the invalid tokens if you would get the failures immediately).
If you keep your database clean (i.e. store in it only device tokens that were sent to your App by Apple, and all of them belong to the same push environment - either sandbox or production), you shouldn't encounter any invalid device tokens.
The device tokens returned by the feedback service are not invalid tokens. They are valid tokens of devices that uninstalled your app. Invalid tokens have never been valid for the current push environment, and never will. The only way to identify invalid tokens is to read the error responses from Apple.
EDIT2:
I forgot to mention it before. I encountered a similar problem to yours when implementing the push notification server side in Java. I couldn't reliably get all the error responses returned by Apple.
I found that in Java there's a way to disable the TCP Nagle's algorithm, which causes the buffering of multiple messages before sending them in a batch to Apple. Though Apple encourages us to use Nagle's algorithm (for performance reasons), I found that when I disable it and then try to read the response from Apple after each message I send to them, I manage to receive 100% of the error responses (I verified it by writing a process that simulated the APNS server).
By disabling Nagle's algorithm and sending the notifications one by one, slowly, and atempting to read the error response after each message, you can locate all the invalid tokens in your DB and remove them. Once you know your DB is clean you can enable Nagle's algorithm and resume sending notifications quickly without bothering to read the error responses from Apple. Then, whenever you get an error while writing a message to the socket, you can simply create a new socket and retry sending only the last message.
My solution (to the now semi-old question) was that I had some development-environment APN tokens in my database trying to send to a production-environment. Once I got rid of them from my database the rest worked fine. Unfortunately, out of 7000+ APNs, I wasn't sure which tokens were bad so I had to erase them all in the hope that fresh tokens would be created when the user re-opened the app. So far so good.
Apple will halt all immediate attempts at sending a push notification if it comes across an erroneous APN token.
I had the exact same message appear which I had never seen before (below) on various apps so I'm glad I was able to resolve it.
Warning: fwrite() [function.fwrite]: SSL operation failed with code 1. OpenSSL Error messages: error:1409F07F:SSL routines:SSL3_WRITE_PENDING:bad write retry in PATH_TO_SCRIPT.php on line [NUMBER]
The solution is:
$msg = chr(0) . pack('n', 32) . pack('H*', $deviceToken) . pack('n', strlen($payload)) . $payload;
try {
$result = fwrite($fp, $msg, strlen($msg));
} catch (Exception $ex) {
sleep(1); //sleep for 5 seconds
$result = fwrite($fp, $msg, strlen($msg));
}
Googling I found some interest things
http://rt.openssl.org/Ticket/Display.html?id=598&user=guest&pass=guest
As the patch comment says
first check if there is a SSL3_BUFFER still being written
out. This will happen with non blocking IO
Answer of Why am I getting "error:1409F07F:SSL routines:SSL3_WRITE_PENDING: bad write retry" error while attempting an SSL_write? says:
SSL_Write returns with SSL_ERROR_WANT_WRITE or SSL_ERROR_WANT_READ, you have to repeat the call to SSL_write with the same parameters again, after the condition is satisfied.
Maybe the ssl buffer is still writing when you try to write, you can check if buffer is not writing, retry, or limiting the buffer could enough.
Duplicates:
Apple Push Notification - PHP - SSL operation failed with code 1
In PHP: OpenSSL Error messages: error: 1409F07F: SSL routines: SSL3_WRITE_PENDING: bad write retry
Additional (edit)
Above I try to say that you need to figure out a way to determine if socket is not writing when you try to write again and then write.
If not have a way to do it, try:
Disabling the non-blocking block
Rerty the write
while(!fwrite($fp, $msg)) {
usleep(400000); //400 msec
}
if is successful, just disable the erors via error_reporting never use # operator.
Setting stream_set_write_buffer() to 0

PHP technique to query the APNs Feedback Server

Can someone clarify what the APNs (Apple Push Notification) wants as far as how you query it?
The docs say it starts sending as soon as the connection is made. Does this mean that I don't do an fread() on it?
Here's my current code to try and read it. I did NOT put the fread() in a loop as I do not know what response indicates "no more records to read" and I didn't want an infinite loop on my server.
<?php
$apnsCert = 'HOHRO-prod.pem';
$streamContext = stream_context_create();
stream_context_set_option($streamContext, 'ssl', 'local_cert', $apnsCert);
stream_context_set_option($streamContext, 'ssl', 'verify_peer', false);
$apns = stream_socket_client('ssl://feedback.push.apple.com:2196', $error, $errorString, 60, STREAM_CLIENT_CONNECT, $streamContext);
echo 'error=' . $error;
echo 'errorString=' . $errorString;
$result = fread($apns, 38);
echo 'result=' . $result;
fclose($apns);
?>
So far all I am getting is a null reply. There are no errors so it is connecting.
I don't know if the null reply means no data is there, or my fread() is the wrong way to do it.
Thanks
Here's a big gotcha which confused me when I first tried connecting: the APNS feedback servers only return the device tokens that have "expired" since your last feedback request. Which means most of the time you'll get a NULL response unless you're already dealing with a high volume of users of your app.
So make sure you store the expired device tokens to disk or db, because after your feedback query they're gone for good. This makes testing a pain to say the least!
Here's a complete function to fetch the device tokens from the APNS feedback servers (many thanks to the answers above for helping me put it all together):
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', '/path/to/my/cert.pem');
$apns = stream_socket_client('ssl://feedback.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;
}
If all is well, the return values from this function will look something like this (via print_r()):
Array
(
Array
(
[timestamp] => 1266604759
[length] => 32
[devtoken] => abc1234..............etcetc
),
Array
(
[timestamp] => 1266604922
[length] => 32
[devtoken] => def56789..............etcetc
),
)
That code looks right however you need to loop and check for end of stream in order to read all the device codes.
while (!feof($apns)) {
$devcon = fread($apns, 38);
}
However my problem is the actual unpacking of the data. Does anyone know how to unpack the binary data which you've just read to get the actual device ID (as string) along with the timestamp etc?
I got the solution from apple forum and it is for development. Try this for production also.
"Well, as dumb as it sounds, I found a solution:
Create a dummy app id in the program portal, enable development push notifications on it
Create and download the associated provisioning profile
Create a new xcode project, and invoke the registerForRemoteNotificationTypes method on start.
Install the dummy app on your device. At this point, you should have two DEVELOPMENT apps running on your device: the original app and the dummy app. Both should be registered to receive push notifications.
Uninstall the original app, and try to send a push notification to that app.
Invoke the feedback service, and you should receive data back."
This finally worked for me.
$arr = unpack("H*", $devconts);
$rawhex = trim(implode("", $arr));
$feedbackTime = hexdec(substr($rawhex, 0, 8));
$feedbackDate = date('Y-m-d H:i', $feedbackTime);
$feedbackLen = hexdec(substr($rawhex, 8, 4));
$feedbackDeviceToken = substr($rawhex, 12, 64);
And then you simply check for the device token against the timestamp!
Just started using this library - works great for me!
https://github.com/mac-cain13/notificato

Categories