Completed
Push — master ( c3eed0...8baa58 )
by Sébastien
04:16 queued 01:32
created

StandaloneServer::getConfig()   A

Complexity

Conditions 1
Paths 1

Size

Total Lines 4
Code Lines 2

Duplication

Lines 0
Ratio 0 %

Code Coverage

Tests 2
CRAP Score 1

Importance

Changes 3
Bugs 0 Features 0
Metric Value
c 3
b 0
f 0
dl 0
loc 4
ccs 2
cts 2
cp 1
rs 10
cc 1
eloc 2
nc 1
nop 0
crap 1
1
<?php
2
3
namespace PjbServer\Tools;
4
5
use PjbServer\Tools\Network\PortTester;
6
use Psr\Log\LoggerInterface;
7
use Psr\Log\NullLogger;
8
9
10
class StandaloneServer
11
{
12
13
    /**
14
     * @var int
15
     */
16
    protected $port;
17
18
    /**
19
     *
20
     * @var StandaloneServer\Config
21
     */
22
    protected $config;
23
24
    /**
25
     * Tells whether the standalone server is started
26
     * @var boolean
27
     */
28
    protected $started = false;
29
30
31
    /**
32
     *
33
     * @var PortTester
34
     */
35
    protected $portTester;
36
37
38
    /**
39
     * @var
40
     */
41
    protected $logger;
42
43
    /**
44
     * Constructor
45
     *
46
     * <code>
47
     *
48
     * $config = array(
49
     *      'port' => 8089,
50
     *
51
     *      // optionally
52
     *      'server_jar' => 'path/to/JavaBridge.jar'
53
     *      'java_bin' => 'path/to/java'
54
     * );
55
     * $server = new StandaloneServer($config);
56
     *
57
     * </code>
58
     *
59
     * @throws Exception\InvalidArgumentException
60
     * @param StandaloneServer\Config $config
61
     * @param LoggerInterface $logger
62
     *
63
     */
64 17
    public function __construct(StandaloneServer\Config $config, LoggerInterface $logger=null)
65
    {
66 17
        $this->config = $config;
67
68 17
        $curl_available = function_exists('curl_version');
69
70 17
        $this->portTester = new PortTester([
71 17
            'backend' => $curl_available ? PortTester::BACKEND_CURL : PortTester::BACKEND_STREAM_SOCKET,
72
            // Close timout ms could be adjusted for your system
73
            // It prevent that port availability testing does
74
            // not close quickly enough to allow standalone server binding
75 17
            'close_timeout_ms' => $curl_available ? null : 300
76 17
        ]);
77 17
        if ($logger === null) {
78 17
            $logger = new NullLogger();
79 17
        }
80 17
        $this->logger = $logger;
81 17
    }
82
83
    /**
84
     * Start the standalone server
85
     *
86
     * @throws Exception\RuntimeException
87
     *
88
     * @param int $timeout_ms maximum number of milliseconds to wait for server start
89
     * @return void
90
     */
91 13
    public function start($timeout_ms = 3000)
92
    {
93 13
        $port = $this->config->getPort();
94
95 13
        $this->logger->info("Starting standalone server on port $port.");
96
97 13
        if ($this->isStarted()) {
98 1
            $this->logger->info("Standalone server already running, skipping start.");
99 1
            return;
100
        }
101
102
103
104 13
        if (!$this->portTester->isAvailable('localhost', $port, 'http')) {
105
            $msg = "Cannot start server on port '$port', it's already in use.";
106 1
            $this->logger->error("Start failed: $msg");
107
            throw new Exception\RuntimeException($msg);
108
        }
109
110 13
        $command = $this->getCommand();
111
112 13
        $log_file = $this->config->getLogFile();
113 13
        $pid_file = $this->config->getPidFile();
114 13
        $cmd = sprintf("%s > %s 2>&1 & echo $! > %s", $command, $log_file, $pid_file);
115
116 13
        $this->logger->debug("Start server with: $cmd");
117 13
        exec($cmd);
118
119 13
        if (!file_exists($pid_file)) {
120
            $msg = "Server not started, pid file was not created in '$pid_file'";
121
            $this->logger->error("Start failed: $msg");
122
            throw new Exception\RuntimeException($msg);
123
        }
124 13
        if (!file_exists($log_file)) {
125
            $msg = "Server not started, log file was not created in '$log_file'";
126
            $this->logger->error("Start failed: $msg");
127
            throw new Exception\RuntimeException($msg);
128
        }
129
130
        // Loop for waiting correct start of phpjavabridge
131 13
        $started = false;
132 13
        $iterations = true;
133 13
        $refresh_us = 100 * 1000; // 100ms
134 13
        $timeout_us = $timeout_ms * 1000;
135 13
        $max_iterations = ceil($timeout_us / min([$refresh_us, $timeout_us]));
136
137 13
        while (!$started || $iterations > $max_iterations) {
138 13
            usleep($refresh_us);
139 13
            $log_file_content = file_get_contents($log_file);
140 13
            if (preg_match('/Exception/', $log_file_content)) {
141
                $msg = "Cannot start standalone server on port '$port', reason:\n";
142
                $msg .= $log_file_content;
143
                $this->logger->error("Start failed: $msg");
144
                throw new Exception\RuntimeException($msg);
145
            }
146
147 13
            $log_file_content = file_get_contents($log_file);
148 13
            if (preg_match('/JavaBridgeRunner started on/', $log_file_content)) {
149 13
                $started = true;
150 13
            }
151 13
            $iterations++;
152 13
        }
153 13
        if (!$started) {
154
            $msg = "Standalone server probably not started, timeout '$timeout_ms' reached before getting output";
155
            $this->logger->error("Start failed: $msg");
156
            throw new Exception\RuntimeException($msg);
157
        }
158 13
        $this->started = true;
159 13
    }
160
161
162
163
    /**
164
     * Stop the standalone server
165
     *
166
     * @throws Exception\RuntimeException
167
     * @param boolean $throws_exception wether to throw exception if pid or process cannot be found or killed.
168
     * @return void
169
     */
170 16
    public function stop($throws_exception = false)
171
    {
172 16
        $this->logger->info("Stopping server");
173 16
        $pid_file = $this->config->getPidFile();
174
        try {
175 16
            $pid = $this->getPid();
176 13
            $running = $this->isProcessRunning($throws_exception=true);
177 13
            if (!$running) {
178
                if ($throws_exception) {
179
                    $msg = "Cannot stop: pid exists ($pid) but server process is not running";
180
                    $this->logger->info("Stop failed: $msg");
181
                    throw new Exception\RuntimeException($msg);
182
                }
183
                return;
184
            }
185 16
        } catch (Exception\PidNotFoundException $e) {
186 11
            if ($throws_exception) {
187 1
                $msg = "Cannot stop server, pid file not found (was the server started ?)";
188 1
                $this->logger->info("Stop failed: $msg");
189 1
                throw new Exception\RuntimeException($msg);
190
            }
191 11
            return;
192
        }
193
194
195
        //$cmd = "kill $pid";
0 ignored issues
show
Unused Code Comprehensibility introduced by
50% of this comment could be valid code. Did you maybe forget this after debugging?

Sometimes obsolete code just ends up commented out instead of removed. In this case it is better to remove the code once you have checked you do not need it.

The code might also have been commented out for debugging purposes. In this case it is vital that someone uncomments it again or your project may behave in very unexpected ways in production.

This check looks for comments that seem to be mostly valid code and reports them.

Loading history...
196
        // Let sleep the process,
197
        // @todo: test sleep mith microseconds on different unix flavours
198 13
        $sleep_time = '0.2';
199 13
        $cmd = sprintf("kill %d; while ps -p %d; do sleep %s;done;", $pid, $pid, $sleep_time);
200
201 13
        exec($cmd, $output, $return_var);
202
        try {
203 13
            if ($return_var !== 0) {
204
                $msg = "Cannot kill standalone server process '$pid', seems to not exists.";
205
                $this->logger->info("Stop failed: $msg");
206
                throw new Exception\RuntimeException($msg);
207
            }
208 13
        } catch (Exception\RuntimeException $e) {
209
            if ($throws_exception) {
210
                if (file_exists($pid_file)) {
211
                    unlink($pid_file);
212
                }
213
                throw $e;
214
            }
215
        }
216
217 13
        if (file_exists($pid_file)) {
218 13
            unlink($pid_file);
219 13
        }
220
221 13
        $this->started = false;
222 13
    }
223
224
    /**
225
     * Tells whether the standalone server is started
226
     * @return boolean
227
     */
228 13
    public function isStarted()
229
    {
230 13
        return $this->started;
231
    }
232
233
    /**
234
     * Return command used to start the standalone server
235
     * @return string
236
     */
237 13
    public function getCommand()
238
    {
239 13
        $port = $this->config->getPort();
240
241 13
        $java_bin = $this->config->getJavaBin();
242
243 13
        $jars = [];
244 13
        $classpaths = $this->config->getClasspaths();
245 13
        foreach ($classpaths as $classpath) {
246 11
            $jars[] = $classpath;
247 13
        }
248 13
        $jars[] = $this->config->getServerJar();
249 13
        $classpath = implode(':', $jars);
250
251 13
        $directives = ' -D' . implode(' -D', [
252 13
                    'php.java.bridge.daemon="false"',
253
                    'php.java.bridge.threads=30'
254 13
        ]);
255
256 13
        $command = sprintf('%s -cp "%s" %s php.java.bridge.Standalone SERVLET:%d',
257 13
                            $java_bin, $classpath, $directives, $port);
258
259 13
        return $command;
260
    }
261
262
263
    /**
264
     * Get standalone server pid number as it was stored during last start
265
     *
266
     * @throws Exception\PidNotFoundException
267
     * @return int
268
     */
269 16
    public function getPid()
270
    {
271 16
        $pid_file = $this->config->getPidFile();
272 16
        if (!file_exists($pid_file)) {
273 11
            $msg = "Pid file cannot be found '$pid_file'";
274 11
            $this->logger->error("Get PID failed: $msg");
275 11
            throw new Exception\PidNotFoundException($msg);
276
        }
277 13
        $pid = trim(file_get_contents($pid_file));
278 13
        if (!preg_match('/^[0-9]+$/', $pid)) {
279 1
            $msg = "Pid found '$pid_file' but no valid pid stored in it or corrupted file '$pid_file'.";
280 1
            $this->logger->error("Get PID failed: $msg");
281 1
            throw new Exception\PidCorruptedException($msg);
282
        }
283 13
        return (int) $pid;
284
    }
285
286
287
    /**
288
     * Return the content of the output_file
289
     * @throws Exception\RuntimeException
290
     * @return string
291
     */
292 3
    public function getOutput()
293
    {
294 3
        $log_file = $this->config->getLogFile();
295 3
        if (!file_exists($log_file)) {
296 1
            $msg = "Server output log file does not exists '$log_file'";
297 1
            $this->logger->error("Get server output failed: $msg");
298 1
            throw new Exception\RuntimeException($msg);
299 2
        } elseif (!is_readable($log_file)) {
300 1
            $msg = "Cannot read log file do to missing read permission '$log_file'";
301 1
            $this->logger->error("Get server output failed: $msg");
302 1
            throw new Exception\RuntimeException($msg);
303
        }
304 2
        $output = file_get_contents($log_file);
305 2
        return $output;
306
    }
307
308
309
    /**
310
     * Test whether the standalone server process
311
     * is effectively running
312
     *
313
     * @throws Exception\PidNotFoundException
314
     * @param boolean $throwsException if false discard exception if pidfile not exists
315
     * @return boolean
316
     */
317 13
    public function isProcessRunning($throwsException = false)
318
    {
319 13
        $running = false;
320
        try {
321 13
            $pid = $this->getPid();
322 13
            $cmd = sprintf("ps -j --no-headers -p %d", $pid);
323 13
            $this->logger->debug("Getting process with cmd: $cmd");
324 13
            $result = trim(shell_exec($cmd));
325 13
            if (preg_match("/^$pid/", $result)) {
326 13
                $running = true;
327 13
            }
328 13
        } catch (Exception\PidNotFoundException $e) {
329 1
            if ($throwsException) {
330
                throw $e;
331
            }
332
        }
333 13
        return $running;
334
    }
335
336
337
    /**
338
     * Restart the standalone server
339
     */
340 3
    public function restart()
341
    {
342 3
        $this->stop();
343 3
        $this->start();
344 3
    }
345
346
    /**
347
     * Return underlying configuration object
348
     * @return StandaloneServer\Config
349
     */
350 7
    public function getConfig()
351
    {
352 7
        return $this->config;
353
    }
354
}
355