2

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

Eran
  • 387,369
  • 54
  • 702
  • 768
emrahgunduz
  • 1,404
  • 1
  • 13
  • 26
  • An addition: We increased the linux kernel limits regarding of socket connections, plus found out that the php.ini socket connection timeout is set to 600 second (not 60) as a default value. After these changes we have not seen any errors (yet). – emrahgunduz Aug 29 '12 at 06:21
  • Can you share your fsockopen implementation please? I try to use this function for my notifications. – Pierre Jan 08 '13 at 09:10
  • Do you have a solution for now, or not? Because I have the same problem. – Dmytro Zarezenko Apr 03 '13 at 16:43
  • 1
    Check php.ini for socket limits, especially timeouts. Keep it at a minimum. Our problem was php-fpm having problems with releasing the sockets. If you are using linux as the server, try changing the kernel limits regarding of the sockets (tcp/ip stack). Google for "changing linux kernel limits", some IBM sites have a few good posts about the subject. However, it is extremely dangerous so be careful. This also solved our problem (before editing php.ini), for the last 8 months we haven't seen any errors. – emrahgunduz Apr 03 '13 at 19:47
  • 3
    @emrahgunduz did you ever get this issue resolved? Im running into the same thing today with APN. Up until now it worked fine though. thanks. – tamak Aug 11 '15 at 14:40
  • 1
    @tamak first, check to be sure that you are not forgetting to close the socket before code ends. on backend if you are using linux, increase open file limits (check kernel limits). check your logs and be sure that you are not receiving FD_SETSIZE errors from php-fpm (if this is the case you must recompile php after increasing kernel FD_SETSIZE limit, search for --enable-fd-setsize). if you are on windows, sorry but I've no idea. – emrahgunduz Aug 12 '15 at 14:51

1 Answers1

-2

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.");
}
lukaswelte
  • 2,951
  • 1
  • 23
  • 45