Completed
Push — master ( cc84b4...34fefe )
by Sébastien
03:30
created

StandaloneServer::__construct()   A

Complexity

Conditions 4
Paths 2

Size

Total Lines 18
Code Lines 9

Duplication

Lines 0
Ratio 0 %

Code Coverage

Tests 0
CRAP Score 20

Importance

Changes 9
Bugs 0 Features 0
Metric Value
c 9
b 0
f 0
dl 0
loc 18
ccs 0
cts 13
cp 0
rs 9.2
cc 4
eloc 9
nc 2
nop 2
crap 20
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
    public function __construct(StandaloneServer\Config $config, LoggerInterface $logger=null)
65
    {
66
        $this->config = $config;
67
68
        $curl_available = function_exists('curl_version');
69
70
        $this->portTester = new PortTester([
71
            '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
            'close_timeout_ms' => $curl_available ? null : 300
76
        ]);
77
        if ($logger === null) {
78
            $logger = new NullLogger();
79
        }
80
        $this->logger = $logger;
81
    }
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
    public function start($timeout_ms = 3000)
92
    {
93
        $port = $this->config->getPort();
94
95
        $this->logger->info("Starting standalone server on port $port.");
96
97
        if ($this->isStarted()) {
98
            $this->logger->info("Standalone server already running, skipping start.");
99
            return;
100
        }
101
102
103
104
        if (!$this->portTester->isAvailable('localhost', $port, 'http')) {
105
            $msg = "Cannot start server on port '$port', it's already in use.";
106
            $this->logger->error("Start failed: $msg");
107
            throw new Exception\RuntimeException($msg);
108
        }
109
110
        $command = $this->getCommand();
111
112
        $log_file = $this->config->getLogFile();
113
        $pid_file = $this->config->getPidFile();
114
        $cmd = sprintf("%s > %s 2>&1 & echo $! > %s", $command, $log_file, $pid_file);
115
116
        $this->logger->debug("Start server with: $cmd");
117
        exec($cmd);
118
119
        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
        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
        $started = false;
132
        $iterations = true;
133
        $refresh_us = 100 * 1000; // 100ms
134
        $timeout_us = $timeout_ms * 1000;
135
        $max_iterations = ceil($timeout_us / min([$refresh_us, $timeout_us]));
136
137
        while (!$started || $iterations > $max_iterations) {
138
            usleep($refresh_us);
139
            $log_file_content = file_get_contents($log_file);
140
            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
            $log_file_content = file_get_contents($log_file);
148
            if (preg_match('/JavaBridgeRunner started on/', $log_file_content)) {
149
                $started = true;
150
            }
151
            $iterations++;
152
        }
153
        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
        $this->started = true;
159
    }
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
    public function stop($throws_exception = false)
171
    {
172
        $this->logger->info("Stopping server");
173
        $pid_file = $this->config->getPidFile();
174
        try {
175
            $pid = $this->getPid();
176
            $running = $this->isProcessRunning($throws_exception=true);
177
            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
        } catch (Exception\PidNotFoundException $e) {
186
            if ($throws_exception) {
187
                $msg = "Cannot stop server, pid file not found (was the server started ?)";
188
                $this->logger->info("Stop failed: $msg");
189
                throw new Exception\RuntimeException($msg);
190
            }
191
            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
        $sleep_time = '0.2';
199
        $cmd = sprintf("kill %d; while ps -p %d; do sleep %s;done;", $pid, $pid, $sleep_time);
200
201
        exec($cmd, $output, $return_var);
202
        try {
203
            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
        } 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
        if (file_exists($pid_file)) {
218
            unlink($pid_file);
219
        }
220
221
        $this->started = false;
222
    }
223
224
    /**
225
     * Tells whether the standalone server is started
226
     * @return boolean
227
     */
228
    public function isStarted()
229
    {
230
        return $this->started;
231
    }
232
233
    /**
234
     * Return command used to start the standalone server
235
     * @return string
236
     */
237
    public function getCommand()
238
    {
239
        $port = $this->config->getPort();
240
241
        $java_bin = $this->config->getJavaBin();
242
243
        $jars = [];
244
        $classpaths = $this->config->getClasspaths();
245
        foreach ($classpaths as $classpath) {
246
            if (preg_match('/\*\.jar$/', $classpath)) {
247
                $directory = preg_replace('/\*\.jar$/', '', $classpath);
248
                $files = glob("$directory/*.jar");
249
                foreach ($files as $file) {
250
                    foreach ($files as $file) {
251
                        $jars[] = $file;
252
                    }
253
                }
254
            } else {
255
                $jars[] = $classpath;
256
            }
257
        }
258
259
        $jars[] = $this->config->getServerJar();
260
        $classpath = implode(':', $jars);
261
262
        $directives = ' -D' . implode(' -D', [
263
                    'php.java.bridge.daemon="false"',
264
                    'php.java.bridge.threads=50'
265
        ]);
266
267
        $command = sprintf('%s -cp "%s" %s php.java.bridge.Standalone SERVLET:%d',
268
                            $java_bin, $classpath, $directives, $port);
269
270
        return $command;
271
    }
272
273
274
    /**
275
     * Get standalone server pid number as it was stored during last start
276
     *
277
     * @throws Exception\PidNotFoundException
278
     * @return int
279
     */
280
    public function getPid()
281
    {
282
        $pid_file = $this->config->getPidFile();
283
        if (!file_exists($pid_file)) {
284
            $msg = "Pid file cannot be found '$pid_file'";
285
            $this->logger->error("Get PID failed: $msg");
286
            throw new Exception\PidNotFoundException($msg);
287
        }
288
        $pid = trim(file_get_contents($pid_file));
289
        if (!preg_match('/^[0-9]+$/', $pid)) {
290
            $msg = "Pid found '$pid_file' but no valid pid stored in it or corrupted file '$pid_file'.";
291
            $this->logger->error("Get PID failed: $msg");
292
            throw new Exception\PidCorruptedException($msg);
293
        }
294
        return (int) $pid;
295
    }
296
297
298
    /**
299
     * Return the content of the output_file
300
     * @throws Exception\RuntimeException
301
     * @return string
302
     */
303
    public function getOutput()
304
    {
305
        $log_file = $this->config->getLogFile();
306
        if (!file_exists($log_file)) {
307
            $msg = "Server output log file does not exists '$log_file'";
308
            $this->logger->error("Get server output failed: $msg");
309
            throw new Exception\RuntimeException($msg);
310
        } elseif (!is_readable($log_file)) {
311
            $msg = "Cannot read log file do to missing read permission '$log_file'";
312
            $this->logger->error("Get server output failed: $msg");
313
            throw new Exception\RuntimeException($msg);
314
        }
315
        $output = file_get_contents($log_file);
316
        return $output;
317
    }
318
319
320
    /**
321
     * Test whether the standalone server process
322
     * is effectively running
323
     *
324
     * @throws Exception\PidNotFoundException
325
     * @param boolean $throwsException if false discard exception if pidfile not exists
326
     * @return boolean
327
     */
328
    public function isProcessRunning($throwsException = false)
329
    {
330
        $running = false;
331
        try {
332
            $pid = $this->getPid();
333
            $cmd = sprintf("ps -j --no-headers -p %d", $pid);
334
            $this->logger->debug("Getting process with cmd: $cmd");
335
            $result = trim(shell_exec($cmd));
336
            if (preg_match("/^$pid/", $result)) {
337
                $running = true;
338
            }
339
        } catch (Exception\PidNotFoundException $e) {
340
            if ($throwsException) {
341
                throw $e;
342
            }
343
        }
344
        return $running;
345
    }
346
347
348
    /**
349
     * Restart the standalone server
350
     */
351
    public function restart()
352
    {
353
        $this->stop();
354
        $this->start();
355
    }
356
357
    /**
358
     * Return underlying configuration object
359
     * @return StandaloneServer\Config
360
     */
361
    public function getConfig()
362
    {
363
        return $this->config;
364
    }
365
}
366