Passed
Pull Request — master (#522)
by
unknown
04:48
created

Manager::clearCache()   A

Complexity

Conditions 3
Paths 4

Size

Total Lines 8
Code Lines 4

Duplication

Lines 0
Ratio 0 %

Importance

Changes 0
Metric Value
cc 3
eloc 4
nc 4
nop 0
dl 0
loc 8
rs 10
c 0
b 0
f 0
1
<?php
2
3
namespace SwooleTW\Http\Server;
4
5
use Exception;
6
use Throwable;
7
use Swoole\Process;
8
use Swoole\Server\Task;
9
use Illuminate\Support\Str;
10
use SwooleTW\Http\Helpers\OS;
11
use SwooleTW\Http\Server\Sandbox;
12
use SwooleTW\Http\Server\PidManager;
13
use SwooleTW\Http\Task\SwooleTaskJob;
14
use Illuminate\Support\Facades\Facade;
15
use SwooleTW\Http\Websocket\Websocket;
16
use SwooleTW\Http\Transformers\Request;
17
use SwooleTW\Http\Server\Facades\Server;
18
use SwooleTW\Http\Transformers\Response;
19
use SwooleTW\Http\Concerns\WithApplication;
20
use Illuminate\Contracts\Container\Container;
21
use Illuminate\Contracts\Debug\ExceptionHandler;
22
use SwooleTW\Http\Concerns\InteractsWithWebsocket;
23
use Symfony\Component\Console\Output\ConsoleOutput;
24
use SwooleTW\Http\Concerns\InteractsWithSwooleQueue;
25
use SwooleTW\Http\Concerns\InteractsWithSwooleTable;
26
use Symfony\Component\ErrorHandler\Error\FatalError;
27
28
/**
29
 * Class Manager
30
 */
31
class Manager
32
{
33
    use InteractsWithWebsocket,
0 ignored issues
show
Bug introduced by
The trait SwooleTW\Http\Concerns\InteractsWithWebsocket requires the property $fd which is not provided by SwooleTW\Http\Server\Manager.
Loading history...
34
        InteractsWithSwooleTable,
35
        InteractsWithSwooleQueue,
36
        WithApplication;
37
38
    /**
39
     * Container.
40
     *
41
     * @var \Illuminate\Contracts\Container\Container
42
     */
43
    protected $container;
44
45
    /**
46
     * @var string
47
     */
48
    protected $framework;
49
50
    /**
51
     * @var string
52
     */
53
    protected $basePath;
54
55
    /**
56
     * Server events.
57
     *
58
     * @var array
59
     */
60
    protected $events = [
61
        'start',
62
        'shutDown',
63
        'workerStart',
64
        'workerStop',
65
        'packet',
66
        'bufferFull',
67
        'bufferEmpty',
68
        'task',
69
        'finish',
70
        'pipeMessage',
71
        'workerError',
72
        'managerStart',
73
        'managerStop',
74
        'request',
75
    ];
76
77
    /**
78
     * HTTP server manager constructor.
79
     *
80
     * @param \Illuminate\Contracts\Container\Container $container
81
     * @param string $framework
82
     * @param string $basePath
83
     *
84
     * @throws \Exception
85
     */
86
    public function __construct(Container $container, $framework, $basePath = null)
87
    {
88
        $this->container = $container;
89
        $this->setFramework($framework);
90
        $this->setBasepath($basePath);
91
        $this->initialize();
92
    }
93
94
    /**
95
     * Run swoole server.
96
     */
97
    public function run()
98
    {
99
        $this->container->make(Server::class)->start();
0 ignored issues
show
Bug introduced by
The method start() 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

99
        $this->container->make(Server::class)->/** @scrutinizer ignore-call */ start();

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...
100
    }
101
102
    /**
103
     * Stop swoole server.
104
     */
105
    public function stop()
106
    {
107
        $this->container->make(Server::class)->shutdown();
0 ignored issues
show
Bug introduced by
The method shutdown() 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

107
        $this->container->make(Server::class)->/** @scrutinizer ignore-call */ shutdown();

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...
108
    }
109
110
    /**
111
     * Initialize.
112
     */
113
    protected function initialize()
114
    {
115
        $this->createTables();
116
        $this->prepareWebsocket();
117
118
        if (! $this->container->make(Server::class)->taskworker) {
0 ignored issues
show
Bug introduced by
The property taskworker does not seem to exist on SwooleTW\Http\Server\Facades\Server.
Loading history...
119
            $this->setSwooleServerListeners();
120
        }
121
    }
122
123
    /**
124
     * Set swoole server listeners.
125
     */
126
    protected function setSwooleServerListeners()
127
    {
128
        $server = $this->container->make(Server::class);
129
        foreach ($this->events as $event) {
130
            $listener = Str::camel("on_$event");
131
            $callback = method_exists($this, $listener) ? [$this, $listener] : function () use ($event) {
132
                $this->container->make('events')->dispatch("swoole.$event", func_get_args());
0 ignored issues
show
Bug introduced by
The method dispatch() does not exist on Mockery\LegacyMockInterface. It seems like you code against a sub-type of Mockery\LegacyMockInterface such as Mockery\Mock. ( Ignorable by Annotation )

If this is a false-positive, you can also ignore this issue in your code via the ignore-call  annotation

132
                $this->container->make('events')->/** @scrutinizer ignore-call */ dispatch("swoole.$event", func_get_args());
Loading history...
Bug introduced by
The method dispatch() does not exist on Mockery\Expectation. ( Ignorable by Annotation )

If this is a false-positive, you can also ignore this issue in your code via the ignore-call  annotation

132
                $this->container->make('events')->/** @scrutinizer ignore-call */ dispatch("swoole.$event", func_get_args());

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...
133
            };
134
135
            $server->on($event, $callback);
0 ignored issues
show
Bug introduced by
The method on() 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

135
            $server->/** @scrutinizer ignore-call */ 
136
                     on($event, $callback);

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...
136
        }
137
    }
138
139
    /**
140
     * "onStart" listener.
141
     */
142
    public function onStart()
143
    {
144
        $this->setProcessName('master process');
145
146
        $server = $this->container->make(Server::class);
147
        $this->container->make(PidManager::class)->write($server->master_pid, $server->manager_pid ?? 0);
0 ignored issues
show
Bug introduced by
The property manager_pid does not seem to exist on SwooleTW\Http\Server\Facades\Server.
Loading history...
Bug introduced by
The property master_pid does not seem to exist on SwooleTW\Http\Server\Facades\Server.
Loading history...
148
149
        $this->container->make('events')->dispatch('swoole.start', func_get_args());
150
    }
151
152
    /**
153
     * The listener of "managerStart" event.
154
     *
155
     * @return void
156
     */
157
    public function onManagerStart()
158
    {
159
        $this->setProcessName('manager process');
160
161
        $this->container->make('events')->dispatch('swoole.managerStart', func_get_args());
162
    }
163
164
    /**
165
     * "onWorkerStart" listener.
166
     *
167
     * @param \Swoole\Http\Server|mixed $server
168
     *
169
     * @throws \Exception
170
     */
171
    public function onWorkerStart($server)
172
    {
173
        $this->clearCache();
174
175
        $this->container->make('events')->dispatch('swoole.workerStart', func_get_args());
176
177
        $this->setProcessName($server->taskworker ? 'task process' : 'worker process');
178
179
        // clear events instance in case of repeated listeners in worker process
180
        Facade::clearResolvedInstance('events');
181
182
        // prepare laravel app
183
        $this->getApplication();
184
185
        // bind after setting laravel app
186
        $this->bindToLaravelApp();
187
188
        // prepare websocket handler and routes
189
        if ($this->isServerWebsocket) {
190
            $this->prepareWebsocketHandler();
191
            $this->loadWebsocketRoutes();
192
        }
193
    }
194
195
    /**
196
     * "onRequest" listener.
197
     *
198
     * @param \Swoole\Http\Request $swooleRequest
199
     * @param \Swoole\Http\Response $swooleResponse
200
     */
201
    public function onRequest($swooleRequest, $swooleResponse)
202
    {
203
        $this->app->make('events')->dispatch('swoole.request');
204
205
        $this->resetOnRequest();
206
        $sandbox = $this->app->make(Sandbox::class);
207
        $handleStatic = $this->container->make('config')->get('swoole_http.server.handle_static_files', true);
208
        $publicPath = $this->container->make('config')->get('swoole_http.server.public_path', base_path('public'));
209
210
        try {
211
            // handle static file request first
212
            if ($handleStatic && Request::handleStatic($swooleRequest, $swooleResponse, $publicPath)) {
213
                return;
214
            }
215
            // transform swoole request to illuminate request
216
            $illuminateRequest = Request::make($swooleRequest)->toIlluminate();
217
218
            if (!$sandbox->isLaravel()) { // is lumen app
219
                $illuminateRequest = LumenRequest::createFromBase($illuminateRequest);
0 ignored issues
show
Bug introduced by
The type SwooleTW\Http\Server\LumenRequest was not found. Maybe you did not declare it correctly or list all dependencies?

The issue could also be caused by a filter entry in the build configuration. If the path has been excluded in your configuration, e.g. excluded_paths: ["lib/*"], you can move it to the dependency path list as follows:

filter:
    dependency_paths: ["lib/*"]

For further information see https://scrutinizer-ci.com/docs/tools/php/php-scrutinizer/#list-dependency-paths

Loading history...
220
            }
221
222
            // set current request to sandbox
223
            $sandbox->setRequest($illuminateRequest);
224
225
            // enable sandbox
226
            $sandbox->enable();
227
228
            // handle request via laravel/lumen's dispatcher
229
            $illuminateResponse = $sandbox->run($illuminateRequest);
230
231
            // send response
232
            Response::make($illuminateResponse, $swooleResponse, $swooleRequest)->send();
233
        } catch (Throwable $e) {
234
            try {
235
                $exceptionResponse = $this->app
236
                    ->make(ExceptionHandler::class)
237
                    ->render(
238
                        $illuminateRequest,
0 ignored issues
show
Comprehensibility Best Practice introduced by
The variable $illuminateRequest does not seem to be defined for all execution paths leading up to this point.
Loading history...
239
                        $this->normalizeException($e)
240
                    );
241
                Response::make($exceptionResponse, $swooleResponse, $swooleRequest)->send();
242
            } catch (Throwable $e) {
243
                $this->logServerError($e);
244
            }
245
        } finally {
246
            // disable and recycle sandbox resource
247
            $sandbox->disable();
248
        }
249
    }
250
251
    /**
252
     * Reset on every request.
253
     */
254
    protected function resetOnRequest()
255
    {
256
        // Reset websocket data
257
        if ($this->isServerWebsocket) {
258
            $this->app->make(Websocket::class)->reset(true);
259
        }
260
    }
261
262
    /**
263
     * Set onTask listener.
264
     *
265
     * @param mixed $server
266
     * @param string|\Swoole\Server\Task $taskId or $task
267
     * @param string|null $srcWorkerId
268
     * @param mixed|null $data
269
     */
270
    public function onTask($server, $task, $srcWorkerId = null, $data = null)
271
    {
272
        if ($task instanceof Task) {
273
            $data = $task->data;
274
            $srcWorkerId = $task->worker_id;
275
            $taskId = $task->id;
276
        } else {
277
            $taskId = $task;
278
        }
279
280
        $this->container->make('events')->dispatch('swoole.task', func_get_args());
281
282
        try {
283
            // push websocket message
284
            if ($this->isWebsocketPushPayload($data)) {
285
                $this->pushMessage($server, $data['data']);
286
            // push async task to queue
287
            } elseif ($this->isAsyncTaskPayload($data)) {
288
                (new SwooleTaskJob($this->container, $server, $data, $taskId, $srcWorkerId))->fire();
289
            }
290
        } catch (Throwable $e) {
291
            $this->logServerError($e);
292
        }
293
    }
294
295
    /**
296
     * Set onFinish listener.
297
     *
298
     * @param mixed $server
299
     * @param string $taskId
300
     * @param mixed $data
301
     */
302
    public function onFinish($server, $taskId, $data)
303
    {
304
        // task worker callback
305
        $this->container->make('events')->dispatch('swoole.finish', func_get_args());
306
307
        return;
308
    }
309
310
    /**
311
     * Set onShutdown listener.
312
     */
313
    public function onShutdown()
314
    {
315
        $this->container->make(PidManager::class)->delete();
316
    }
317
318
    /**
319
     * Set bindings to Laravel app.
320
     */
321
    protected function bindToLaravelApp()
322
    {
323
        $this->bindSandbox();
324
        $this->bindSwooleTable();
325
326
        if ($this->isServerWebsocket) {
327
            $this->bindRoom();
328
            $this->bindWebsocket();
329
        }
330
    }
331
332
    /**
333
     * Bind sandbox to Laravel app container.
334
     */
335
    protected function bindSandbox()
336
    {
337
        $this->app->singleton(Sandbox::class, function ($app) {
338
            return new Sandbox($app, $this->framework);
339
        });
340
341
        $this->app->alias(Sandbox::class, 'swoole.sandbox');
342
    }
343
344
    /**
345
     * Clear APC or OPCache.
346
     */
347
    protected function clearCache()
348
    {
349
        if (extension_loaded('apc')) {
350
            apc_clear_cache();
351
        }
352
353
        if (extension_loaded('Zend OPcache')) {
354
            opcache_reset();
355
        }
356
    }
357
358
    /**
359
     * Set process name.
360
     *
361
     * @codeCoverageIgnore
362
     *
363
     * @param $process
364
     */
365
    protected function setProcessName($process)
366
    {
367
        // MacOS doesn't support modifying process name.
368
        if (OS::is(OS::MAC_OS, OS::CYGWIN) || $this->isInTesting()) {
369
            return;
370
        }
371
        $serverName = 'swoole_http_server';
372
        $appName = $this->container->make('config')->get('app.name', 'Laravel');
373
374
        $name = sprintf('%s: %s for %s', $serverName, $process, $appName);
375
376
        swoole_set_process_name($name);
377
    }
378
379
    /**
380
     * Add process to http server
381
     *
382
     * @param \Swoole\Process $process
383
     */
384
    public function addProcess(Process $process): void
385
    {
386
        $this->container->make(Server::class)->addProcess($process);
0 ignored issues
show
Bug introduced by
The method addProcess() 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

386
        $this->container->make(Server::class)->/** @scrutinizer ignore-call */ addProcess($process);

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...
387
    }
388
389
    /**
390
     * Indicates if it's in phpunit environment.
391
     *
392
     * @return bool
393
     */
394
    protected function isInTesting()
395
    {
396
        return defined('IN_PHPUNIT') && IN_PHPUNIT;
0 ignored issues
show
Bug introduced by
The constant SwooleTW\Http\Server\IN_PHPUNIT was not found. Maybe you did not declare it correctly or list all dependencies?
Loading history...
397
    }
398
399
    /**
400
     * Log server error.
401
     *
402
     * @param \Throwable|\Exception $e
403
     */
404
    public function logServerError(Throwable $e)
405
    {
406
        if ($this->isInTesting()) {
407
            return;
408
        }
409
410
        $exception = $this->normalizeException($e);
411
        $this->container->make(ConsoleOutput::class)
412
            ->writeln(sprintf("<error>%s</error>", $exception));
413
414
        $this->container->make(ExceptionHandler::class)
415
            ->report($exception);
416
    }
417
418
    /**
419
     * Normalize a throwable/exception to exception.
420
     *
421
     * @param \Throwable|\Exception $e
422
     */
423
    protected function normalizeException(Throwable $e)
424
    {
425
        if (! $e instanceof Exception) {
426
            if ($e instanceof \ParseError) {
427
                $severity = E_PARSE;
428
            } elseif ($e instanceof \TypeError) {
429
                $severity = E_RECOVERABLE_ERROR;
430
            } else {
431
                $severity = E_ERROR;
432
            }
433
434
            $error = [
435
                'type' => $severity,
436
                'message' => $e->getMessage(),
437
                'file' => $e->getFile(),
438
                'line' => $e->getLine(),
439
            ];
440
441
            $e = new FatalError($e->getMessage(), $e->getCode(), $error, null, true, $e->getTrace());
442
        }
443
444
        return $e;
445
    }
446
447
    /**
448
     * Indicates if the payload is async task.
449
     *
450
     * @param mixed $payload
451
     *
452
     * @return boolean
453
     */
454
    protected function isAsyncTaskPayload($payload): bool
455
    {
456
        $data = json_decode($payload, true);
457
458
        if (JSON_ERROR_NONE !== json_last_error()) {
459
            return false;
460
        }
461
462
        return isset($data['job']);
463
    }
464
}
465