Apple Push Notification - PHP - SSL operation fail

2019-04-02 06:16发布

问题:

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

回答1:

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.");
}