xref: /PHP-8.1/sapi/cli/tests/php_cli_server.inc (revision 6ab4e330)
1<?php
2
3// TODO: Move address/port info in here?
4class CliServerInfo {
5    public function __construct(
6        public string $docRoot,
7    ) {}
8}
9
10function php_cli_server_start(
11    ?string $code = 'echo "Hello world";',
12    ?string $router = 'index.php',
13    array $cmd_args = []
14): CliServerInfo {
15    $php_executable = getenv('TEST_PHP_EXECUTABLE');
16    $error = null;
17
18    // Create dedicated doc root to avoid index.php clashes between tests.
19    $doc_root = __DIR__ . '/' . basename($_SERVER['PHP_SELF'], '.php');
20    @mkdir($doc_root);
21
22    if ($code) {
23        file_put_contents($doc_root . '/' . ($router ?: 'index.php'), '<?php ' . $code . ' ?>');
24    }
25
26    $cmd = [$php_executable, '-t', $doc_root, '-n', ...$cmd_args, '-S', 'localhost:0'];
27    if (!is_null($router)) {
28        $cmd[] = $router;
29    }
30
31    $descriptorspec = array(
32        0 => STDIN,
33        1 => STDOUT,
34        2 => ['pipe', 'w'],
35    );
36    $handle = proc_open($cmd, $descriptorspec, $pipes, $doc_root, null, array("suppress_errors" => true));
37
38    // First, wait for the dev server to declare itself ready.
39    $bound = null;
40    stream_set_blocking($pipes[2], false);
41    for ($i = 0; $i < 60; $i++) {
42        usleep(50000); // 50ms per try
43        $status = proc_get_status($handle);
44        if (empty($status['running'])) {
45            echo "Server is not running\n";
46            proc_terminate($handle);
47            exit(1);
48        }
49
50        while (($line = fgets($pipes[2])) !== false) {
51            if (preg_match('@PHP \S* Development Server \(https?://(.*?:\d+)\) started@', $line, $matches)) {
52                $bound = $matches[1];
53                // Now that we've identified the listen address, close STDERR.
54                // Otherwise the pipe may clog up with unread log messages.
55                fclose($pipes[2]);
56                break 2;
57            }
58        }
59    }
60    if ($bound === null) {
61        echo "Server did not output startup message";
62        proc_terminate($handle);
63        exit(1);
64    }
65
66    // Now wait for a connection to succeed.
67    // note: even when server prints 'Listening on localhost:8964...Press Ctrl-C to quit.'
68    //       it might not be listening yet...need to wait until fsockopen() call returns
69    $error = "Unable to connect to server\n";
70    for ($i=0; $i < 60; $i++) {
71        usleep(50000); // 50ms per try
72        $status = proc_get_status($handle);
73        $fp = @fsockopen("tcp://$bound");
74        // Failure, the server is no longer running
75        if (!($status && $status['running'])) {
76            $error = "Server is not running\n";
77            break;
78        }
79        // Success, Connected to servers
80        if ($fp) {
81            $error = '';
82            break;
83        }
84    }
85
86    if ($error) {
87        echo $error;
88        proc_terminate($handle);
89        exit(1);
90    }
91
92    register_shutdown_function(
93        function($handle) use($router, $doc_root) {
94            proc_terminate($handle);
95            @unlink(__DIR__ . "/{$router}");
96            @rmdir($doc_root);
97        },
98        $handle
99    );
100
101    // Define the same "constants" we previously did.
102    $port = (int) substr($bound, strrpos($bound, ':') + 1);
103    define("PHP_CLI_SERVER_HOSTNAME", "localhost");
104    define("PHP_CLI_SERVER_PORT", $port);
105    define("PHP_CLI_SERVER_ADDRESS", PHP_CLI_SERVER_HOSTNAME.":".PHP_CLI_SERVER_PORT);
106
107    return new CliServerInfo($doc_root);
108}
109
110function php_cli_server_connect() {
111    $timeout = 1.0;
112    $fp = fsockopen(PHP_CLI_SERVER_HOSTNAME, PHP_CLI_SERVER_PORT, $errno, $errstr, $timeout);
113    if (!$fp) {
114        die("connect failed");
115    }
116    return $fp;
117}
118
119?>
120