Compare commits

..

No commits in common. "master" and "wiki" have entirely different histories.
master ... wiki

5 changed files with 3 additions and 410 deletions

3
ProjectHome.md Normal file
View File

@ -0,0 +1,3 @@
A set of PHP scripts (to be run as services) to send Push Notifications using Apple Push Notification Service.
For requirements and instructions, please see http://php-apns.googlecode.com/svn/trunk/README .

46
README
View File

@ -1,46 +0,0 @@
PHP APNS Provider as Service
------------------------------
A small set of PHP scripts (to be run as a background service) to send messages to APNS (Apple Push Notifications Service).
Features:
* Messages are sent from a queue. You can have a single, central queue and many instances of this script that send messages. Thus, this solution is extremely scalable.
* The connection with APNS servers is closed after X minutes of inactivity.
* If the connection with APNS servers is dropped (for whatever reason), the script creates a new connection automatically.
* 100% pure PHP code!
Included files:
* PushMonitor.php (created by Alessandro Segala - released under LGPL)
The main script: this is the script you will run. It checks for new items in the queue at regular intervals and sends them to Apple through the PushService script.
* PushService.php (created by Luke Rhodes and adapted by Alessandro Segala - released into the public domain)
Manages the connection with APNS servers.
This script should never be executed directly: it is started by PushMonitor.
* PushErrors.log
A log file where the scripts write errors. Make sure it is writable.
Requirements:
* PHP with CLI support and Memcache extension
o on Linux servers, if they aren't already installed, you should find these packages in your distro's repisitories (on Debian: "sudo aptitude install php-cli php-memcache").
o on Mac OSX Leopard, the built-in version of PHP doesn't have the Memcache extension: you might want to grab the version compiled by entropy.ch.
* MemcacheQ
o This is a little daemon that manages the message queue.
o It is based on MemcacheDB, so it extremely fast and persistent (based on Berkeley DB, a non-relational database).
o Uses the same protocol as Memcached, so you can connect to the MemcacheQ dameon using one of the many libraries, or even just with telnet.
o To install it, you must compile it (requires about 2 minutes): http://memcachedb.org/memcacheq/
Installation:
1. Copy all files to a directory (e.g. /usr/local/push).
2. Place your push certificate in the same folder. Name it "apns.pem" (if you wish to change this name, please change it also in PushService.php). Don't add any password to the certificate.
3. If you need to use the sandbox, change the hostname in PushService.php.
4. Set the options in PushMonitor.php. In particular, you might wish to change the queue name.
Usage:
* Start the PushMonitor.php script. The standard way is:
php -f /usr/local/push/PushMonitor.php
You might want to create an init.d/rc.d/launchd script to execute the file as a background process.
* To send messages, just add them to the queue!

View File

View File

@ -1,261 +0,0 @@
<?php
/**
PHP APNS Service with Queue
(C) 2009 Alessandro Segala (alessandro.segala@letsdev.it)
This library is free software; you can redistribute it and/or
modify it under the terms of the GNU Lesser General Public
License as published by the Free Software Foundation; either
version 2.1 of the License, or (at your option) any later version.
This library is distributed in the hope that it will be useful,
but WITHOUT ANY WARRANTY; without even the implied warranty of
MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU
Lesser General Public License for more details.
You should have received a copy of the GNU Lesser General Public
License along with this library; if not, write to the Free Software
Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA
*/
// Some settings
$cache_server = 'localhost';
$cache_port = 22201;
$queue_id = 'PushQueue';
$check_interval = 10;
$php_executable = 'php';
$inactivity_timeout = 600; // Quits the connection with APNS after X seconds of inactivity
$debug = false; // Set to true to display verbose information
// We want this process to run forever... Well, at least until the user stops it!
set_time_limit(0);
// Settings for child processes
$descriptorspec = array(
0 => array('pipe', 'r'), // stdin
1 => array('pipe', 'w'), // stdout
2 => array('pipe', 'w') // stderr
);
$process = false;
// Changes the working directory so it matches this script's directory
chdir(dirname(__FILE__));
// Opens a connection to the MemcacheQ daemon, used for queueing requests
$cache = new Memcache();
if(!$cache->connect($cache_server, $cache_port))
{
echo "Error while connecting to cache server\n";
exit;
}
// Application's loop
while(true)
{
$value = $cache->get($queue_id);
if(!$value)
{
if($debug)
{
echo "No data: sleeping for $check_interval seconds\n";
}
// Kills the process after X seconds of inactivity
if($process)
{
if($process->time < (time() - $inactivity_timeout))
{
if($debug)
{
echo "Quitting connection with Apple for inactivity\n";
}
$process->kill();
$process = false;
}
}
sleep($check_interval);
continue;
}
if($debug)
{
var_dump($value);
}
set_error_handler(create_function('', 'error_log(date(\'Y-m-d H:i\')." - Error while unserializing\n\n", 3, \'PushErrors.log\');'));
$unserialized = unserialize($value);
restore_error_handler();
if(!$unserialized)
{
continue;
}
list($deviceToken, $value) = $unserialized;
// Try to send the message 3 times
for($i = 0; $i < 3; $i++)
{
if($debug)
{
echo "Sending message\n";
}
if(!$process)
{
$process = new Process(1153 + $i);
if(!$process->init())
{
echo "Error while starting child process\n";
$process->kill();
$process = false;
if($debug)
{
echo "Sleeping 10 seconds before trying again\n";
}
sleep(10);
continue;
}
}
$result = $process->sendMessage($deviceToken, $value);
if($result)
{
if($debug)
{
echo "Message sent\n\n";
}
break;
}
else
{
if($debug)
{
echo "Attempt ",$i+1," failed\n";
}
if($process)
{
$process->kill();
$process = false;
}
}
}
// If all 3 attempts failed...
if($i == 3)
{
if($process)
{
$process->kill();
$process = false;
}
error_log(date('Y-m-d H:i')." - Cannot send message:\n".$message."\nTo device: ".$deviceToken."\n\n", 3, 'PushErrors.log');
}
}
class Process
{
private $pointer;
private $pipes;
private $port;
public $time;
public function __construct($port = 1153)
{
$this->port = $port;
}
public function init()
{
global $descriptorspec, $php_executable;
$this->pointer = proc_open($php_executable.' -f PushService.php -- -p'.$this->port, $descriptorspec, $this->pipes);
if(!$this->pointer)
{
return false;
}
$this->time = time();
// Wait until the process is fully ready
$read = fgets($this->pipes[1]);
if(trim($read) != 'LISTENING '.$this->port)
{
global $debug;
if($debug)
{
echo "Error while starting child process.\n";
}
return false;
}
return true;
}
public function sendMessage($deviceToken, $message)
{
if(!($conn = fsockopen('localhost', $this->port, $errno, $errstr, 5)))
{
return false;
}
$read = fgets($conn);
if(trim($read) != 'OK')
{
fclose($conn);
return false;
}
fwrite($conn, $deviceToken."\n");
fwrite($conn, $message."\n");
$read = fgets($conn);
if(trim($read) != 'SENT')
{
fclose($conn);
return false;
}
fclose($conn);
$this->time = time();
return true;
}
public function kill()
{
global $debug;
$status = proc_get_status($this->pointer);
if($status['running'])
{
fclose($this->pipes[0]);
fclose($this->pipes[1]);
fclose($this->pipes[2]);
$ppid = $status['pid'];
$pids = preg_split('/\s+/', `ps -o pid --no-heading --ppid $ppid`);
foreach($pids as $pid)
{
if(is_numeric($pid))
{
if($debug)
{
echo "Killing $pid\n";
}
posix_kill($pid, 9); //9 is the SIGKILL signal
}
}
return (proc_close($this->pointer) == 0);
}
}
}
?>

View File

@ -1,103 +0,0 @@
<?php
/**
PHP APNS Service
Created by Luke Rhodes
Released into the public domain
*/
$port = 0;
foreach($argv as $a)
{
if($a[0] == '-' && $a[1] == 'p' && !empty($a[2]))
{
$port = intval(substr($a, 2));
}
}
$pushNotifications = new pushNotifications($port);
class pushNotifications
{
private $apnsHost = 'gateway.push.apple.com';
private $apnsPort = '2195';
private $sslPem = 'apns.pem';
private $passPhrase = '';
private $serviceHost = '127.0.0.1';
private $servicePort = '1153';
private $apnsConnection;
private $serviceConnection;
function __construct($port = 0)
{
if($port)
{
$this->servicePort = $port;
}
$this->connectToAPNS();
$this->listenForClients();
$this->closeConnections();
}
function connectToAPNS()
{
$streamContext = stream_context_create();
stream_context_set_option($streamContext, 'ssl', 'local_cert', $this->sslPem);
stream_context_set_option($streamContext, 'ssl', 'passphrase', $this->passPhrase);
$this->apnsConnection = stream_socket_client('ssl://'.$this->apnsHost.':'.$this->apnsPort, $error, $errorString, 60, STREAM_CLIENT_CONNECT, $streamContext);
if($this->apnsConnection == false)
{
print "Failed to connect {$error} {$errorString}\n";
$this->closeConnections();
}
}
function listenForClients()
{
$this->serviceConnection = socket_create(AF_INET, SOCK_STREAM, SOL_TCP);
socket_bind($this->serviceConnection, $this->serviceHost, $this->servicePort);
socket_listen($this->serviceConnection, 10);
echo 'LISTENING ',$this->servicePort,"\n";
while($clientSocket = socket_accept($this->serviceConnection))
{
socket_write($clientSocket, "OK\n");
$deviceToken = trim(socket_read($clientSocket, 1024, PHP_NORMAL_READ));
$message = trim(socket_read($clientSocket, 1024, PHP_NORMAL_READ));
if(!empty($deviceToken) && !empty($message))
{
$this->sendNotification($deviceToken, $message);
socket_write($clientSocket, "SENT\n");
}
else
{
socket_write($clientSocket, "ERROR\n");
}
socket_close($clientSocket);
}
}
function sendNotification($deviceToken, $message)
{
$apnsMessage = chr(0) . chr(0) . chr(32) . pack('H*', str_replace(' ', '', $deviceToken)) . chr(0) . chr(strlen($message)) . $message;
fwrite($this->apnsConnection, $apnsMessage);
}
function closeConnections()
{
socket_close($this->serviceConnection);
fclose($this->apnsConnection);
}
}
?>