This week i experimented with php sockets but there is one thing that does not appear to work.
I want to send a Apple push notification (apns) from within a socket server. The socket server works fine but opening another socket to send a message to apns does not work.
The server socket script i use is similar to the example of php.net. (http://www.php.net/manual/en/sockets.examples.php) The script i use to send a apns notification is the following function:
function sendToAPNS($data, $deviceTokens, $pem, $password, $debug=0)
{
$server = ($debug == 1) ? 'gateway.sandbox.push.apple.com' : 'gateway.push.apple.com';
$ctx = stream_context_create();
stream_context_set_option($ctx, 'ssl', 'local_cert', '/home/testing/pushNotifications/'.$pem);
stream_context_set_option($ctx, 'ssl', 'passphrase', $password);
$fp = stream_socket_client('ssl://'. $server .':2195', $err, $errstr, 60, STREAM_CLIENT_CONNECT|STREAM_CLIENT_PERSISTENT, $ctx);
if (!$fp)
{
echo 'APNS error:'. $err .' '. $errstr ."\n";
return;
}
$body['aps'] = array('alert' => $data['message']);
foreach($data AS $key => $value)
{
if($key != 'message') $body['aps'][$key] = $value;
}
$payload = json_encode($body);
for($i = 0,$max=count($deviceTokens); $i < $max; $i++)
{
$msg = chr(0) . pack('n', 32) . pack('H*', $deviceTokens[$i]) . pack('n', strlen($payload)) . $payload;
$result = fwrite($fp, $msg, strlen($msg));
if (!$result)
BPLog('Message['. $data['ID'] .'] not delivered to '. $deviceTokens[$i] .': '. PHP_EOL);
// ob_flush();
// flush();
}
fclose($fp);
return true;
}
The following warnings are displayed:
PHP Warning: stream_socket_client(): SSL operation failed with code 1. OpenSSL Error messages:
error:14094410:SSL routines:func(148):reason(1040) in /home/testing/pushNotifications/index.php on line 162
Warning: stream_socket_client(): SSL operation failed with code 1. OpenSSL Error messages:
error:14094410:SSL routines:func(148):reason(1040) in /home/testing/pushNotifications/index.php on line 162
PHP Warning: stream_socket_client(): Failed to enable crypto in /home/testing/pushNotifications/index.php on line 162
Warning: stream_socket_client(): Failed to enable crypto in /home/testing/pushNotifications/index.php on line 162
PHP Warning: stream_socket_client(): unable to connect to ssl://gateway.push.apple.com:2195 (Unknown error) in /home/testing/pushNotifications/index.php on line 162
Warning: stream_socket_client(): unable to connect to ssl://gateway.push.apple.com:2195 (Unknown error) in /home/testing/pushNotifications/index.php on line 162
The function works in an other environment though. I have tested it in a php file that can be called from the browser and it sends like it should. I use absolute paths for the certificates so that could not be the problem.
Maybe there is a difference in php since the socket server is called from the commandline. Or maybe it simply is not possible to open another socket stream from within a socket server?
Does anyone have an idea of what the problem can be?
Related
I have the following PHP code to send Apple Push Notifications to my app:
<?php
$body = '{"aps":{"alert":{"title":"test title","subtitle":"","body":"test body"},"badge":0,"sound":"default","additional_data":"test additional_data"}}';
$context = stream_context_create();
stream_context_set_option($context, "ssl", "local_cert", "certificate.pem");
stream_context_set_option($context, "ssl", "passphrase", "HERE_COMES_THE_PASSWORD_OF_THE_certificate.pem_FILE");
$socket = stream_socket_client("ssl://gateway.sandbox.push.apple.com:2195", $error, $errstr, 30, STREAM_CLIENT_CONNECT|STREAM_CLIENT_PERSISTENT, $context);
$msg = chr(0) . chr(0) . chr(32) . pack("H*", "HERE_COMES_MY_APPLE_PUSH_TOKEN") . pack("n", strlen($body)) . $body;
$result = fwrite($socket, $msg, strlen($msg));
fclose($socket);
?>
This code is stored on my server together with the certificate file called certificate.pem.
I'm using this code unchanged since month without any problems. Today, I noticed, that I'm not getting push notifications any more.
The PHP error log shows the following:
[23-Mar-2022 11:39:45 UTC] PHP Warning: stream_socket_client(): unable to connect to ssl://gateway.sandbox.push.apple.com:2195 (Connection timed out) in /home2/kd37875/public_html/test/index.php on line 7
[23-Mar-2022 11:39:45 UTC] PHP Warning: fwrite() expects parameter 1 to be resource, bool given in /home2/kd37875/public_html/test/index.php on line 9
[23-Mar-2022 11:39:45 UTC] PHP Warning: fclose() expects parameter 1 to be resource, bool given in /home2/kd37875/public_html/test/index.php on line 10
First, I thought, there's something wrong with the certificate file. Then, I found this: https://www.pushtry.com website. If I insert the Device Token, upload the certificate.pem file, insert the Bundle ID and a message, I'm successfully receiving a push message in my app. (There's a field for password on the website but it's also working if I don't insert it. No idea, why.) So this says me, that the certificate.pem must be okay.
Do you have any idea what I'm doing wrong? Why doesn't it work any more? Did Apple change something?
I finally solved my problem. Apple made a change to this about a year ago. No idea why it affected me only today.
This is the working code:
<?php
function sendHTTP2Push($http2_server, $apple_cert, $app_bundle_id, $message, $token) {
if(!defined('CURL_HTTP_VERSION_2_0')) {
define('CURL_HTTP_VERSION_2_0', 3);
}
$http2ch = curl_init();
curl_setopt($http2ch, CURLOPT_HTTP_VERSION, CURL_HTTP_VERSION_2_0);
curl_setopt_array($http2ch, array(
CURLOPT_URL => "{$http2_server}/3/device/{$token}",
CURLOPT_PORT => 443,
CURLOPT_HTTPHEADER => array("apns-topic: {$app_bundle_id}"),
CURLOPT_POST => TRUE,
CURLOPT_POSTFIELDS => $message,
CURLOPT_RETURNTRANSFER => TRUE,
CURLOPT_TIMEOUT => 30,
CURLOPT_SSL_VERIFYPEER => false,
CURLOPT_SSLCERT => realpath($apple_cert),
CURLOPT_HEADER => 1
));
$result = curl_exec($http2ch);
if($result === FALSE) {
throw new Exception('Curl failed with error: ' . curl_error($http2ch));
}
$status = curl_getinfo($http2ch, CURLINFO_HTTP_CODE);
return $status;
curl_close($http2ch);
}
$status = sendHTTP2Push('https://api.development.push.apple.com', 'certificate.pem', 'HERE_COMES_THE_APPS_BUNDLE_ID', '{"aps":{"alert":{"title":"test title","subtitle":"","body":"test body"},"badge":0,"sound":"default","additional_data":"test additional_data"}}', 'HERE_COMES_MY_APPLE_PUSH_TOKEN');
echo "Response code: ".$status;
?>
Source: https://gist.github.com/valfer/18e1052bd4b160fed86e6cbb426bb9fc
I am having issue with web socket connection/handshake from Firefox to my server written in Php.
Problem is when I try to connect client(using js/php or even websocket.org/echo.html) I get issue and the issue is client is firstly connected and then disconnected automatically. And at server, I am using fwrite to to send message to client. And i have also noticed that for a client from firefox fwrite returns 0 as a response, but at client side I can see server has posted handshake data.
Now I see problem is here, when server is writing but response is 0 and still browser(firefox) is getting response but it gets disconnected with the server at the same time. I even tried to compare(calculate) data in bytes, which server is posting to client. I see its exactly same.
Note:-
Just to mention here, same php server script is working fine for Opera/Chrome/Safari browsers.
Below are the things which are involved in this issue;
Firefox [71.0]
Php [7.0]
Apache
Let's Encrypt SSL
# STREAM SOCKET CREATION OVER SSL(WSS)
$context = stream_context_create();
stream_context_set_option($context, 'ssl', 'local_cert', 'ssl-test.pem'); #change to your own ssl.pem path
stream_context_set_option($context, 'ssl', 'allow_self_signed', true);
stream_context_set_option($context, 'ssl', 'verify_peer', false);
$sock = stream_socket_server("ssl://".HOST_NAME.":".PORT,$errno,$errstr,STREAM_SERVER_BIND|STREAM_SERVER_LISTEN, $context) or die('Unable to start server, seems server is already running!');
// stream_set_blocking($sock,TRUE);
# PUTTING ALL(CURRENT/UPCOMING) SOCKET CONNECTIONS IN ARRAY
$clients = array();
# INFINTE LOOP THOURGH ALL SOCKECT CURRENT/UPCOMING CONNECTIONS, SEND/RECEIVE DATA
while (true) {
# ALL ACTIVE SOCKET CONNECTIONS
$read = $clients;
foreach($received_header as $key => $header){
if($header['time']+1 < microtime(true)){
$headers = [];
// echo $header['data'];
$lines = preg_split("/\r\n/", $header['data']);
foreach($lines as $line) {
$line = chop($line);
if(preg_match('/\A(\S+): (.*)\z/', $line, $matches)){
$headers[$matches[1]] = $matches[2];
}
}
$secKey = $headers['Sec-WebSocket-Key'];
$secAccept = base64_encode(pack('H*', sha1($secKey . '258EAFA5-E914-47DA-95CA-C5AB0DC85B11')));
// create handshake header
$upgrade = "HTTP/1.1 101 Web Socket Protocol Handshake" .PHP_EOL.
"Upgrade: websocket" .PHP_EOL.
"Connection: Upgrade" .PHP_EOL.
"Accept: text/html" .PHP_EOL.
"Sec-WebSocket-Accept: $secAccept".PHP_EOL.PHP_EOL;
// send handshake packet
if(!fwrite($clients[$key], $upgrade)){
file_put_contents('handshakes-est.txt', ($upgrade));
}
$handshakes[$key] = true;
$unset_key = $key;
}
}
/* i have rest of the script here which do further communication after connection is setup */
}
Below are some screen-shots to better understand connection behavior issue on Firefox.
Headers during handshake:
Connection behavior over websocket:
I have trouble with establishing a SSL connection.
These warnings are displayed:
Warning: stream_socket_client() [function.stream-socket-client]: Unable to set local cert chain file `D:\path\cert.pem'; Check that your cafile/capath settings include details of your certificate and its issuer in D:\path\testSll.php on line 23
Warning: stream_socket_client() [function.stream-socket-client]: failed to create an SSL handle in D:\path\testSll.php on line 23
Warning: stream_socket_client() [function.stream-socket-client]: Failed to enable crypto in D:\path\testSll.php on line 23
Warning: stream_socket_client() [function.stream-socket-client]: unable to connect to ssl://host.tld:700 (Unknown error) in D:\path\testSll.php on line 23
So I read all the question about the "Unable to set local cert chain file", but all the answer I found didn't work for me.
Here is the code I use :
$host = 'host.tld';
$port = 700;
$cert = dirname(__FILE__).'\\cert.pem'; //
$passe_phrase = 'pass';
$opts = array(
'ssl'=>array(
'local_cert' => $cert,
'passphrase' => $passe_phrase,
'verify_peer' => false
)
);
$context = stream_context_create($opts);
$fp = stream_socket_client('ssl://'.$host.':'.$port, $errno, $errstr, 30, STREAM_CLIENT_CONNECT, $context);
if ($fp) {
echo "OK";
} else {
echo "ERROR: $errno - $errstr<br />\n";
}
The certificate is the good one.
The script can access to the file cert.pem.
I cant find what I am missing here.
PHP version : 5.2.6
You should look at include details of your certificate and its issuer. In my case I accidentally deleted header of a file. When I return it back, the script is connecting successfully.
Before:
-----BEGIN CERTIFICATE-----
MIIFCBG+gAwIBAg...
After
Bag Attributes
friendlyName: ...
localKeyID: ...
subject=...
issuer=/C=US/... CN=Apple Worldwide Developer Relations Certification Authority
-----BEGIN CERTIFICATE-----
MIIFCBG+gAwIBAg...
for the past 3 days i have been trying to make things work with my php code and apns.
So far i'm able to deliver notifications (i only have one device so i don't know if this is working for multiple devices).
When i try to test sending one notification for 2 devices where the first is an device token invalid (invented by me) and the second is my device token, im unable to deliver the notification to my device... From what i read, when a notification is unreadable or erroneous, apns shuts the connection down and sends an error of 6 bytes long, where the 2nd byte (status_code) is the error type. If no error happens, im still unable to understand if apns send the status_code 0 (No errors encountered) or if it does not send anything at all.
So far i wasn't able to ever find this status_code, despite of following several codes found on internet.
CODE
$ctx = stream_context_create();
stream_context_set_option($ctx, 'ssl', 'local_cert', '.\certif\ck.pem');
stream_context_set_option($ctx, 'ssl', 'passphrase', $passphrase);
// Create the payload body
//file_put_contents('logs/debug_not.log', 'body'. "\n", FILE_APPEND);
$body['aps'] = array(
'alert' => $message,
'sound' => 'default'
);
// Encode the payload as JSON
//file_put_contents('logs/debug_not.log', 'json body'. "\n", FILE_APPEND);
$payload = json_encode($body);
$open = true;
foreach ($devices as $device) {
//file_put_contents('logs/debug_not.log', 'inicio ciclo'. "\n", FILE_APPEND);
if($open == true){
//file_put_contents('logs/debug_not.log', 'abrir ligacao'. "\n", FILE_APPEND);
// Open a connection to the APNS server
$fp = stream_socket_client(
'ssl://gateway.sandbox.push.apple.com:2195', $err, $errstr, 60, STREAM_CLIENT_CONNECT | STREAM_CLIENT_PERSISTENT, $ctx);
if (!$fp){
//file_put_contents('logs/debug_not.log', 'erro abrir ligacao'. "\n", FILE_APPEND);
throw new \Exception;
}
}
// Build the binary notification
//file_put_contents('logs/debug_not.log', 'criar payload'. "\n", FILE_APPEND);
$msg = chr(0) . pack('n', 32) . pack('H*', str_replace(' ', '', $device['token'])) . pack('n', strlen($payload)) . $payload;
// Send it to the server
//file_put_contents('logs/debug_not.log', 'enviar payload'. "\n", FILE_APPEND);
$result = fwrite($fp, $msg, strlen($msg));
stream_set_blocking ($fp, 0);
$errorResponse = #fread($apns, 6);
//if i var_dump($errorResponse) it gives me null/false all the time, no matter what
//this is my workaround for invalid device tokens-- its not working or at least is not send the valid tokens
if(!$result || !$fp)
{
//file_put_contents('logs/debug_not.log', 'erro na not '. chr($data["status_code"]). "\n", FILE_APPEND);
fclose($fp);
$open = true;
} else{
//file_put_contents('logs/debug_not.log', 'suc na not'. "\n", FILE_APPEND);
$open = false;
}
//file_put_contents('logs/debug_not.log', 'fim ciclo'. "\n", FILE_APPEND);
}
// Close the connection to the server
if($fp){
//file_put_contents('logs/debug_not.log', 'fechar connection'. "\n", FILE_APPEND);
fclose($fp);
}
fread on my connection gives null/false all the time, even when i receive my notification or i'm trying with a wrong token.
My logic for connect after error, seems to be not working.
can please someone help me??? i dont intend to use 3rd classes or code, would like to have a simple basic one working for an array of device tokens, even if there are some that apns gives error.
Thanks everyone.
You have made your connection as $fp = stream_socket_client ......
and you are trying to $errorResponse = #fread($apns, 6); which will never give you any output as $apns is not defined.
Rather you should do this :
$errorResponse = #fread($fp, 6);
and your code will work fine
If you receive an error (most likely in your case because your invented device token does not exist on Apple's servers for your app), then an error message is returned just before the connection is closed. If the message is successful, there is no response, but the connection remains open.
Here's how you could handle this:
Send your message.
Read the error with a timeout (or in non-blocking mode). If you get an error response, the message failed. (You'll need a new connection.)
If the read times out (or returns nothing in non-blocking mode), the message may be successful. (Do nothing.)
Send the next message. If the send is successful (still connected), the last message was definitely successful.
Keep track of your last successful message. You could always follow up a series of messages by sending your own device a message just to prevent any false positives.
You might want to check out the source code for EasyAPNS, which is an open source PHP implementation of this. Also, you can read the details in Apple's documentation: Provider Communication with Apple Push Notification Service
Here's the code I'm using
<?php
$deviceToken = 'my device key'; // not putting in for security
$payload['aps'] = array('alert' => 'This is the alert text', 'badge' => 1, 'sound' => 'default');
$payload = json_encode($payload);
$apnsHost = 'gateway.sandbox.push.apple.com';
$apnsPort = 2195;
$apnsCert = 'apns-dev.pem';
$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);
$apnsMessage = chr(0) . chr(0) . chr(32) . pack('H*', str_replace(' ', '', $deviceToken)) . chr(0) . chr(strlen($payload)) . $payload;
fwrite($apns, $apnsMessage);
socket_close($apns);
fclose($apns);
?>
and I get these errors
Warning: stream_socket_client() [function.stream-socket-client]: Unable to set private key file `apns-dev.pem' in /home/bryan/sendpush.php on line 14
Warning: stream_socket_client() [function.stream-socket-client]: failed to create an SSL handle in /home/bryan/sendpush.php on line 14
Warning: stream_socket_client() [function.stream-socket-client]: Failed to enable crypto in /home/bryan/sendpush.php on line 14
Warning: stream_socket_client() [function.stream-socket-client]: unable to connect to ssl://gateway.sandbox.push.apple.com:2195 (Unknown error) in /home/bryan/sendpush.php on line 14
Warning: fwrite(): supplied argument is not a valid stream resource in /home/bryan/sendpush.php on line 17
Warning: socket_close() expects parameter 1 to be resource, boolean given in /home/bryan/sendpush.php on line 19
Warning: fclose(): supplied argument is not a valid stream resource in /home/bryan/sendpush.php on line 20
I actually now got it down to these errors
Warning: stream_socket_client() [function.stream-socket-client]: SSL operation failed with code 1. OpenSSL Error messages: error:14094410:SSL routines:SSL3_READ_BYTES:sslv3 alert handshake failure in /home/bryan/PushService.php on line 27
Warning: stream_socket_client() [function.stream-socket-client]: Failed to enable crypto in /home/bryan/PushService.php on line 27
Warning: stream_socket_client() [function.stream-socket-client]: unable to connect to ssl://gateway.sandbox.push.apple.com:2195 (Unknown error) in /home/bryan/PushService.php on line
Use the absolute path for the private key instead of relative path.
Make sure the php user (or webserver user, depending.. www-data, apache, nginx, www...) is allowed to read it (chown, chmod).
I was getting this error also. found out that I had set up the permission wrong on the folder that had the certificate file. This worked for me:
chmod 755 your_folder_that_has_certificate_files