From 730e84e062ff914899f7d85dcbc7be197479c72d Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Jaime=20Pe=CC=81rez?= <jaime.perez@uninett.no> Date: Fri, 22 Jul 2016 13:18:53 +0200 Subject: [PATCH] test: Add a class to handle PHP's built-in server. With this class we can run PHP's built-in server specifying the document root (defaulting to the www directory) and a "router" file, which the server will execute for every request received. This is useful to allow testing of the web interfaces as part of our unit testing setup. --- tests/BuiltInServer.php | 201 ++++++++++++++++++++++++++++++++++++++++ 1 file changed, 201 insertions(+) create mode 100644 tests/BuiltInServer.php diff --git a/tests/BuiltInServer.php b/tests/BuiltInServer.php new file mode 100644 index 000000000..4f614f511 --- /dev/null +++ b/tests/BuiltInServer.php @@ -0,0 +1,201 @@ +<?php +/** + * An extremely simple class to start and stop PHP's built-in server, with the possibility to specify the document + * root and the "router" file to run for every request. + * + * @author Jaime Pérez Crespo <jaime.perez@uninett.no> + * @package SimpleSAMLphp + */ + +namespace SimpleSAML\Test; + + +class BuiltInServer +{ + + /** + * The PID of the running server. + * + * @var int + */ + protected $pid = 0; + + /** + * The address (host:port) where the server is listening for connections after being started. + * + * @var string + */ + protected $address; + + /** + * The name of a "router" file to run for every request performed to this server. + * + * @var string + */ + protected $router = ''; + + /** + * The document root of the server. + * + * @var string + */ + protected $docroot; + + + /** + * BuiltInServer constructor. + * + * @param string|null $router The name of a "router" file to run first for every request performed to this server. + * @param string|null $docroot The document root to use when starting the server. + * + * @see http://php.net/manual/en/features.commandline.webserver.php + */ + public function __construct($router = null, $docroot = null) + { + if (!is_null($router)) { + $this->setRouter($router); + } + + if (!is_null($docroot)) { + $this->docroot = $docroot; + } else { + $this->docroot = dirname(dirname(__FILE__)).'/www/'; + } + } + + + /** + * Start the built-in server in a random port. + * + * This method will wait up to 5 seconds for the server to start. When it returns an address, it is guaranteed that + * the server has started and is listening for connections. If it returns false on the other hand, there will be no + * guarantee that the server started properly. + * + * @return string The address where the server is listening for connections, or false if the server failed to start + * for some reason. + * + * @todo This method should be resilient to clashes in the randomly-picked port number. + */ + public function start() + { + $port = mt_rand(1025, 65535); + $this->address = 'localhost:'.$port; + + $command = sprintf( + 'php -S %s -t %s %s >> /dev/null 2>&1 & echo $!', + $this->address, + $this->docroot, + $this->router + ); + + // execute the command and store the process ID + $output = array(); + exec($command, $output); + $this->pid = (int) $output[0]; + + // wait until it's listening for connections to avoid race conditions + $start = microtime(true); + while (($sock = @fsockopen('localhost', $port, $errno, $errstr, 10)) === false) { + // set a 5 secs timeout waiting for the server to start + if (microtime(true) > $start + 5) { + $this->pid = false; // signal failure + $this->address = false; + break; + } + } + if ($sock !== false) { + fclose($sock); + } + + return $this->address; + } + + + /** + * Stop the built-in server. + */ + public function stop() + { + if ($this->pid === 0) { + return; + } + exec('kill '.$this->pid); + $this->pid = 0; + } + + + /** + * Get the PID of the running server. + * + * @return int The PID of the server, or 0 if the server was not started. + */ + public function getPid() + { + return $this->pid; + } + + + /** + * Get the name of the "router" file. + * + * @return string The name of the "router" file. + */ + public function getRouter() + { + return $this->router; + } + + + /** + * Set the "router" file. + * + * @param string $router The name of a "router" file to use when starting the server. + */ + public function setRouter($router) + { + $file = dirname(dirname(__FILE__)).'/tests/routers/'.$router.'.php'; + if (!file_exists($file)) { + throw new \InvalidArgumentException('Unknown router "'.$router.'".'); + } + $this->router = $file; + } + + + /** + * This function performs an HTTP GET request to the built-in server. + * + * @param string $query The query to perform. + * @param array $parameters An array (can be empty) with parameters for the requested URI. + * @param array $curlopts An array (can be empty) with options for cURL. + * + * @return array|string The response obtained from the built-in server. + */ + public function get($query, $parameters, $curlopts = array()) + { + $ch = curl_init(); + $url = 'http://'.$this->address.$query; + $url .= (!empty($parameters)) ? '?'.http_build_query($parameters) : ''; + curl_setopt_array($ch, array( + CURLOPT_URL => $url, + CURLOPT_RETURNTRANSFER => 1, + CURLOPT_HEADER => 1, + )); + curl_setopt_array($ch, $curlopts); + $resp = curl_exec($ch); + $code = curl_getinfo($ch, CURLINFO_HTTP_CODE); + list($header, $body) = explode("\r\n\r\n", $resp, 2); + $raw_headers = explode("\r\n", $header); + array_shift($raw_headers); + $headers = array(); + foreach ($raw_headers as $header) { + list($name, $value) = explode(':', $header, 2); + $headers[trim($name)] = trim($value); + } + curl_close($ch); + return array( + 'code' => $code, + 'headers' => $headers, + 'body' => $body, + ); + } +} -- GitLab