|
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 = [ |
|
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 = [ |
|
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
|
19 |
|
public function __construct(array $config) |
|
89
|
|
|
{ |
|
90
|
19 |
|
if (!isset($config['port'])) { |
|
91
|
3 |
|
throw new Exception\InvalidArgumentException("Error missing required 'port' in config"); |
|
92
|
16 |
|
} elseif (!filter_var($config['port'], FILTER_VALIDATE_INT)) { |
|
93
|
1 |
|
throw new Exception\InvalidArgumentException("Option 'port' must be numeric"); |
|
94
|
|
|
} |
|
95
|
15 |
|
$config = array_merge($this->getDefaultConfig($config['port']), $config); |
|
96
|
15 |
|
$this->checkConfigRequiredArgs($config); |
|
97
|
14 |
|
$this->setServerPort($config['port']); |
|
98
|
14 |
|
$this->config = $config; |
|
99
|
|
|
|
|
100
|
14 |
|
$curl_available = function_exists('curl_version'); |
|
101
|
|
|
|
|
102
|
14 |
|
$this->portTester = new PortTester([ |
|
103
|
14 |
|
'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
|
14 |
|
'close_timeout_ms' => $curl_available ? null : 300 |
|
108
|
14 |
|
]); |
|
109
|
14 |
|
} |
|
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
|
11 |
|
public function start($timeout_ms = 3000) |
|
121
|
|
|
{ |
|
122
|
11 |
|
if ($this->isStarted()) { |
|
123
|
1 |
|
return; |
|
124
|
|
|
} |
|
125
|
|
|
|
|
126
|
11 |
|
$port = $this->getServerPort(); |
|
127
|
|
|
|
|
128
|
11 |
|
if (!$this->portTester->isAvailable('localhost', $port, 'http')) { |
|
129
|
|
|
$msg = "Cannot start server on port '$port', it's already in use."; |
|
130
|
|
|
throw new Exception\RuntimeException($msg); |
|
131
|
|
|
} |
|
132
|
|
|
|
|
133
|
11 |
|
$command = $this->getCommand(); |
|
134
|
|
|
|
|
135
|
11 |
|
$log_file = $this->config['log_file']; |
|
136
|
11 |
|
$pid_file = $this->config['pid_file']; |
|
137
|
11 |
|
$cmd = sprintf("%s > %s 2>&1 & echo $! > %s", $command, $log_file, $pid_file); |
|
138
|
|
|
|
|
139
|
11 |
|
exec($cmd); |
|
140
|
|
|
|
|
141
|
11 |
|
if (!file_exists($pid_file)) { |
|
142
|
|
|
$msg = "Server not started, pid file was not created in '$pid_file'"; |
|
143
|
|
|
throw new Exception\RuntimeException($msg); |
|
144
|
|
|
} |
|
145
|
11 |
|
if (!file_exists($log_file)) { |
|
146
|
|
|
$msg = "Server not started, log file was not created in '$log_file'"; |
|
147
|
|
|
throw new Exception\RuntimeException($msg); |
|
148
|
|
|
} |
|
149
|
|
|
|
|
150
|
|
|
// Loop for waiting correct start of phpjavabridge |
|
151
|
11 |
|
$started = false; |
|
152
|
11 |
|
$iterations = true; |
|
153
|
11 |
|
$refresh_us = 100 * 1000; // 100ms |
|
154
|
11 |
|
$timeout_us = $timeout_ms * 1000; |
|
155
|
11 |
|
$max_iterations = ceil($timeout_us / min([$refresh_us, $timeout_us])); |
|
156
|
|
|
|
|
157
|
11 |
|
while (!$started || $iterations > $max_iterations) { |
|
158
|
11 |
|
usleep($refresh_us); |
|
159
|
11 |
|
$log_file_content = file_get_contents($log_file); |
|
160
|
11 |
|
if (preg_match('/Exception/', $log_file_content)) { |
|
161
|
|
|
$msg = "Cannot start standalone server on port '$port', reason:\n"; |
|
162
|
|
|
$msg .= $log_file_content; |
|
163
|
|
|
throw new Exception\RuntimeException($msg); |
|
164
|
|
|
} |
|
165
|
|
|
|
|
166
|
11 |
|
$log_file_content = file_get_contents($log_file); |
|
167
|
11 |
|
if (preg_match('/JavaBridgeRunner started on/', $log_file_content)) { |
|
168
|
11 |
|
$started = true; |
|
169
|
11 |
|
} |
|
170
|
11 |
|
$iterations++; |
|
171
|
11 |
|
} |
|
172
|
11 |
|
if (!$started) { |
|
173
|
|
|
$msg = "Standalone server probably not started, timeout '$timeout_ms' reached before getting output"; |
|
174
|
|
|
throw new Exception\RuntimeException($msg); |
|
175
|
|
|
} |
|
176
|
11 |
|
$this->started = true; |
|
177
|
11 |
|
} |
|
178
|
|
|
|
|
179
|
|
|
|
|
180
|
|
|
|
|
181
|
|
|
/** |
|
182
|
|
|
* Stop the standalone server |
|
183
|
|
|
* |
|
184
|
|
|
* @throws Exception\RuntimeException |
|
185
|
|
|
* @param boolean $throws_exception wether to throw exception if pid or process cannot be found or killed. |
|
186
|
|
|
* @return void |
|
187
|
|
|
*/ |
|
188
|
12 |
|
public function stop($throws_exception = false) |
|
189
|
|
|
{ |
|
190
|
12 |
|
$pid_file = $this->config['pid_file']; |
|
191
|
|
|
try { |
|
192
|
12 |
|
$pid = $this->getPid(); |
|
193
|
11 |
|
$running = $this->isProcessRunning($throws_exception=true); |
|
194
|
11 |
|
if (!$running) { |
|
195
|
|
|
if ($throws_exception) { |
|
196
|
|
|
$msg = "Cannot stop: pid exists ($pid) but server process is not running"; |
|
197
|
|
|
throw new Exception\RuntimeException($msg); |
|
198
|
|
|
} |
|
199
|
|
|
return; |
|
200
|
|
|
} |
|
201
|
12 |
|
} catch (Exception\PidNotFoundException $e) { |
|
202
|
9 |
|
if ($throws_exception) { |
|
203
|
1 |
|
$msg = "Cannot stop server, pid file not found (was the server started ?)"; |
|
204
|
1 |
|
throw new Exception\RuntimeException($msg); |
|
205
|
|
|
} |
|
206
|
9 |
|
return; |
|
207
|
|
|
} |
|
208
|
|
|
|
|
209
|
|
|
|
|
210
|
|
|
//$cmd = "kill $pid"; |
|
|
|
|
|
|
211
|
|
|
// Let sleep the process, |
|
212
|
|
|
// @todo: test sleep mith microseconds on different unix flavours |
|
213
|
11 |
|
$sleep_time = '0.2'; |
|
214
|
11 |
|
$cmd = sprintf("kill %d; while ps -p %d; do sleep %s;done;", $pid, $pid, $sleep_time); |
|
215
|
|
|
|
|
216
|
11 |
|
exec($cmd, $output, $return_var); |
|
217
|
|
|
try { |
|
218
|
11 |
|
if ($return_var !== 0) { |
|
219
|
|
|
$msg = "Cannot kill standalone server process '$pid', seems to not exists."; |
|
220
|
|
|
throw new Exception\RuntimeException($msg); |
|
221
|
|
|
} |
|
222
|
11 |
|
} catch (Exception\RuntimeException $e) { |
|
223
|
|
|
if ($throws_exception) { |
|
224
|
|
|
if (file_exists($pid_file)) { |
|
225
|
|
|
unlink($pid_file); |
|
226
|
|
|
} |
|
227
|
|
|
throw $e; |
|
228
|
|
|
} |
|
229
|
|
|
} |
|
230
|
|
|
|
|
231
|
11 |
|
if (file_exists($pid_file)) { |
|
232
|
11 |
|
unlink($pid_file); |
|
233
|
11 |
|
} |
|
234
|
|
|
|
|
235
|
11 |
|
$this->started = false; |
|
236
|
11 |
|
} |
|
237
|
|
|
|
|
238
|
|
|
/** |
|
239
|
|
|
* Tells whether the standalone server is started |
|
240
|
|
|
* @return boolean |
|
241
|
|
|
*/ |
|
242
|
11 |
|
public function isStarted() |
|
243
|
|
|
{ |
|
244
|
11 |
|
return $this->started; |
|
245
|
|
|
} |
|
246
|
|
|
|
|
247
|
|
|
/** |
|
248
|
|
|
* Return command used to start the standalone server |
|
249
|
|
|
* @return string |
|
250
|
|
|
*/ |
|
251
|
11 |
|
public function getCommand() |
|
252
|
|
|
{ |
|
253
|
11 |
|
$port = $this->getServerPort(); |
|
254
|
|
|
|
|
255
|
11 |
|
$java_bin = $this->config['java_bin']; |
|
256
|
|
|
|
|
257
|
11 |
|
$jars = []; |
|
258
|
11 |
|
$autoload_path = $this->config['autoload_path']; |
|
259
|
11 |
|
$files = glob("$autoload_path/*.jar"); |
|
260
|
11 |
|
foreach ($files as $file) { |
|
261
|
11 |
|
$jars[] = $file; |
|
262
|
11 |
|
} |
|
263
|
11 |
|
$jars[] = $this->config['server_jar']; |
|
264
|
|
|
|
|
265
|
11 |
|
$classpath = implode(':', $jars); |
|
266
|
|
|
|
|
267
|
11 |
|
$directives = ' -D' . implode(' -D', [ |
|
268
|
11 |
|
'php.java.bridge.daemon="false"', |
|
269
|
|
|
'php.java.bridge.threads=30' |
|
270
|
11 |
|
]); |
|
271
|
|
|
|
|
272
|
11 |
|
$command = "$java_bin -cp $classpath $directives php.java.bridge.Standalone SERVLET:$port"; |
|
273
|
11 |
|
return $command; |
|
274
|
|
|
} |
|
275
|
|
|
|
|
276
|
|
|
|
|
277
|
|
|
/** |
|
278
|
|
|
* Get runnin standalone server pid number |
|
279
|
|
|
* |
|
280
|
|
|
* @throws Exception\PidNotFoundException |
|
281
|
|
|
* @return int |
|
282
|
|
|
*/ |
|
283
|
12 |
|
public function getPid() |
|
284
|
|
|
{ |
|
285
|
12 |
|
$pid_file = $this->config['pid_file']; |
|
286
|
12 |
|
if (!file_exists($pid_file)) { |
|
287
|
9 |
|
$msg = "Pid file cannot be found '$pid_file'"; |
|
288
|
9 |
|
throw new Exception\PidNotFoundException($msg); |
|
289
|
|
|
} |
|
290
|
11 |
|
$pid = trim(file_get_contents($pid_file)); |
|
291
|
11 |
|
if (!preg_match('/^[0-9]+$/', $pid)) { |
|
292
|
1 |
|
$msg = "Pid found '$pid_file' but no valid pid stored in it or corrupted file '$pid_file'."; |
|
293
|
1 |
|
throw new Exception\PidCorruptedException($msg); |
|
294
|
|
|
} |
|
295
|
11 |
|
return (int) $pid; |
|
296
|
|
|
} |
|
297
|
|
|
|
|
298
|
|
|
|
|
299
|
|
|
/** |
|
300
|
|
|
* Return the content of the output_file |
|
301
|
|
|
* @throws \RuntimeException |
|
302
|
|
|
* @return string |
|
303
|
|
|
*/ |
|
304
|
3 |
|
public function getOutput() |
|
305
|
|
|
{ |
|
306
|
3 |
|
$log_file = $this->config['log_file']; |
|
307
|
3 |
|
if (!file_exists($log_file)) { |
|
308
|
1 |
|
throw new Exception\RuntimeException("Server output log file does not exists '$log_file'"); |
|
309
|
2 |
|
} elseif (!is_readable($log_file)) { |
|
310
|
1 |
|
throw new Exception\RuntimeException("Cannot read log file do to missing read permission '$log_file'"); |
|
311
|
|
|
} |
|
312
|
2 |
|
$output = file_get_contents($log_file); |
|
313
|
2 |
|
return $output; |
|
314
|
|
|
} |
|
315
|
|
|
|
|
316
|
|
|
|
|
317
|
|
|
/** |
|
318
|
|
|
* Test whether the standalone server process |
|
319
|
|
|
* is effectively running |
|
320
|
|
|
* |
|
321
|
|
|
* @throws Exception\PidNotFoundException |
|
322
|
|
|
* @param $throwsException if false discard exception if pidfile not exists |
|
323
|
|
|
* @return boolean |
|
324
|
|
|
*/ |
|
325
|
11 |
|
public function isProcessRunning($throwsException = false) |
|
326
|
|
|
{ |
|
327
|
11 |
|
$running = false; |
|
328
|
|
|
try { |
|
329
|
11 |
|
$pid = $this->getPid(); |
|
330
|
11 |
|
$result = trim(shell_exec(sprintf("ps -j --no-headers -p %d", $pid))); |
|
331
|
11 |
|
if (preg_match("/^$pid/", $result)) { |
|
332
|
11 |
|
$running = true; |
|
333
|
11 |
|
} |
|
334
|
11 |
|
} catch (Exception\PidNotFoundException $e) { |
|
335
|
1 |
|
if ($throwsException) { |
|
336
|
|
|
throw $e; |
|
337
|
|
|
} |
|
338
|
|
|
} |
|
339
|
11 |
|
return $running; |
|
340
|
|
|
} |
|
341
|
|
|
|
|
342
|
|
|
|
|
343
|
|
|
/** |
|
344
|
|
|
* Restart the standalone server |
|
345
|
|
|
*/ |
|
346
|
2 |
|
public function restart() |
|
347
|
|
|
{ |
|
348
|
2 |
|
$this->stop(); |
|
349
|
2 |
|
$this->start(); |
|
350
|
2 |
|
} |
|
351
|
|
|
|
|
352
|
|
|
/** |
|
353
|
|
|
* Return port on which standalone server listens |
|
354
|
|
|
* @return int |
|
355
|
|
|
*/ |
|
356
|
12 |
|
public function getServerPort() |
|
357
|
|
|
{ |
|
358
|
12 |
|
return $this->port; |
|
359
|
|
|
} |
|
360
|
|
|
|
|
361
|
|
|
/** |
|
362
|
|
|
* Set port on which standalone server listens. |
|
363
|
|
|
* |
|
364
|
|
|
* @param int $port |
|
365
|
|
|
* @return void |
|
366
|
|
|
*/ |
|
367
|
14 |
|
protected function setServerPort($port) |
|
368
|
|
|
{ |
|
369
|
14 |
|
$this->port = $port; |
|
370
|
14 |
|
} |
|
371
|
|
|
|
|
372
|
|
|
/** |
|
373
|
|
|
* Return standalone configuration |
|
374
|
|
|
* |
|
375
|
|
|
* @return array |
|
376
|
|
|
*/ |
|
377
|
7 |
|
public function getConfig() |
|
378
|
|
|
{ |
|
379
|
7 |
|
return $this->config; |
|
380
|
|
|
} |
|
381
|
|
|
|
|
382
|
|
|
/** |
|
383
|
|
|
* Return default configuration options |
|
384
|
|
|
* @param int $port |
|
385
|
|
|
* @return array |
|
386
|
|
|
*/ |
|
387
|
15 |
|
protected function getDefaultConfig($port) |
|
388
|
|
|
{ |
|
389
|
15 |
|
$base_dir = realpath(__DIR__ . '/../../../'); |
|
390
|
15 |
|
$config = []; |
|
391
|
15 |
|
foreach ($this->default_config as $key => $value) { |
|
392
|
15 |
|
$tmp = str_replace('{base_dir}', $base_dir, $value); |
|
393
|
15 |
|
$tmp = str_replace('{tcp_port}', $port, $tmp); |
|
394
|
15 |
|
$config[$key] = $tmp; |
|
395
|
15 |
|
} |
|
396
|
15 |
|
return $config; |
|
397
|
|
|
} |
|
398
|
|
|
|
|
399
|
|
|
/** |
|
400
|
|
|
* Check configuration parameters |
|
401
|
|
|
* @throws Exception\InvalidArgumentException |
|
402
|
|
|
* @param array $config |
|
403
|
|
|
*/ |
|
404
|
15 |
|
protected function checkConfigRequiredArgs(array $config) |
|
405
|
|
|
{ |
|
406
|
15 |
|
foreach ($this->required_arguments as $name => $type) { |
|
407
|
15 |
|
if (!isset($config[$name])) { |
|
408
|
|
|
$msg = "Missing option '$name' in Standalone server configuration"; |
|
409
|
|
|
throw new Exception\InvalidArgumentException($msg); |
|
410
|
|
|
} |
|
411
|
15 |
|
if (is_long($type) && !filter_var($config[$name], $type)) { |
|
412
|
|
|
$msg = "Unsupported type in option '$name' in Standalone server configuration (required $type)"; |
|
413
|
|
|
throw new Exception\InvalidArgumentException($msg); |
|
414
|
15 |
|
} elseif (is_string($type)) { |
|
415
|
|
|
switch ($type) { |
|
416
|
15 |
|
case 'existing_file': |
|
417
|
15 |
|
$file = $config[$name]; |
|
418
|
15 |
|
if (!file_exists($file)) { |
|
419
|
1 |
|
$msg = "The '$name' file '$file 'does not exists."; |
|
420
|
1 |
|
throw new Exception\InvalidArgumentException($msg); |
|
421
|
|
|
} |
|
422
|
14 |
|
break; |
|
423
|
15 |
|
case 'existing_directory': |
|
424
|
14 |
|
$directory = $config[$name]; |
|
425
|
14 |
|
if (!is_dir($directory)) { |
|
426
|
|
|
$msg = "The '$name' directory '$directory 'does not exists."; |
|
427
|
|
|
throw new Exception\InvalidArgumentException($msg); |
|
428
|
|
|
} |
|
429
|
14 |
|
break; |
|
430
|
|
|
|
|
431
|
15 |
|
case 'file_with_existing_directory': |
|
432
|
14 |
|
$file = $config[$name]; |
|
433
|
14 |
|
$info = pathinfo($file); |
|
434
|
14 |
|
$dirname = $info['dirname']; |
|
435
|
14 |
|
if (!is_dir($dirname) || $dirname == ".") { |
|
436
|
|
|
$msg = "Option '$name' refer to an invalid or non-existent directory ($file)"; |
|
437
|
|
|
throw new Exception\InvalidArgumentException($msg); |
|
438
|
|
|
} |
|
439
|
14 |
|
if (is_dir($file)) { |
|
440
|
|
|
$msg = "Option '$name' does not refer to a file but an existing directory ($file)"; |
|
441
|
|
|
throw new Exception\InvalidArgumentException($msg); |
|
442
|
|
|
} |
|
443
|
14 |
|
if (file_exists($file) && !is_writable($file)) { |
|
444
|
|
|
$msg = "File specified in '$name' is not writable ($file)"; |
|
445
|
|
|
throw new Exception\InvalidArgumentException($msg); |
|
446
|
|
|
} |
|
447
|
14 |
|
break; |
|
448
|
|
|
} |
|
449
|
15 |
|
} |
|
450
|
15 |
|
} |
|
451
|
14 |
|
} |
|
452
|
|
|
} |
|
453
|
|
|
|
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.