Passed
Pull Request — master (#132)
by
unknown
02:31
created

Manager::getServer()   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
c 0
b 0
f 0
nc 1
nop 0
dl 0
loc 3
rs 10
1
<?php
2
3
namespace SwooleTW\Http\Server;
4
5
use Exception;
6
use SuperClosure\Serializer;
7
use SwooleTW\Http\Server\Sandbox;
8
use Swoole\Http\Server as HttpServer;
9
use SwooleTW\Http\Task\CanSwooleTask;
10
use Illuminate\Support\Facades\Facade;
11
use SwooleTW\Http\Websocket\Websocket;
12
use SwooleTW\Http\Table\CanSwooleTable;
13
use SwooleTW\Http\Websocket\CanWebsocket;
14
use Illuminate\Contracts\Container\Container;
15
use Swoole\WebSocket\Server as WebSocketServer;
16
use SwooleTW\Http\Websocket\Rooms\RoomContract;
17
use Illuminate\Contracts\Debug\ExceptionHandler;
18
19
class Manager
20
{
21
    use CanWebsocket, CanSwooleTable, CanSwooleTask;
0 ignored issues
show
introduced by
The trait SwooleTW\Http\Websocket\CanWebsocket requires some properties which are not provided by SwooleTW\Http\Server\Manager: $data, $fd, $connections
Loading history...
22
23
    const MAC_OSX = 'Darwin';
24
25
    /**
26
     * @var \Swoole\Http\Server | \Swoole\Websocket\Server
27
     */
28
    protected $server;
29
30
    /**
31
     * Container.
32
     *
33
     * @var \Illuminate\Contracts\Container\Container
34
     */
35
    protected $container;
36
37
    /**
38
     * @var \SwooleTW\Http\Server\Application
39
     */
40
    protected $application;
41
42
    /**
43
     * Laravel|Lumen Application.
44
     *
45
     * @var \Illuminate\Container\Container
46
     */
47
    protected $app;
48
49
    /**
50
     * @var string
51
     */
52
    protected $framework;
53
54
    /**
55
     * @var string
56
     */
57
    protected $basePath;
58
59
    /**
60
     * @var \SwooleTW\Http\Server\Sandbox
61
     */
62
    protected $sandbox;
63
64
    /**
65
     * Server events.
66
     *
67
     * @var array
68
     */
69
    protected $events = [
70
        'start', 'shutDown', 'workerStart', 'workerStop', 'packet',
71
        'bufferFull', 'bufferEmpty', 'task', 'finish', 'pipeMessage',
72
        'workerError', 'managerStart', 'managerStop', 'request',
73
    ];
74
75
    /**
76
     * HTTP server manager constructor.
77
     *
78
     * @param \Illuminate\Contracts\Container\Container $container
79
     * @param string $framework
80
     * @param string $basePath
81
     */
82
    public function __construct(Container $container, $framework, $basePath = null)
83
    {
84
        $this->container = $container;
85
        $this->framework = $framework;
86
        $this->basePath = $basePath;
87
88
        $this->initialize();
89
    }
90
91
    /**
92
     * Run swoole server.
93
     */
94
    public function run()
95
    {
96
        $this->server->start();
97
    }
98
99
    /**
100
     * Stop swoole server.
101
     */
102
    public function stop()
103
    {
104
        $this->server->shutdown();
105
    }
106
107
    /**
108
     * Initialize.
109
     */
110
    protected function initialize()
111
    {
112
        $this->setProcessName('manager process');
113
114
        $this->createTables();
115
116
        $this->prepareWebsocket();
117
        $this->createSwooleServer();
118
        $this->configureSwooleServer();
119
        $this->setSwooleServerListeners();
120
    }
121
122
    /**
123
     * Prepare settings if websocket is enabled.
124
     */
125
    protected function prepareWebsocket()
126
    {
127
        $isWebsocket = $this->container['config']->get('swoole_http.websocket.enabled');
128
        $parser = $this->container['config']->get('swoole_websocket.parser');
129
130
        if ($isWebsocket) {
131
            array_push($this->events, ...$this->wsEvents);
132
            $this->isWebsocket = true;
133
            $this->setParser(new $parser);
134
            $this->setWebsocketRoom();
135
        }
136
    }
137
138
    /**
139
     * Create swoole server.
140
     */
141
    protected function createSwooleServer()
142
    {
143
        $server = $this->isWebsocket ? WebsocketServer::class : HttpServer::class;
144
        $host = $this->container['config']->get('swoole_http.server.host');
145
        $port = $this->container['config']->get('swoole_http.server.port');
146
        $hasCert = $this->container['config']->get('swoole_http.server.options.ssl_cert_file');
147
        $hasKey = $this->container['config']->get('swoole_http.server.options.ssl_key_file');
148
        $args = $hasCert && $hasKey ? [SWOOLE_PROCESS, SWOOLE_SOCK_TCP | SWOOLE_SSL] : [];
149
150
        $this->server = new $server($host, $port, ...$args);
151
    }
152
153
    /**
154
     * Set swoole server configurations.
155
     */
156
    protected function configureSwooleServer()
157
    {
158
        $config = $this->container['config']->get('swoole_http.server.options');
159
160
        // only enable task worker in websocket mode
161
        if (! $this->isWebsocket) {
162
            unset($config['task_worker_num']);
163
        }
164
165
        $this->server->set($config);
166
    }
167
168
    /**
169
     * Set swoole server listeners.
170
     */
171
    protected function setSwooleServerListeners()
172
    {
173
        foreach ($this->events as $event) {
174
            $listener = 'on' . ucfirst($event);
175
176
            if (method_exists($this, $listener)) {
177
                $this->server->on($event, [$this, $listener]);
178
            } else {
179
                $this->server->on($event, function () use ($event) {
180
                    $event = sprintf('swoole.%s', $event);
181
182
                    $this->container['events']->fire($event, func_get_args());
183
                });
184
            }
185
        }
186
    }
187
188
    /**
189
     * "onStart" listener.
190
     */
191
    public function onStart()
192
    {
193
        $this->setProcessName('master process');
194
        $this->createPidFile();
195
196
        $this->container['events']->fire('swoole.start', func_get_args());
197
    }
198
199
    /**
200
     * "onWorkerStart" listener.
201
     */
202
    public function onWorkerStart(HttpServer $server)
203
    {
204
        $this->clearCache();
205
        $this->setProcessName('worker process');
206
207
        $this->container['events']->fire('swoole.workerStart', func_get_args());
208
209
        // don't init laravel app in task workers
210
        if ($server->taskworker) {
211
            return;
212
        }
213
214
        // clear events instance in case of repeated listeners in worker process
215
        Facade::clearResolvedInstance('events');
216
217
        // initialize laravel app
218
        $this->createApplication();
219
        $this->setLaravelApp();
220
221
        // bind after setting laravel app
222
        $this->bindToLaravelApp();
223
224
        // set application to sandbox environment
225
        $this->sandbox = Sandbox::make($this->getApplication());
226
227
        // load websocket handlers after binding websocket to laravel app
228
        if ($this->isWebsocket) {
229
            $this->setWebsocketHandler();
230
            $this->loadWebsocketRoutes();
231
        }
232
    }
233
234
    /**
235
     * "onRequest" listener.
236
     *
237
     * @param \Swoole\Http\Request $swooleRequest
238
     * @param \Swoole\Http\Response $swooleResponse
239
     */
240
    public function onRequest($swooleRequest, $swooleResponse)
241
    {
242
        $this->app['events']->fire('swoole.request');
243
244
        $this->resetOnRequest();
245
246
        $handleStatic = $this->container['config']->get('swoole_http.handle_static_files', true);
247
248
        try {
249
            // transform swoole request to illuminate request
250
            $illuminateRequest = Request::make($swooleRequest)->toIlluminate();
251
252
            // handle static file request first
253
            if ($handleStatic && $this->handleStaticRequest($illuminateRequest, $swooleResponse)) {
254
                return;
255
            }
256
257
            // set current request to sandbox
258
            $this->sandbox->setRequest($illuminateRequest);
259
260
            // enable sandbox
261
            $this->sandbox->enable();
262
            $application = $this->sandbox->getApplication();
263
264
            // handle request via laravel/lumen's dispatcher
265
            $illuminateResponse = $application->run($illuminateRequest);
266
            $response = Response::make($illuminateResponse, $swooleResponse);
267
            $response->send();
268
        } catch (Exception $e) {
269
            try {
270
                $exceptionResponse = $this->app[ExceptionHandler::class]->render($illuminateRequest, $e);
271
                $response = Response::make($exceptionResponse, $swooleResponse);
272
                $response->send();
273
            } catch (Exception $e) {
274
                $this->logServerError($e);
275
            }
276
        } finally {
277
            // disable and recycle sandbox resource
278
            $this->sandbox->disable();
279
        }
280
    }
281
282
    /**
283
     * Handle static file request.
284
     *
285
     * @param \Illuminate\Http\Request $illuminateRequest
286
     * @param \Swoole\Http\Response $swooleResponse
287
     * @return boolean
288
     */
289
    protected function handleStaticRequest($illuminateRequest, $swooleResponse)
290
    {
291
        $uri = $illuminateRequest->getRequestUri();
292
        $blackList = ['php', 'htaccess', 'config'];
293
        $extension = substr(strrchr($uri, '.'), 1);
294
        if ($extension && in_array($extension, $blackList)) {
295
            return;
296
        }
297
298
        $publicPath = $this->container['config']->get('swoole_http.server.public_path', base_path('public'));
299
        $filename = $publicPath . $uri;
300
301
        if (! is_file($filename) || filesize($filename) === 0) {
302
            return;
303
        }
304
305
        $swooleResponse->status(200);
306
        $mime = mime_content_type($filename);
307
        if ($extension === 'js') {
308
            $mime = 'text/javascript';
309
        } elseif ($extension === 'css') {
310
            $mime = 'text/css';
311
        }
312
        $swooleResponse->header('Content-Type', $mime);
313
        $swooleResponse->sendfile($filename);
314
315
        return true;
316
    }
317
318
    /**
319
     * Reset on every request.
320
     */
321
    protected function resetOnRequest()
322
    {
323
        // Reset websocket data
324
        if ($this->isWebsocket) {
325
            $this->websocket->reset(true);
326
        }
327
    }
328
329
    /**
330
     * Set onTask listener.
331
     */
332
    public function onTask(HttpServer $server, $taskId, $srcWorkerId, $data)
333
    {
334
        $this->container['events']->fire('swoole.task', func_get_args());
335
336
        try {
337
            if ($this->isWebsocket) {
338
                // push websocket message
339
                if (is_array($data)
340
                    && array_key_exists('action', $data)
341
                    && $data['action'] === Websocket::PUSH_ACTION
342
                ) {
343
                    $this->pushMessage($server, $data['data'] ?? []);
344
                } else {
345
                    $unserializedData = str_contains($data, 'SuperClosure') ? (new Serializer)->unserialize($data) : unserialize($data);
346
347
                    $this->server->finish($unserializedData());
348
                }
349
            }
350
        } catch (Exception $e) {
351
            $this->logServerError($e);
352
        }
353
    }
354
355
    /**
356
     * Set onFinish listener.
357
     */
358
    public function onFinish(HttpServer $server, $taskId, $data)
0 ignored issues
show
Unused Code introduced by
The parameter $taskId is not used and could be removed. ( Ignorable by Annotation )

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

358
    public function onFinish(HttpServer $server, /** @scrutinizer ignore-unused */ $taskId, $data)

This check looks for parameters that have been defined for a function or method, but which are not used in the method body.

Loading history...
Unused Code introduced by
The parameter $data is not used and could be removed. ( Ignorable by Annotation )

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

358
    public function onFinish(HttpServer $server, $taskId, /** @scrutinizer ignore-unused */ $data)

This check looks for parameters that have been defined for a function or method, but which are not used in the method body.

Loading history...
Unused Code introduced by
The parameter $server is not used and could be removed. ( Ignorable by Annotation )

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

358
    public function onFinish(/** @scrutinizer ignore-unused */ HttpServer $server, $taskId, $data)

This check looks for parameters that have been defined for a function or method, but which are not used in the method body.

Loading history...
359
    {
360
        // dump($taskId, $data);
361
    }
362
363
    /**
364
     * Set onShutdown listener.
365
     */
366
    public function onShutdown()
367
    {
368
        $this->removePidFile();
369
    }
370
371
    /**
372
     * Create application.
373
     */
374
    protected function createApplication()
375
    {
376
        return $this->application = Application::make($this->framework, $this->basePath);
377
    }
378
379
    /**
380
     * Get application.
381
     *
382
     * @return \SwooleTW\Http\Server\Application
383
     */
384
    protected function getApplication()
385
    {
386
        if (! $this->application instanceof Application) {
0 ignored issues
show
introduced by
$this->application is always a sub-type of SwooleTW\Http\Server\Application. If $this->application can have other possible types, add them to src/Server/Manager.php:38.
Loading history...
387
            $this->createApplication();
388
        }
389
390
        return $this->application;
391
    }
392
393
    /**
394
     * Set bindings to Laravel app.
395
     */
396
    protected function bindToLaravelApp()
397
    {
398
        $this->bindSwooleServer();
399
        $this->bindSwooleTable();
400
        $this->bindSwooleTask();
401
402
        if ($this->isWebsocket) {
403
            $this->bindRoom();
404
            $this->bindWebsocket();
405
        }
406
    }
407
408
    /**
409
     * Set isSandbox config.
410
     */
411
    protected function setIsSandbox()
412
    {
413
        $this->isSandbox = $this->container['config']->get('swoole_http.sandbox_mode', false);
0 ignored issues
show
Bug Best Practice introduced by
The property isSandbox does not exist. Although not strictly required by PHP, it is generally a best practice to declare properties explicitly.
Loading history...
414
    }
415
416
    /**
417
     * Set Laravel app.
418
     */
419
    protected function setLaravelApp()
420
    {
421
        $this->app = $this->getApplication()->getApplication();
422
    }
423
424
    /**
425
     * Bind swoole server to Laravel app container.
426
     */
427
    protected function bindSwooleServer()
428
    {
429
        $this->app->singleton('swoole.server', function () {
430
            return $this->server;
431
        });
432
    }
433
434
    /**
435
     * Gets pid file path.
436
     *
437
     * @return string
438
     */
439
    protected function getPidFile()
440
    {
441
        return $this->container['config']->get('swoole_http.server.options.pid_file');
442
    }
443
444
    /**
445
     * Create pid file.
446
     */
447
    protected function createPidFile()
448
    {
449
        $pidFile = $this->getPidFile();
450
        $pid = $this->server->master_pid;
451
452
        file_put_contents($pidFile, $pid);
453
    }
454
455
    /**
456
     * Remove pid file.
457
     */
458
    protected function removePidFile()
459
    {
460
        $pidFile = $this->getPidFile();
461
462
        if (file_exists($pidFile)) {
463
            unlink($pidFile);
464
        }
465
    }
466
467
    /**
468
     * Clear APC or OPCache.
469
     */
470
    protected function clearCache()
471
    {
472
        if (function_exists('apc_clear_cache')) {
473
            apc_clear_cache();
474
        }
475
476
        if (function_exists('opcache_reset')) {
477
            opcache_reset();
478
        }
479
    }
480
481
    /**
482
     * Set process name.
483
     *
484
     * @param $process
485
     */
486
    protected function setProcessName($process)
487
    {
488
        if (PHP_OS === static::MAC_OSX) {
489
            return;
490
        }
491
        $serverName = 'swoole_http_server';
492
        $appName = $this->container['config']->get('app.name', 'Laravel');
493
494
        $name = sprintf('%s: %s for %s', $serverName, $process, $appName);
495
496
        swoole_set_process_name($name);
497
    }
498
499
    /**
500
     * Log server error.
501
     *
502
     * @param Exception
503
     */
504
    protected function logServerError(Exception $e)
505
    {
506
        $this->app[ExceptionHandler::class]->report($e);
507
    }
508
509
    /**
510
     * Get swoole server instance.
511
     *
512
     * @return Swoole\Http\Server
0 ignored issues
show
Bug introduced by
The type SwooleTW\Http\Server\Swoole\Http\Server was not found. Did you mean Swoole\Http\Server? If so, make sure to prefix the type with \.
Loading history...
513
     */
514
    public function getServer()
515
    {
516
        return $this->server;
0 ignored issues
show
Bug Best Practice introduced by
The expression return $this->server returns the type Swoole\Websocket\Server|Swoole\Http\Server which is incompatible with the documented return type SwooleTW\Http\Server\Swoole\Http\Server.
Loading history...
517
    }
518
}
519