From 6f78af6d0943c48521d17b356059b0d70cabbc26 Mon Sep 17 00:00:00 2001 From: Daniil Gentili Date: Tue, 11 Feb 2020 14:45:11 +0100 Subject: [PATCH] Begin adding web forking --- lib/Context/Internal/Runner/ProcessRunner.php | 209 ++++++++++++++++++ .../Internal/Runner/RunnerAbstract.php | 123 +++++++++++ lib/Context/Internal/Runner/WebRunner.php | 140 ++++++++++++ lib/Context/Process.php | 69 +----- 4 files changed, 476 insertions(+), 65 deletions(-) create mode 100644 lib/Context/Internal/Runner/ProcessRunner.php create mode 100644 lib/Context/Internal/Runner/RunnerAbstract.php create mode 100644 lib/Context/Internal/Runner/WebRunner.php diff --git a/lib/Context/Internal/Runner/ProcessRunner.php b/lib/Context/Internal/Runner/ProcessRunner.php new file mode 100644 index 0000000..1052235 --- /dev/null +++ b/lib/Context/Internal/Runner/ProcessRunner.php @@ -0,0 +1,209 @@ + "0", + "display_errors" => "0", + "log_errors" => "1", + ]; + + $command = \implode(" ", [ + \escapeshellarg($binary), + self::formatOptions($options), + \escapeshellarg($runPath), + $hub->getUri(), + $script, + ]); + + $this->process = new BaseProcess($command, $cwd, $env); + } + private static function locateBinary(): string + { + $executable = \strncasecmp(\PHP_OS, "WIN", 3) === 0 ? "php.exe" : "php"; + + $paths = \array_filter(\explode(\PATH_SEPARATOR, \getenv("PATH"))); + $paths[] = \PHP_BINDIR; + $paths = \array_unique($paths); + + foreach ($paths as $path) { + $path .= \DIRECTORY_SEPARATOR.$executable; + if (\is_executable($path)) { + return self::$binaryPath = $path; + } + } + + throw new \Error("Could not locate PHP executable binary"); + } + + private static function formatOptions(array $options): string + { + $result = []; + + foreach ($options as $option => $value) { + $result[] = \sprintf("-d%s=%s", $option, $value); + } + + return \implode(" ", $result); + } + + + /** + * Set process key + * + * @param string $key Process key + * + * @return Promise + */ + public function setProcessKey(string $key): Promise + { + return $this->process->getStdin()->write($key); + } + + /** + * @return bool + */ + public function isRunning(): bool + { + return $this->process->isRunning(); + } + + /** + * Starts the process. + * + * @return Promise Resolved with the PID + */ + public function start(): Promise + { + return $this->process->start(); + } + + /** + * Immediately kills the process. + */ + public function kill(): void + { + $this->process->kill(); + } + + /** + * @return \Amp\Promise Resolves with the returned from the process. + */ + public function join(): Promise + { + return $this->process->join(); + } + + /** + * Send a signal to the process. + * + * @see \Amp\Process\Process::signal() + * + * @param int $signo + * + * @throws \Amp\Process\ProcessException + * @throws \Amp\Process\StatusError + */ + public function signal(int $signo): void + { + $this->process->signal($signo); + } + + /** + * Returns the PID of the process. + * + * @see \Amp\Process\Process::getPid() + * + * @return int + * + * @throws \Amp\Process\StatusError + */ + public function getPid(): int + { + return $this->process->getPid(); + } + + /** + * Returns the STDIN stream of the process. + * + * @see \Amp\Process\Process::getStdin() + * + * @return ProcessOutputStream + * + * @throws \Amp\Process\StatusError + */ + public function getStdin(): ProcessOutputStream + { + return $this->process->getStdin(); + } + + /** + * Returns the STDOUT stream of the process. + * + * @see \Amp\Process\Process::getStdout() + * + * @return ProcessInputStream + * + * @throws \Amp\Process\StatusError + */ + public function getStdout(): ProcessInputStream + { + return $this->process->getStdout(); + } + + /** + * Returns the STDOUT stream of the process. + * + * @see \Amp\Process\Process::getStderr() + * + * @return ProcessInputStream + * + * @throws \Amp\Process\StatusError + */ + public function getStderr(): ProcessInputStream + { + return $this->process->getStderr(); + } + +} diff --git a/lib/Context/Internal/Runner/RunnerAbstract.php b/lib/Context/Internal/Runner/RunnerAbstract.php new file mode 100644 index 0000000..80b1ef9 --- /dev/null +++ b/lib/Context/Internal/Runner/RunnerAbstract.php @@ -0,0 +1,123 @@ + Resolves with the PID + */ + abstract public function start(): Promise; + + /** + * Immediately kills the process. + */ + abstract public function kill(): void; + + /** + * @return \Amp\Promise Resolves with the returned from the process. + */ + abstract public function join(): Promise; + + /** + * Returns the PID of the process. + * + * @see \Amp\Process\Process::getPid() + * + * @return int + * + * @throws \Amp\Process\StatusError + */ + abstract public function getPid(): int; + + /** + * Send a signal to the process. + * + * @see \Amp\Process\Process::signal() + * + * @param int $signo + * + * @throws \Amp\Process\ProcessException + * @throws \Amp\Process\StatusError + */ + public function signal(int $signo): void + { + throw new ProcessException("Not supported!"); + } + + /** + * Returns the STDIN stream of the process. + * + * @see \Amp\Process\Process::getStdin() + * + * @return ProcessOutputStream + * + * @throws \Amp\Process\StatusError + */ + public function getStdin(): ProcessOutputStream + { + throw new ProcessException("Not supported!"); + } + + /** + * Returns the STDOUT stream of the process. + * + * @see \Amp\Process\Process::getStdout() + * + * @return ProcessInputStream + * + * @throws \Amp\Process\StatusError + */ + public function getStdout(): ProcessInputStream + { + throw new ProcessException("Not supported!"); + } + + /** + * Returns the STDOUT stream of the process. + * + * @see \Amp\Process\Process::getStderr() + * + * @return ProcessInputStream + * + * @throws \Amp\Process\StatusError + */ + public function getStderr(): ProcessInputStream + { + throw new ProcessException("Not supported!"); + } +} diff --git a/lib/Context/Internal/Runner/WebRunner.php b/lib/Context/Internal/Runner/WebRunner.php new file mode 100644 index 0000000..195f17a --- /dev/null +++ b/lib/Context/Internal/Runner/WebRunner.php @@ -0,0 +1,140 @@ +params = [ + 'options' => [ + "html_errors" => "0", + "display_errors" => "0", + "log_errors" => "1", + ], + 'argv' => [ + $hub->getUri(), + ...$script + ] + ]; + $this->pid = \random_int(0, PHP_INT_MAX); + } + + + /** + * Returns the PID of the process. + * + * @see \Amp\Process\Process::getPid() + * + * @return int + * + * @throws \Amp\Process\StatusError + */ + public function getPid(): int + { + return $this->pid; + } + + /** + * Set process key. + * + * @param string $key Process key + * + * @return Promise + */ + public function setProcessKey(string $key): Promise + { + $this->params['key'] = $key; + $params = \http_build_query($params); + + $address = ($_SERVER['HTTPS'] ?? false ? 'tls' : 'tcp').'://'.$_SERVER['SERVER_NAME']; + $port = $_SERVER['SERVER_PORT']; + $uri = $_SERVER['REQUEST_URI']; + $params = $_GET; + + $url = \explode('?', $uri, 2)[0] ?? ''; + $query = \http_build_query($params); + $uri = \implode('?', [$url, $query]); + + $this->payload = "GET $uri HTTP/1.1\r\nHost: ${_SERVER['SERVER_NAME']}\r\n\r\n"; + + $a = \fsockopen($address, $port); + \fwrite($a, $payload); + + $this->running =true; + } + + /** + * @return bool + */ + public function isRunning(): bool + { + return $this->running; + } + + /** + * Starts the process. + * + * @return Promise Resolved with the PID + */ + public function start(): Promise + { + return new Success(); + } + + /** + * Immediately kills the process. + */ + public function kill(): void + { + $this->process->kill(); + } + + /** + * @return \Amp\Promise Resolves with the returned from the process. + */ + public function join(): Promise + { + return new Success(); + } +} diff --git a/lib/Context/Process.php b/lib/Context/Process.php index 498ffe2..c0d98ac 100644 --- a/lib/Context/Process.php +++ b/lib/Context/Process.php @@ -3,10 +3,10 @@ namespace Amp\Parallel\Context; use Amp\Loop; +use Amp\Parallel\Context\Internal\Runner\ProcessRunner; use Amp\Parallel\Sync\ChannelException; use Amp\Parallel\Sync\ExitResult; use Amp\Parallel\Sync\SynchronizationError; -use Amp\Process\Process as BaseProcess; use Amp\Process\ProcessInputStream; use Amp\Process\ProcessOutputStream; use Amp\Promise; @@ -23,13 +23,10 @@ final class Process implements Context /** @var string|null PHAR path with a '.phar' extension. */ private static $pharCopy; - /** @var string|null Cached path to located PHP binary. */ - private static $binaryPath; - /** @var Internal\ProcessHub */ private $hub; - /** @var \Amp\Process\Process */ + /** @var Internal\Runner\RunnerAbstract */ private $process; /** @var \Amp\Parallel\Sync\ChannelledSocket */ @@ -73,22 +70,6 @@ final class Process implements Context Loop::setState(self::class, $this->hub); } - $options = [ - "html_errors" => "0", - "display_errors" => "0", - "log_errors" => "1", - ]; - - if ($binary === null) { - if (\PHP_SAPI === "cli") { - $binary = \PHP_BINARY; - } else { - $binary = self::$binaryPath ?? self::locateBinary(); - } - } elseif (!\is_executable($binary)) { - throw new \Error(\sprintf("The PHP binary path '%s' was not found or is not executable", $binary)); - } - // Write process runner to external file if inside a PHAR, // because PHP can't open files inside a PHAR directly except for the stub. if (\strpos(self::SCRIPT_PATH, "phar://") === 0) { @@ -127,51 +108,9 @@ final class Process implements Context $scriptPath = self::SCRIPT_PATH; } - if (\is_array($script)) { - $script = \implode(" ", \array_map("escapeshellarg", $script)); - } else { - $script = \escapeshellarg($script); - } - - $command = \implode(" ", [ - \escapeshellarg($binary), - $this->formatOptions($options), - \escapeshellarg($scriptPath), - $this->hub->getUri(), - $script, - ]); - - $this->process = new BaseProcess($command, $cwd, $env); + $this->process = new ProcessRunner($script, $scriptPath, $this->hub, $cwd, $env, $binary); } - private static function locateBinary(): string - { - $executable = \strncasecmp(\PHP_OS, "WIN", 3) === 0 ? "php.exe" : "php"; - - $paths = \array_filter(\explode(\PATH_SEPARATOR, \getenv("PATH"))); - $paths[] = \PHP_BINDIR; - $paths = \array_unique($paths); - - foreach ($paths as $path) { - $path .= \DIRECTORY_SEPARATOR . $executable; - if (\is_executable($path)) { - return self::$binaryPath = $path; - } - } - - throw new \Error("Could not locate PHP executable binary"); - } - - private function formatOptions(array $options): string - { - $result = []; - - foreach ($options as $option => $value) { - $result[] = \sprintf("-d%s=%s", $option, $value); - } - - return \implode(" ", $result); - } /** * Private method to prevent cloning. @@ -189,7 +128,7 @@ final class Process implements Context try { $pid = yield $this->process->start(); - yield $this->process->getStdin()->write($this->hub->generateKey($pid, self::KEY_LENGTH)); + yield $this->process->setProcessKey($this->hub->generateKey($pid, self::KEY_LENGTH)); $this->channel = yield $this->hub->accept($pid);