Passed
Push — master ( 277d4a...4e9b37 )
by Albert
06:50 queued 04:31
created

HttpServerCommand::isDaemon()   A

Complexity

Conditions 1
Paths 1

Size

Total Lines 3
Code Lines 1

Duplication

Lines 0
Ratio 0 %

Importance

Changes 0
Metric Value
cc 1
eloc 1
nc 1
nop 0
dl 0
loc 3
rs 10
c 0
b 0
f 0
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->runAction();
74
    }
75
76
    /**
77
     * Load configs.
78
     */
79
    protected function loadConfigs()
80
    {
81
        $this->config = $this->laravel->make('config')->get('swoole_http');
82
    }
83
84
    /**
85
     * Run action.
86
     */
87
    protected function runAction()
88
    {
89
        $this->{$this->action}();
90
    }
91
92
    /**
93
     * Run swoole_http_server.
94
     */
95
    protected function start()
96
    {
97
        if ($this->isRunning()) {
98
            $this->error('Failed! swoole_http_server process is already running.');
99
100
            return;
101
        }
102
103
        $host = Arr::get($this->config, 'server.host');
104
        $port = Arr::get($this->config, 'server.port');
105
        $hotReloadEnabled = Arr::get($this->config, 'hot_reload.enabled');
106
        $accessLogEnabled = Arr::get($this->config, 'server.access_log');
107
108
        $this->info('Starting swoole http server...');
109
        $this->info("Swoole http server started: <http://{$host}:{$port}>");
110
        if ($this->isDaemon()) {
111
            $this->info(
112
                '> (You can run this command to ensure the ' .
113
                'swoole_http_server process is running: ps aux|grep "swoole")'
114
            );
115
        }
116
117
        $manager = $this->laravel->make(Manager::class);
118
        $server = $this->laravel->make(Server::class);
119
120
        if ($accessLogEnabled) {
121
            $this->registerAccessLog();
122
        }
123
124
        if ($hotReloadEnabled) {
125
            $manager->addProcess($this->getHotReloadProcess($server));
126
        }
127
128
        $manager->run();
129
    }
130
131
    /**
132
     * Stop swoole_http_server.
133
     */
134
    protected function stop()
135
    {
136
        if (! $this->isRunning()) {
137
            $this->error("Failed! There is no swoole_http_server process running.");
138
139
            return;
140
        }
141
142
        $this->info('Stopping swoole http server...');
143
144
        $isRunning = $this->killProcess(SIGTERM, 15);
145
146
        if ($isRunning) {
147
            $this->error('Unable to stop the swoole_http_server process.');
148
149
            return;
150
        }
151
152
        // I don't known why Swoole didn't trigger "onShutdown" after sending SIGTERM.
153
        // So we should manually remove the pid file.
154
        $this->laravel->make(PidManager::class)->delete();
155
156
        $this->info('> success');
157
    }
158
159
    /**
160
     * Restart swoole http server.
161
     */
162
    protected function restart()
163
    {
164
        if ($this->isRunning()) {
165
            $this->stop();
166
        }
167
168
        $this->start();
169
    }
170
171
    /**
172
     * Reload.
173
     */
174
    protected function reload()
175
    {
176
        if (! $this->isRunning()) {
177
            $this->error("Failed! There is no swoole_http_server process running.");
178
179
            return;
180
        }
181
182
        $this->info('Reloading swoole_http_server...');
183
184
        if (! $this->killProcess(SIGUSR1)) {
185
            $this->error('> failure');
186
187
            return;
188
        }
189
190
        $this->info('> success');
191
    }
192
193
    /**
194
     * Display PHP and Swoole misc info.
195
     */
196
    protected function infos()
197
    {
198
        $this->showInfos();
199
    }
200
201
    /**
202
     * Display PHP and Swoole miscs infos.
203
     */
204
    protected function showInfos()
205
    {
206
        $isRunning = $this->isRunning();
207
        $host = Arr::get($this->config, 'server.host');
208
        $port = Arr::get($this->config, 'server.port');
209
        $reactorNum = Arr::get($this->config, 'server.options.reactor_num');
210
        $workerNum = Arr::get($this->config, 'server.options.worker_num');
211
        $taskWorkerNum = Arr::get($this->config, 'server.options.task_worker_num');
212
        $isWebsocket = Arr::get($this->config, 'websocket.enabled');
213
        $hasTaskWorker = $isWebsocket || Arr::get($this->config, 'queue.default') === 'swoole';
214
        $logFile = Arr::get($this->config, 'server.options.log_file');
215
        $pidManager = $this->laravel->make(PidManager::class);
216
        [$masterPid, $managerPid] = $pidManager->read();
217
218
        $table = [
219
            ['PHP Version', 'Version' => phpversion()],
220
            ['Swoole Version', 'Version' => swoole_version()],
221
            ['Laravel Version', $this->getApplication()->getVersion()],
222
            ['Listen IP', $host],
223
            ['Listen Port', $port],
224
            ['Server Status', $isRunning ? 'Online' : 'Offline'],
225
            ['Reactor Num', $reactorNum],
226
            ['Worker Num', $workerNum],
227
            ['Task Worker Num', $hasTaskWorker ? $taskWorkerNum : 0],
228
            ['Websocket Mode', $isWebsocket ? 'On' : 'Off'],
229
            ['Master PID', $isRunning ? $masterPid : 'None'],
230
            ['Manager PID', $isRunning && $managerPid ? $managerPid : 'None'],
231
            ['Log Path', $logFile],
232
        ];
233
234
        $this->table(['Name', 'Value'], $table);
235
    }
236
237
    /**
238
     * Initialize command action.
239
     */
240
    protected function initAction()
241
    {
242
        $this->action = $this->argument('action');
0 ignored issues
show
Documentation Bug introduced by
It seems like $this->argument('action') can also be of type string[]. However, the property $action is declared as type string. Maybe add an additional type check?

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 the id property of an instance of the Account 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.

class Id
{
    public $id;

    public function __construct($id)
    {
        $this->id = $id;
    }

}

class Account
{
    /** @var  Id $id */
    public $id;
}

$account_id = false;

if (starsAreRight()) {
    $account_id = new Id(42);
}

$account = new Account();
if ($account instanceof Id)
{
    $account->id = $account_id;
}
Loading history...
243
244
        if (! in_array($this->action, ['start', 'stop', 'restart', 'reload', 'infos'], true)) {
245
            $this->error(
246
                "Invalid argument '{$this->action}'. Expected 'start', 'stop', 'restart', 'reload' or 'infos'."
247
            );
248
249
            return;
250
        }
251
    }
252
253
    /**
254
     * @param \SwooleTW\Http\Server\Facades\Server $server
255
     *
256
     * @return \Swoole\Process
257
     */
258
    protected function getHotReloadProcess($server)
259
    {
260
        $recursively = Arr::get($this->config, 'hot_reload.recursively');
261
        $directory = Arr::get($this->config, 'hot_reload.directory');
262
        $filter = Arr::get($this->config, 'hot_reload.filter');
263
        $log = Arr::get($this->config, 'hot_reload.log');
264
265
        $cb = function (FSEvent $event) use ($server, $log) {
266
            $log ? $this->info(FSOutput::format($event)) : null;
267
            $server->reload();
0 ignored issues
show
Bug introduced by
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 ignore-call  annotation

267
            $server->/** @scrutinizer ignore-call */ 
268
                     reload();

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.

Loading history...
268
        };
269
270
        return (new FSProcess($filter, $recursively, $directory))->make($cb);
0 ignored issues
show
Bug introduced by
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 ignore-type  annotation

270
        return (new FSProcess($filter, $recursively, /** @scrutinizer ignore-type */ $directory))->make($cb);
Loading history...
Bug introduced by
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 ignore-type  annotation

270
        return (new FSProcess(/** @scrutinizer ignore-type */ $filter, $recursively, $directory))->make($cb);
Loading history...
Bug introduced by
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 ignore-type  annotation

270
        return (new FSProcess($filter, /** @scrutinizer ignore-type */ $recursively, $directory))->make($cb);
Loading history...
271
    }
272
273
    /**
274
     * If Swoole process is running.
275
     *
276
     * @param int $pid
277
     *
278
     * @return bool
279
     */
280
    public function isRunning()
281
    {
282
        $pids = $this->laravel->make(PidManager::class)->read();
283
284
        if (! count($pids)) {
285
            return false;
286
        }
287
288
        [$masterPid, $managerPid] = $pids;
289
290
        if ($managerPid) {
291
            // Swoole process mode
292
            return $masterPid && $managerPid && Process::kill((int) $managerPid, 0);
293
        }
294
295
        // Swoole base mode, no manager process
296
        return $masterPid && Process::kill((int) $masterPid, 0);
297
    }
298
299
    /**
300
     * Kill process.
301
     *
302
     * @param int $sig
303
     * @param int $wait
304
     *
305
     * @return bool
306
     */
307
    protected function killProcess($sig, $wait = 0)
308
    {
309
        Process::kill(
310
            Arr::first($this->laravel->make(PidManager::class)->read()),
311
            $sig
312
        );
313
314
        if ($wait) {
315
            $start = time();
316
317
            do {
318
                if (! $this->isRunning()) {
319
                    break;
320
                }
321
322
                usleep(100000);
323
            } while (time() < $start + $wait);
324
        }
325
326
        return $this->isRunning();
327
    }
328
329
    /**
330
     * Return daemonize config.
331
     */
332
    protected function isDaemon(): bool
333
    {
334
        return Arr::get($this->config, 'server.options.daemonize', false);
335
    }
336
337
    /**
338
     * Check running enironment.
339
     */
340
    protected function checkEnvironment()
341
    {
342
        if (OS::is(OS::WIN)) {
343
            $this->error('Swoole extension doesn\'t support Windows OS.');
344
345
            exit(1);
0 ignored issues
show
Best Practice introduced by
Using exit here is not recommended.

In general, usage of exit should be done with care and only when running in a scripting context like a CLI script.

Loading history...
346
        }
347
348
        if (! extension_loaded('swoole')) {
349
            $this->error('Can\'t detect Swoole extension installed.');
350
351
            exit(1);
0 ignored issues
show
Best Practice introduced by
Using exit here is not recommended.

In general, usage of exit should be done with care and only when running in a scripting context like a CLI script.

Loading history...
352
        }
353
354
        if (! version_compare(swoole_version(), '4.3.1', 'ge')) {
355
            $this->error('Your Swoole version must be higher than `4.3.1`.');
356
357
            exit(1);
0 ignored issues
show
Best Practice introduced by
Using exit here is not recommended.

In general, usage of exit should be done with care and only when running in a scripting context like a CLI script.

Loading history...
358
        }
359
    }
360
361
    /**
362
     * Register access log services.
363
     */
364
    protected function registerAccessLog()
365
    {
366
        $this->laravel->singleton(OutputStyle::class, function () {
367
            return new OutputStyle($this->input, $this->output);
368
        });
369
370
        $this->laravel->singleton(AccessOutput::class, function () {
371
            return new AccessOutput(new ConsoleOutput);
372
        });
373
374
        $this->laravel->singleton(AccessLog::class, function (Container $container) {
375
            return new AccessLog($container->make(AccessOutput::class));
376
        });
377
    }
378
}
379