1
|
|
|
<?php |
2
|
|
|
|
3
|
|
|
namespace SwooleTW\Http\Commands; |
4
|
|
|
|
5
|
|
|
use Throwable; |
6
|
|
|
use Swoole\Process; |
7
|
|
|
use Illuminate\Support\Arr; |
8
|
|
|
use SwooleTW\Http\Helpers\OS; |
9
|
|
|
use Illuminate\Console\Command; |
10
|
|
|
use SwooleTW\Http\Server\Manager; |
11
|
|
|
use Illuminate\Console\OutputStyle; |
12
|
|
|
use SwooleTW\Http\HotReload\FSEvent; |
13
|
|
|
use SwooleTW\Http\HotReload\FSOutput; |
14
|
|
|
use SwooleTW\Http\HotReload\FSProcess; |
15
|
|
|
use SwooleTW\Http\Server\AccessOutput; |
16
|
|
|
use SwooleTW\Http\Server\PidManager; |
17
|
|
|
use SwooleTW\Http\Middleware\AccessLog; |
18
|
|
|
use SwooleTW\Http\Server\Facades\Server; |
19
|
|
|
use Illuminate\Contracts\Container\Container; |
20
|
|
|
use Symfony\Component\Console\Output\ConsoleOutput; |
21
|
|
|
|
22
|
|
|
/** |
23
|
|
|
* @codeCoverageIgnore |
24
|
|
|
*/ |
25
|
|
|
class HttpServerCommand extends Command |
26
|
|
|
{ |
27
|
|
|
/** |
28
|
|
|
* The name and signature of the console command. |
29
|
|
|
* |
30
|
|
|
* @var string |
31
|
|
|
*/ |
32
|
|
|
protected $signature = 'swoole:http {action : start|stop|restart|reload|infos}'; |
33
|
|
|
|
34
|
|
|
/** |
35
|
|
|
* The console command description. |
36
|
|
|
* |
37
|
|
|
* @var string |
38
|
|
|
*/ |
39
|
|
|
protected $description = 'Swoole HTTP Server controller.'; |
40
|
|
|
|
41
|
|
|
/** |
42
|
|
|
* The console command action. start|stop|restart|reload |
43
|
|
|
* |
44
|
|
|
* @var string |
45
|
|
|
*/ |
46
|
|
|
protected $action; |
47
|
|
|
|
48
|
|
|
/** |
49
|
|
|
* |
50
|
|
|
* The pid. |
51
|
|
|
* |
52
|
|
|
* @var int |
53
|
|
|
*/ |
54
|
|
|
protected $currentPid; |
55
|
|
|
|
56
|
|
|
/** |
57
|
|
|
* The configs for this package. |
58
|
|
|
* |
59
|
|
|
* @var array |
60
|
|
|
*/ |
61
|
|
|
protected $config; |
62
|
|
|
|
63
|
|
|
/** |
64
|
|
|
* Execute the console command. |
65
|
|
|
* |
66
|
|
|
* @return void |
67
|
|
|
*/ |
68
|
|
|
public function handle() |
69
|
|
|
{ |
70
|
|
|
$this->checkEnvironment(); |
71
|
|
|
$this->loadConfigs(); |
72
|
|
|
$this->initAction(); |
73
|
|
|
$this->hookAction(); |
74
|
|
|
$this->runAction(); |
75
|
|
|
} |
76
|
|
|
|
77
|
|
|
/** |
78
|
|
|
* Load configs. |
79
|
|
|
*/ |
80
|
|
|
protected function loadConfigs() |
81
|
|
|
{ |
82
|
|
|
$this->config = $this->laravel->make('config')->get('swoole_http'); |
83
|
|
|
} |
84
|
|
|
|
85
|
|
|
/** |
86
|
|
|
* Hook action |
87
|
|
|
*/ |
88
|
|
|
protected function hookAction() |
89
|
|
|
{ |
90
|
|
|
// custom hook task before starting server |
91
|
|
|
} |
92
|
|
|
|
93
|
|
|
/** |
94
|
|
|
* Run action. |
95
|
|
|
*/ |
96
|
|
|
protected function runAction() |
97
|
|
|
{ |
98
|
|
|
$this->{$this->action}(); |
99
|
|
|
} |
100
|
|
|
|
101
|
|
|
/** |
102
|
|
|
* Run swoole_http_server. |
103
|
|
|
*/ |
104
|
|
|
protected function start() |
105
|
|
|
{ |
106
|
|
|
if ($this->isRunning()) { |
107
|
|
|
$this->error('Failed! swoole_http_server process is already running.'); |
108
|
|
|
|
109
|
|
|
return; |
110
|
|
|
} |
111
|
|
|
|
112
|
|
|
$host = Arr::get($this->config, 'server.host'); |
113
|
|
|
$port = Arr::get($this->config, 'server.port'); |
114
|
|
|
$hotReloadEnabled = Arr::get($this->config, 'hot_reload.enabled'); |
115
|
|
|
$accessLogEnabled = Arr::get($this->config, 'server.access_log'); |
116
|
|
|
|
117
|
|
|
$this->info('Starting swoole http server...'); |
118
|
|
|
$this->info("Swoole http server started: <http://{$host}:{$port}>"); |
119
|
|
|
if ($this->isDaemon()) { |
120
|
|
|
$this->info( |
121
|
|
|
'> (You can run this command to ensure the ' . |
122
|
|
|
'swoole_http_server process is running: ps aux|grep "swoole")' |
123
|
|
|
); |
124
|
|
|
} |
125
|
|
|
|
126
|
|
|
$manager = $this->laravel->make(Manager::class); |
127
|
|
|
$server = $this->laravel->make(Server::class); |
128
|
|
|
|
129
|
|
|
if ($accessLogEnabled) { |
130
|
|
|
$this->registerAccessLog(); |
131
|
|
|
} |
132
|
|
|
|
133
|
|
|
if ($hotReloadEnabled) { |
134
|
|
|
$manager->addProcess($this->getHotReloadProcess($server)); |
135
|
|
|
} |
136
|
|
|
|
137
|
|
|
$manager->run(); |
138
|
|
|
} |
139
|
|
|
|
140
|
|
|
/** |
141
|
|
|
* Stop swoole_http_server. |
142
|
|
|
*/ |
143
|
|
|
protected function stop() |
144
|
|
|
{ |
145
|
|
|
if (! $this->isRunning()) { |
146
|
|
|
$this->error("Failed! There is no swoole_http_server process running."); |
147
|
|
|
|
148
|
|
|
return; |
149
|
|
|
} |
150
|
|
|
|
151
|
|
|
$this->info('Stopping swoole http server...'); |
152
|
|
|
|
153
|
|
|
$isRunning = $this->killProcess(SIGTERM, 15); |
154
|
|
|
|
155
|
|
|
if ($isRunning) { |
156
|
|
|
$this->error('Unable to stop the swoole_http_server process.'); |
157
|
|
|
|
158
|
|
|
return; |
159
|
|
|
} |
160
|
|
|
|
161
|
|
|
// I don't known why Swoole didn't trigger "onShutdown" after sending SIGTERM. |
162
|
|
|
// So we should manually remove the pid file. |
163
|
|
|
$this->laravel->make(PidManager::class)->delete(); |
164
|
|
|
|
165
|
|
|
$this->info('> success'); |
166
|
|
|
} |
167
|
|
|
|
168
|
|
|
/** |
169
|
|
|
* Restart swoole http server. |
170
|
|
|
*/ |
171
|
|
|
protected function restart() |
172
|
|
|
{ |
173
|
|
|
if ($this->isRunning()) { |
174
|
|
|
$this->stop(); |
175
|
|
|
} |
176
|
|
|
|
177
|
|
|
$this->start(); |
178
|
|
|
} |
179
|
|
|
|
180
|
|
|
/** |
181
|
|
|
* Reload. |
182
|
|
|
*/ |
183
|
|
|
protected function reload() |
184
|
|
|
{ |
185
|
|
|
if (! $this->isRunning()) { |
186
|
|
|
$this->error("Failed! There is no swoole_http_server process running."); |
187
|
|
|
|
188
|
|
|
return; |
189
|
|
|
} |
190
|
|
|
|
191
|
|
|
$this->info('Reloading swoole_http_server...'); |
192
|
|
|
|
193
|
|
|
if (! $this->killProcess(SIGUSR1)) { |
194
|
|
|
$this->error('> failure'); |
195
|
|
|
|
196
|
|
|
return; |
197
|
|
|
} |
198
|
|
|
|
199
|
|
|
$this->info('> success'); |
200
|
|
|
} |
201
|
|
|
|
202
|
|
|
/** |
203
|
|
|
* Display PHP and Swoole misc info. |
204
|
|
|
*/ |
205
|
|
|
protected function infos() |
206
|
|
|
{ |
207
|
|
|
$this->showInfos(); |
208
|
|
|
} |
209
|
|
|
|
210
|
|
|
/** |
211
|
|
|
* Display PHP and Swoole miscs infos. |
212
|
|
|
*/ |
213
|
|
|
protected function showInfos() |
214
|
|
|
{ |
215
|
|
|
$isRunning = $this->isRunning(); |
216
|
|
|
$host = Arr::get($this->config, 'server.host'); |
217
|
|
|
$port = Arr::get($this->config, 'server.port'); |
218
|
|
|
$reactorNum = Arr::get($this->config, 'server.options.reactor_num'); |
219
|
|
|
$workerNum = Arr::get($this->config, 'server.options.worker_num'); |
220
|
|
|
$taskWorkerNum = Arr::get($this->config, 'server.options.task_worker_num'); |
221
|
|
|
$isWebsocket = Arr::get($this->config, 'websocket.enabled'); |
222
|
|
|
|
223
|
|
|
$queueConfig = $this->laravel->make('config')->get('queue'); |
224
|
|
|
|
225
|
|
|
// lookup for set swoole driver |
226
|
|
|
$isDefinedSwooleDriver = in_array( |
227
|
|
|
'swoole', |
228
|
|
|
array_column( |
229
|
|
|
$queueConfig['connections'] ?? [], |
230
|
|
|
'driver' |
231
|
|
|
), |
232
|
|
|
true |
233
|
|
|
) || ($queueConfig['default'] ?? null) === 'swoole'; |
234
|
|
|
|
235
|
|
|
$hasTaskWorker = $isWebsocket || $isDefinedSwooleDriver; |
236
|
|
|
|
237
|
|
|
$logFile = Arr::get($this->config, 'server.options.log_file'); |
238
|
|
|
$pids = $this->laravel->make(PidManager::class)->read(); |
239
|
|
|
$masterPid = $pids['masterPid'] ?? null; |
240
|
|
|
$managerPid = $pids['managerPid'] ?? null; |
241
|
|
|
|
242
|
|
|
$table = [ |
243
|
|
|
['PHP Version', 'Version' => phpversion()], |
244
|
|
|
['Swoole Version', 'Version' => swoole_version()], |
245
|
|
|
['Laravel Version', $this->getApplication()->getVersion()], |
246
|
|
|
['Listen IP', $host], |
247
|
|
|
['Listen Port', $port], |
248
|
|
|
['Server Status', $isRunning ? 'Online' : 'Offline'], |
249
|
|
|
['Reactor Num', $reactorNum], |
250
|
|
|
['Worker Num', $workerNum], |
251
|
|
|
['Task Worker Num', $hasTaskWorker ? $taskWorkerNum : 0], |
252
|
|
|
['Websocket Mode', $isWebsocket ? 'On' : 'Off'], |
253
|
|
|
['Master PID', $isRunning ? $masterPid : 'None'], |
254
|
|
|
['Manager PID', $isRunning && $managerPid ? $managerPid : 'None'], |
255
|
|
|
['Log Path', $logFile], |
256
|
|
|
]; |
257
|
|
|
|
258
|
|
|
$this->table(['Name', 'Value'], $table); |
259
|
|
|
} |
260
|
|
|
|
261
|
|
|
/** |
262
|
|
|
* Initialize command action. |
263
|
|
|
*/ |
264
|
|
|
protected function initAction() |
265
|
|
|
{ |
266
|
|
|
$this->action = $this->argument('action'); |
|
|
|
|
267
|
|
|
|
268
|
|
|
if (! in_array($this->action, ['start', 'stop', 'restart', 'reload', 'infos'], true)) { |
269
|
|
|
$this->error( |
270
|
|
|
"Invalid argument '{$this->action}'. Expected 'start', 'stop', 'restart', 'reload' or 'infos'." |
271
|
|
|
); |
272
|
|
|
|
273
|
|
|
return; |
274
|
|
|
} |
275
|
|
|
} |
276
|
|
|
|
277
|
|
|
/** |
278
|
|
|
* @param \SwooleTW\Http\Server\Facades\Server $server |
279
|
|
|
* |
280
|
|
|
* @return \Swoole\Process |
281
|
|
|
*/ |
282
|
|
|
protected function getHotReloadProcess($server) |
283
|
|
|
{ |
284
|
|
|
$recursively = Arr::get($this->config, 'hot_reload.recursively'); |
285
|
|
|
$directory = Arr::get($this->config, 'hot_reload.directory'); |
286
|
|
|
$filter = Arr::get($this->config, 'hot_reload.filter'); |
287
|
|
|
$log = Arr::get($this->config, 'hot_reload.log'); |
288
|
|
|
|
289
|
|
|
$cb = function (FSEvent $event) use ($server, $log) { |
290
|
|
|
$log ? $this->info(FSOutput::format($event)) : null; |
291
|
|
|
$server->reload(); |
|
|
|
|
292
|
|
|
}; |
293
|
|
|
|
294
|
|
|
return (new FSProcess($filter, $recursively, $directory))->make($cb); |
|
|
|
|
295
|
|
|
} |
296
|
|
|
|
297
|
|
|
/** |
298
|
|
|
* If Swoole process is running. |
299
|
|
|
* |
300
|
|
|
* @param int $pid |
301
|
|
|
* |
302
|
|
|
* @return bool |
303
|
|
|
*/ |
304
|
|
|
public function isRunning() |
305
|
|
|
{ |
306
|
|
|
$pids = $this->laravel->make(PidManager::class)->read(); |
307
|
|
|
|
308
|
|
|
if (! count($pids)) { |
309
|
|
|
return false; |
310
|
|
|
} |
311
|
|
|
|
312
|
|
|
$masterPid = $pids['masterPid'] ?? null; |
313
|
|
|
$managerPid = $pids['managerPid'] ?? null; |
314
|
|
|
|
315
|
|
|
if ($managerPid) { |
316
|
|
|
// Swoole process mode |
317
|
|
|
return $masterPid && $managerPid && Process::kill((int) $managerPid, 0); |
318
|
|
|
} |
319
|
|
|
|
320
|
|
|
// Swoole base mode, no manager process |
321
|
|
|
return $masterPid && Process::kill((int) $masterPid, 0); |
322
|
|
|
} |
323
|
|
|
|
324
|
|
|
/** |
325
|
|
|
* Kill process. |
326
|
|
|
* |
327
|
|
|
* @param int $sig |
328
|
|
|
* @param int $wait |
329
|
|
|
* |
330
|
|
|
* @return bool |
331
|
|
|
*/ |
332
|
|
|
protected function killProcess($sig, $wait = 0) |
333
|
|
|
{ |
334
|
|
|
Process::kill( |
335
|
|
|
Arr::first($this->laravel->make(PidManager::class)->read()), |
336
|
|
|
$sig |
337
|
|
|
); |
338
|
|
|
|
339
|
|
|
if ($wait) { |
340
|
|
|
$start = time(); |
341
|
|
|
|
342
|
|
|
do { |
343
|
|
|
if (! $this->isRunning()) { |
344
|
|
|
break; |
345
|
|
|
} |
346
|
|
|
|
347
|
|
|
usleep(100000); |
348
|
|
|
} while (time() < $start + $wait); |
349
|
|
|
} |
350
|
|
|
|
351
|
|
|
return $this->isRunning(); |
352
|
|
|
} |
353
|
|
|
|
354
|
|
|
/** |
355
|
|
|
* Return daemonize config. |
356
|
|
|
*/ |
357
|
|
|
protected function isDaemon(): bool |
358
|
|
|
{ |
359
|
|
|
return Arr::get($this->config, 'server.options.daemonize', false); |
360
|
|
|
} |
361
|
|
|
|
362
|
|
|
/** |
363
|
|
|
* Check running enironment. |
364
|
|
|
*/ |
365
|
|
|
protected function checkEnvironment() |
366
|
|
|
{ |
367
|
|
|
if (OS::is(OS::WIN)) { |
368
|
|
|
$this->error('Swoole extension doesn\'t support Windows OS.'); |
369
|
|
|
|
370
|
|
|
exit(1); |
|
|
|
|
371
|
|
|
} |
372
|
|
|
|
373
|
|
|
if (! extension_loaded('swoole')) { |
374
|
|
|
$this->error('Can\'t detect Swoole extension installed.'); |
375
|
|
|
|
376
|
|
|
exit(1); |
|
|
|
|
377
|
|
|
} |
378
|
|
|
|
379
|
|
|
if (! version_compare(swoole_version(), '4.3.1', 'ge')) { |
380
|
|
|
$this->error('Your Swoole version must be higher than `4.3.1`.'); |
381
|
|
|
|
382
|
|
|
exit(1); |
|
|
|
|
383
|
|
|
} |
384
|
|
|
} |
385
|
|
|
|
386
|
|
|
/** |
387
|
|
|
* Register access log services. |
388
|
|
|
*/ |
389
|
|
|
protected function registerAccessLog() |
390
|
|
|
{ |
391
|
|
|
$this->laravel->singleton(OutputStyle::class, function () { |
392
|
|
|
return new OutputStyle($this->input, $this->output); |
393
|
|
|
}); |
394
|
|
|
|
395
|
|
|
$this->laravel->singleton(AccessOutput::class, function () { |
396
|
|
|
return new AccessOutput(new ConsoleOutput); |
397
|
|
|
}); |
398
|
|
|
|
399
|
|
|
$this->laravel->singleton(AccessLog::class, function (Container $container) { |
400
|
|
|
return new AccessLog($container->make(AccessOutput::class)); |
401
|
|
|
}); |
402
|
|
|
} |
403
|
|
|
} |
404
|
|
|
|
Our type inference engine has found a suspicous assignment of a value to a property. This check raises an issue when a value that can be of a mixed type is assigned to a property that is type hinted more strictly.
For example, imagine you have a variable
$accountId
that can either hold an Id object or false (if there is no account id yet). Your code now assigns that value to theid
property of an instance of theAccount
class. This class holds a proper account, so the id value must no longer be false.Either this assignment is in error or a type check should be added for that assignment.