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'); |
||||||||
0 ignored issues
–
show
|
|||||||||
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(); |
||||||||
0 ignored issues
–
show
The method
reload() does not exist on SwooleTW\Http\Server\Facades\Server .
(
Ignorable by Annotation
)
If this is a false-positive, you can also ignore this issue in your code via the
This check looks for calls to methods that do not seem to exist on a given type. It looks for the method on the type itself as well as in inherited classes or implemented interfaces. This is most likely a typographical error or the method has been renamed. ![]() |
|||||||||
292 | }; |
||||||||
293 | |||||||||
294 | return (new FSProcess($filter, $recursively, $directory))->make($cb); |
||||||||
0 ignored issues
–
show
It seems like
$filter can also be of type null ; however, parameter $filter of SwooleTW\Http\HotReload\FSProcess::__construct() does only seem to accept string , maybe add an additional type check?
(
Ignorable by Annotation
)
If this is a false-positive, you can also ignore this issue in your code via the
![]() It seems like
$recursively can also be of type null ; however, parameter $recursively of SwooleTW\Http\HotReload\FSProcess::__construct() does only seem to accept boolean , maybe add an additional type check?
(
Ignorable by Annotation
)
If this is a false-positive, you can also ignore this issue in your code via the
![]() It seems like
$directory can also be of type null ; however, parameter $directory of SwooleTW\Http\HotReload\FSProcess::__construct() does only seem to accept string , maybe add an additional type check?
(
Ignorable by Annotation
)
If this is a false-positive, you can also ignore this issue in your code via the
![]() |
|||||||||
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); |
||||||||
0 ignored issues
–
show
|
|||||||||
371 | } |
||||||||
372 | |||||||||
373 | if (! extension_loaded('swoole') && ! extension_loaded('openswoole')) { |
||||||||
374 | $this->error('Can\'t detect Swoole extension installed.'); |
||||||||
375 | |||||||||
376 | exit(1); |
||||||||
0 ignored issues
–
show
|
|||||||||
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); |
||||||||
0 ignored issues
–
show
|
|||||||||
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.