Completed
Push — master ( d12817...b61217 )
by Sébastien
02:16
created

StandaloneServer::getCommand()   B

Complexity

Conditions 2
Paths 2

Size

Total Lines 24
Code Lines 15

Duplication

Lines 0
Ratio 0 %

Code Coverage

Tests 16
CRAP Score 2

Importance

Changes 3
Bugs 0 Features 1
Metric Value
c 3
b 0
f 1
dl 0
loc 24
ccs 16
cts 16
cp 1
rs 8.9714
cc 2
eloc 15
nc 2
nop 0
crap 2
1
<?php
2
3
namespace PjbServer\Tools;
4
5
use PjbServer\Tools\Network\PortTester;
6
7
/*
8
  java -Djava.awt.headless="true"
9
  -Dphp.java.bridge.threads=50
10
  -Dphp.java.bridge.base=/usr/lib/php/modules
11
  -Dphp.java.bridge.php_exec=/usr/local/bin/php-cgi
12
  -Dphp.java.bridge.default_log_file=
13
  -Dphp.java.bridge.default_log_level=5
14
  -Dphp.java.bridge.daemon="false"
15
  -jar JavaBridge.jar
16
 * sudo netstat -anltp|grep :8089
17
 */
18
19
class StandaloneServer
20
{
21
22
    /**
23
     * @var int
24
     */
25
    protected $port;
26
27
    /**
28
     *
29
     * @var array
30
     */
31
    protected $config;
32
33
    /**
34
     * Tells whether the standalone server is started
35
     * @var boolean
36
     */
37
    protected $started = false;
38
39
    /**
40
     * @var array
41
     */
42
    protected $required_arguments = array(
43
        'port' => 'FILTER_VALIDATE_INT',
44
        'server_jar' => 'existing_file',
45
        'log_file' => 'file_with_existing_directory',
46
        'pid_file' => 'file_with_existing_directory',
47
        'autoload_path' => 'existing_directory'
48
    );
49
50
    /**
51
     * Default configuration options
52
     * @var array
53
     */
54
    protected $default_config = array(
55
        'server_jar' => '{base_dir}/resources/pjb621_standalone/JavaBridge.jar',
56
        'java_bin' => 'java',
57
        'log_file' => '{base_dir}/resources/pjb621_standalone/logs/pjbserver-port{tcp_port}.log',
58
        'pid_file' => '{base_dir}/resources/pjb621_standalone/var/run/pjbserver-port{tcp_port}.pid',
59
        'autoload_path' => '{base_dir}/resources/autoload'
60
    );
61
62
    /**
63
     *
64
     * @var PortTester
65
     */
66
    protected $portTester;
67
68
69
    /**
70
     * Constructor
71
     *
72
     * <code>
73
     *
74
     * $config = array(
75
     *      'port' => 8089,
76
     *
77
     *      // optionally
78
     *      'server_jar' => 'path/to/JavaBridge.jar'
79
     *      'java_bin' => 'path/to/java'
80
     * );
81
     * $server = new StandaloneServer($config);
82
     *
83
     * </code>
84
     *
85
     * @throws Exception\InvalidArgumentException
86
     * @param array $config
87
     */
88 12
    public function __construct(array $config)
89
    {
90 12
        if (!isset($config['port'])) {
91 3
            throw new Exception\InvalidArgumentException("Error missing required 'port' in config");
92 9
        } elseif (!filter_var($config['port'], FILTER_VALIDATE_INT)) {
93 1
            throw new Exception\InvalidArgumentException("Option 'port' must be numeric");
94
        }
95 8
        $config = array_merge($this->getDefaultConfig($config['port']), $config);
96 8
        $this->checkConfigRequiredArgs($config);
97 7
        $this->setServerPort($config['port']);
98 7
        $this->config = $config;
99
100 7
        $curl_available = function_exists('curl_version');
101
102 7
        $this->portTester = new PortTester(array(
103 7
            'backend' => $curl_available ? PortTester::BACKEND_CURL : PortTester::BACKEND_STREAM_SOCKET,
104
            // Close timout ms could be adjusted for your system
105
            // It prevent that port availability testing does
106
            // not close quickly enough to allow standalone server binding
107 7
            'close_timeout_ms' => $curl_available ? null : 300
108 7
        ));
109 7
    }
110
111
    /**
112
     * Start the standalone server
113
     *
114
     * @throws Exception\RuntimeException
115
     * @throws Exce
116
     *
117
     * @param int $timeout_ms maximum number of milliseconds to wait for server start
118
     * @return void
119
     */
120 5
    public function start($timeout_ms = 3000)
121
    {
122
123 5
        if ($this->isStarted()) {
124
            return;
125
        }
126
127 5
        $port = $this->getServerPort();
128
129 5
        if (!$this->portTester->isAvailable('localhost', $port, 'http')) {
130
            $msg = "Cannot start server on port '$port', it's already in use.";
131
            throw new Exception\RuntimeException($msg);
132
        }
133
134 5
        $command = $this->getCommand();
135
136 5
        $log_file = $this->config['log_file'];
137 5
        $pid_file = $this->config['pid_file'];
138 5
        $cmd = sprintf("%s > %s 2>&1 & echo $! > %s", $command, $log_file, $pid_file);
139
140 5
        exec($cmd);
141
142 5
        if (!file_exists($pid_file)) {
143
            $msg = "Server not started, pid file was not created in '$pid_file'";
144
            throw new Exception\RuntimeException($msg);
145
        }
146 5
        if (!file_exists($log_file)) {
147
            $msg = "Server not started, log file was not created in '$log_file'";
148
            throw new Exception\RuntimeException($msg);
149
        }
150
151
        // Loop for waiting correct start of phpjavabridge
152 5
        $started = false;
153 5
        $iterations = true;
154 5
        $refresh_us = 100 * 1000; // 200ms
155 5
        $timeout_us = $timeout_ms * 1000;
156 5
        $max_iterations = ceil($timeout_us / min(array($refresh_us, $timeout_us)));
157
158 5
        while (!$started || $iterations > $max_iterations) {
159 5
            usleep($refresh_us);
160 5
            $log_file_content = file_get_contents($log_file);
161 5
            if (preg_match('/Exception/', $log_file_content)) {
162
                $msg = "Cannot start standalone server on port '$port', reason:\n";
163
                $msg .= $log_file_content;
164
                throw new Exception\RuntimeException($msg);
165
            }
166
167 5
            $log_file_content = file_get_contents($log_file);
168 5
            if (preg_match('/JavaBridgeRunner started on/', $log_file_content)) {
169 5
                $started = true;
170 5
            }
171 5
            $iterations++;
172 5
        }
173 5
        if (!$started) {
174
            $msg = "Standalone server probably not started, timeout '$timeout_ms' reached before getting output";
175
            throw new Exception\RuntimeException($msg);
176
        }
177 5
        $this->started = true;
178 5
    }
179
180
181
182
    /**
183
     * Stop the standalone server
184
     *
185
     * @throws Exception\RuntimeException
186
     * @param boolean $throws_exception wether to throw exception if pid or process cannot be found or killed.
187
     * @return void
188
     */
189 5
    public function stop($throws_exception = false)
190
    {
191 5
        $pid_file = $this->config['pid_file'];
192
193
        try {
194 5
            $pid = $this->getPid();
195 5
        } catch (Exception\RuntimeException $e) {
196 1
            $msg = "Cannot stop server, pid cannot be determined (was the server started ?)";
197 1
            if ($throws_exception) {
198
                throw new Exception\RuntimeException($msg);
199
            }
200 1
            return;
201
        }
202
203
        //$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...
204
        // Let sleep the process, 
205
        // @todo: test sleep mith microseconds on different unix flavours
206 5
        $sleep_time = '0.2';
207 5
        $cmd = "kill $pid; while ps -p $pid; do sleep $sleep_time;done;";
208
209 5
        exec($cmd, $output, $return_var);
210
        
211
        try {
212 5
            if ($return_var !== 0) {
213
                $msg = "Cannot kill standalone server process '$pid', seems to not exists.";
214
                throw new Exception\RuntimeException($msg);
215
            }
216 5
        } catch (Exception\RuntimeException $e) {
217
            if ($throws_exception) {
218
                if (file_exists($pid_file)) {
219
                    unlink($pid_file);
220
                }
221
                throw $e;
222
            }
223
        }
224
225
        
226 5
        if (file_exists($pid_file)) {
227 5
            unlink($pid_file);
228 5
        }
229
        
230 5
        $this->started = false;
231 5
    }
232
233
    /**
234
     * Tells whether the standalone server is started
235
     * @return boolean
236
     */
237 5
    public function isStarted()
238
    {
239 5
        return $this->started;
240
    }
241
242
    /**
243
     * Return command used to start the standalone server
244
     * @return string
245
     */
246 5
    public function getCommand()
247
    {
248 5
        $port = $this->getServerPort();
249
250 5
        $java_bin = $this->config['java_bin'];
251
252 5
        $jars = array();
253 5
        $autoload_path = $this->config['autoload_path'];
254 5
        $files = glob("$autoload_path/*.jar");
255 5
        foreach($files as $file) {
256 5
            $jars[] = $file;
257 5
        }
258 5
        $jars[] = $this->config['server_jar'];
259
        
260 5
        $classpath = join(':', $jars);
261
262 5
        $directives = ' -D' . join(' -D', array(
263 5
                    'php.java.bridge.daemon="false"',
264
                    'php.java.bridge.threads=30'
265 5
        ));
266
267 5
        $command = "$java_bin -cp $classpath $directives php.java.bridge.Standalone SERVLET:$port";
268 5
        return $command;
269
    }
270
271
272
    /**
273
     * Get runnin standalone server pid number
274
     *
275
     * @throws Exception\RuntimeException
276
     * @return int
277
     */
278 5
    public function getPid()
279
    {
280 5
        $pid_file = $this->config['pid_file'];
281 5
        if (!file_exists($pid_file)) {
282 1
            $msg = "Pid file cannot be found '$pid_file'";
283 1
            throw new Exception\RuntimeException($msg);
284
        }
285 5
        $pid = trim(file_get_contents($pid_file));
286 5
        if (!is_numeric($pid)) {
287
            $msg = "Pid found '$pid_file' but no valid pid stored in it.";
288
            throw new Exception\RuntimeException($msg);
289
        }
290 5
        return $pid;
291
    }
292
    
293
    
294
    /**
295
     * Return the content of the output_file
296
     * @throws \RuntimeException
297
     * @return string
298
     */
299 1
    public function getOutput()
300
    {
301 1
        $log_file = $this->config['log_file'];
302 1
        if (!file_exists($log_file)) {
303
            throw new \RuntimeException("Server output log file does not exists '$log_file'");
304
        }
305 1
        $output = file_get_contents($log_file);
306 1
        return $output;
307
        
308
    }
309
    
310
    
311
    /**
312
     * Test whether the standalone server is effectively running
313
     * @return boolean
314
     */
315 1
    public function isRunning()
316
    {
317
        try {
318 1
            $pid = $this->getPid();
319 1
        } catch (\Exception $e) {
320 1
            return false;
321
        }
322 1
        $result = shell_exec(sprintf("ps %d", $pid));
323 1
        if( count(preg_split("/\n/", $result)) > 2){
324 1
            return true;
325
        }                    
326
        return false;
327
        
328
    }
329
330
    /**
331
     * Restart the standalone server
332
     */
333
    public function restart()
334
    {
335
        $this->stop();
336
        $this->start();
337
    }
338
339
    /**
340
     * Return port on which standalone server listens
341
     * @return int
342
     */
343 6
    public function getServerPort()
344
    {
345 6
        return $this->port;
346
    }
347
348
    /**
349
     * Set port on which standalone server listens.
350
     *
351
     * @param int $port
352
     * @return void
353
     */
354 7
    protected function setServerPort($port)
355
    {
356 7
        $this->port = $port;
357 7
    }
358
359
    /**
360
     * Return standalone configuration
361
     *
362
     * @return array
363
     */
364 4
    public function getConfig()
365
    {
366 4
        return $this->config;
367
    }
368
369
    /**
370
     * Return default configuration options
371
     * @param int $port
372
     * @return array
373
     */
374 8
    protected function getDefaultConfig($port)
375
    {
376 8
        $base_dir = realpath(__DIR__ . '/../../../');
377 8
        $config = array();
378 8
        foreach ($this->default_config as $key => $value) {
379 8
            $tmp = str_replace('{base_dir}', $base_dir, $value);
380 8
            $tmp = str_replace('{tcp_port}', $port, $tmp);
381 8
            $config[$key] = $tmp;
382 8
        }
383 8
        return $config;
384
    }
385
386
    /**
387
     * Check configuration parameters
388
     * @throws Exception\InvalidArgumentException
389
     * @param array $config
390
     */
391 8
    protected function checkConfigRequiredArgs(array $config)
392
    {
393 8
        foreach ($this->required_arguments as $name => $type) {
394 8
            if (!isset($config[$name])) {
395
                $msg = "Missing option '$name' in Standalone server configuration";
396
                throw new Exception\InvalidArgumentException($msg);
397
            }
398 8
            if (is_long($type) && !filter_var($config[$name], $type)) {
399
                $msg = "Unsupported type in option '$name' in Standalone server configuration (required $type)";
400
                throw new Exception\InvalidArgumentException($msg);
401 8
            } elseif (is_string($type)) {
402
                switch ($type) {
403 8
                    case 'existing_file':
404 8
                        $file = $config[$name];
405 8
                        if (!file_exists($file)) {
406 1
                            $msg = "The '$name' file '$file 'does not exists.";
407 1
                            throw new Exception\InvalidArgumentException($msg);
408
                        }
409 7
                        break;
410 8
                    case 'existing_directory':
411 7
                        $directory = $config[$name];
412 7
                        if (!is_dir($directory)) {
413
                            $msg = "The '$name' directory '$directory 'does not exists.";
414
                            throw new Exception\InvalidArgumentException($msg);
415
                        }
416 7
                        break;
417
                        
418 8
                    case 'file_with_existing_directory':
419 7
                        $file = $config[$name];
420 7
                        $info = pathinfo($file);
421 7
                        $dirname = $info['dirname'];
422 7
                        if (!is_dir($dirname) || $dirname == ".") {
423
                            $msg = "Option '$name' refer to an invalid or non-existent directory ($file)";
424
                            throw new Exception\InvalidArgumentException($msg);
425
                        }
426 7
                        if (is_dir($file)) {
427
                            $msg = "Option '$name' does not refer to a file but an existing directory ($file)";
428
                            throw new Exception\InvalidArgumentException($msg);
429
                        }
430 7
                        if (file_exists($file) && !is_writable($file)) {
431
                            $msg = "File specified in '$name' is not writable ($file)";
432
                            throw new Exception\InvalidArgumentException($msg);
433
                        }
434 7
                        break;
435
                }
436 8
            }
437 8
        }
438 7
    }
439
}
440