commit 9c987c19f9f28d28dbdb3f6b0d4132fb0f4013c8 Author: alessandro.segala Date: Fri Jun 19 17:12:56 2009 +0000 Importing all scripts diff --git a/push/PushErrors.log b/push/PushErrors.log new file mode 100644 index 0000000..e69de29 diff --git a/push/PushMonitor.php b/push/PushMonitor.php new file mode 100644 index 0000000..6c604fe --- /dev/null +++ b/push/PushMonitor.php @@ -0,0 +1,248 @@ + 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; + } + elseif($debug) + { + echo "Attempt ",$i+1," failed\n"; + } + } + + // If all 3 attempts failed... + if($i == 3) + { + $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() + { + $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 --pid $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); + } + } +} +?> diff --git a/push/PushService.php b/push/PushService.php new file mode 100644 index 0000000..ce867f2 --- /dev/null +++ b/push/PushService.php @@ -0,0 +1,103 @@ +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); + } +} + +?>