| 123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234 |
- <?php declare(strict_types=1);
- /*
- * This file is part of the Monolog package.
- *
- * (c) Jordi Boggiano <j.boggiano@seld.be>
- *
- * For the full copyright and license information, please view the LICENSE
- * file that was distributed with this source code.
- */
- namespace Monolog\Util;
- use Symfony\Component\Process\Process;
- class LocalSocket
- {
- const TCP = 'tcp';
- const UDP = 'udp';
- private static $sockets = [];
- private static $shutdownHandler = false;
- public static function initSocket(int $port = 51984, string $proto = LocalSocket::TCP)
- {
- if (!isset(self::$sockets[$proto][$port])) {
- $file = self::initFile($port, $proto);
- $process = new Process(escapeshellarg(PHP_BINARY).' '.escapeshellarg($file));
- $process->start(function ($type, $out) use ($proto, $port) {
- if ($type === 'err') {
- if (substr($out, 0, 4) === 'INIT') {
- if ($proto === LocalSocket::UDP) {
- self::$sockets[$proto][$port]['comms'] = null;
- } else {
- $sock = socket_create(AF_INET, SOCK_STREAM, getprotobyname($proto));
- socket_connect($sock, '127.0.0.1', $port);
- socket_write($sock, "MONITOR\n");
- self::$sockets[$proto][$port]['comms'] = $sock;
- }
- }
- }
- });
- self::$sockets[$proto][$port] = [
- 'file' => $file,
- 'process' => $process,
- 'busy' => false,
- ];
- // make sure the socket is listening
- while (true) {
- if ($process->getErrorOutput() === 'INIT') {
- break;
- }
- usleep(100);
- }
- if (!self::$shutdownHandler) {
- register_shutdown_function(function () {
- LocalSocket::shutdownSockets();
- });
- self::$shutdownHandler = true;
- }
- }
- $sock = self::$sockets[$proto][$port];
- if (!$sock['process']->isRunning()) {
- throw new \RuntimeException(
- 'LocalSocket '.$proto.'://127.0.0.1:'.$port.' appears to have died unexpectedly: ' . "\n\n" .
- $sock['process']->getOutput()
- );
- }
- self::clearSocket($port, $proto);
- return new class($sock['process'], $sock['comms']) {
- public function __construct(Process $proc, $comms)
- {
- $this->process = $proc;
- $this->comms = $comms;
- }
- public function getOutput()
- {
- // read out until getting a !DONE! ack and then tell the socket to terminate the connection
- if ($this->comms) {
- $out = '';
- socket_write($this->comms, "DONE?\n");
- while ($data = socket_read($this->comms, 2048)) {
- $out .= $data;
- if (substr($out, -6) === '!DONE!') {
- $out = substr($out, 0, -6);
- break;
- }
- }
- $out = preg_replace('{.*!BEGIN!}', '', $out);
- socket_write($this->comms, "TERMINATE\n");
- return $out;
- }
- // wait 3 seconds max for output for UDP
- $retries = 3000;
- while (!$this->process->getOutput() && $retries-- && $this->process->getStatus()) {
- usleep(100);
- }
- return $this->process->getOutput();
- }
- };
- }
- private static function clearSocket(int $port = 51984, string $proto = LocalSocket::TCP)
- {
- if (isset(self::$sockets[$proto][$port])) {
- self::$sockets[$proto][$port]['process']->clearOutput();
- }
- }
- public static function shutdownSocket(int $port = 51984, string $proto = LocalSocket::TCP)
- {
- if (!isset(self::$sockets[$proto][$port])) {
- return;
- }
- if (is_resource(self::$sockets[$proto][$port]['comms'])) {
- socket_write(self::$sockets[$proto][$port]['comms'], "EXIT\n");
- socket_close(self::$sockets[$proto][$port]['comms']);
- }
- $sock = self::$sockets[$proto][$port];
- $sock['process']->stop();
- @unlink($sock['file']);
- unset(self::$sockets[$proto][$port]);
- }
- public static function shutdownSockets()
- {
- foreach (self::$sockets as $proto => $ports) {
- foreach ($ports as $port => $sock) {
- self::shutdownSocket($port, $proto);
- }
- }
- }
- private static function initFile(int $port, string $proto): string
- {
- $tmpFile = sys_get_temp_dir().'/monolog-test-'.$proto.'-socket-'.$port.'.php';
- if ($proto === self::UDP) {
- file_put_contents($tmpFile, <<<SCRIPT
- <?php
- \$sock = socket_create(AF_INET, SOCK_DGRAM, getprotobyname('udp'));
- if (!socket_bind(\$sock, '127.0.0.1', $port)) {
- fwrite(STDERR, 'COULD NOT BIND $port');
- }
- fwrite(STDERR, 'INIT');
- while (true) {
- socket_recvfrom(\$sock, \$read, 100*1024, 0, \$ip, \$port);
- fwrite(STDOUT, \$read);
- }
- SCRIPT
- );
- } else {
- file_put_contents($tmpFile, <<<SCRIPT
- <?php
- \$sock = socket_create(AF_INET, SOCK_STREAM, getprotobyname('tcp'));
- if (!socket_bind(\$sock, '127.0.0.1', $port)) {
- fwrite(STDERR, 'COULD NOT BIND $port');
- }
- if (!socket_listen(\$sock)) {
- fwrite(STDERR, 'COULD NOT LISTEN $port');
- }
- fwrite(STDERR, 'INIT');
- \$monitor = socket_accept(\$sock);
- \$read = socket_read(\$monitor, 1024, PHP_NORMAL_READ);
- if (substr(\$read, 0, 7) !== 'MONITOR') {
- fwrite(STDERR, "Unexpected input: \$read");
- } else {
- fwrite(STDERR, "MONITORED");
- }
- while (true) {
- \$res = socket_accept(\$sock);
- socket_set_option(\$res, SOL_SOCKET, SO_RCVTIMEO, array("sec" => 0, "usec" => 0));
- socket_write(\$monitor, '!BEGIN!');
- while (true) {
- \$read = [\$res, \$monitor, \$sock];
- \$write = [];
- \$except = [];
- \$timeout = 0;
- if (socket_select(\$read, \$write, \$except, \$timeout) < 1) {
- continue;
- }
- foreach (\$read as \$readsock) {
- if (\$readsock === \$res) {
- \$bytes = socket_read(\$res, 1024);
- //if (\$bytes === '' && in_array(\$sock, \$read)) {
- // // client closed
- // socket_write(\$monitor, 'CLIENTCLOSED');
- // break 2;
- //}
- socket_write(\$monitor, \$bytes);
- } else {
- \$bytes = socket_read(\$monitor, 1024, PHP_NORMAL_READ);
- if (substr(trim(\$bytes), 0, 9) === 'TERMINATE') {
- break 2;
- } elseif (substr(trim(\$bytes), 0, 5) === 'DONE?') {
- socket_write(\$monitor, '!DONE!');
- } elseif (substr(trim(\$bytes), 0, 5) === 'EXIT') {
- socket_close(\$res);
- socket_close(\$monitor);
- die;
- }
- }
- }
- }
- socket_close(\$res);
- }
- SCRIPT
- );
- }
- return $tmpFile;
- }
- }
|