Completed
Branch master (9dff9d)
by Albert
05:30
created

Manager::bindSandbox()   A

Complexity

Conditions 1
Paths 1

Size

Total Lines 6
Code Lines 3

Duplication

Lines 0
Ratio 0 %

Importance

Changes 0
Metric Value
cc 1
eloc 3
nc 1
nop 0
dl 0
loc 6
rs 10
c 0
b 0
f 0
1
<?php
2
3
namespace SwooleTW\Http\Server;
4
5
use Throwable;
6
use Swoole\Http\Server;
7
use SwooleTW\Http\Server\Sandbox;
8
use SwooleTW\Http\Task\SwooleTaskJob;
9
use Illuminate\Support\Facades\Facade;
10
use SwooleTW\Http\Websocket\Websocket;
11
use SwooleTW\Http\Transformers\Request;
12
use SwooleTW\Http\Transformers\Response;
13
use SwooleTW\Http\Concerns\WithApplication;
14
use Illuminate\Contracts\Container\Container;
15
use Illuminate\Contracts\Debug\ExceptionHandler;
16
use SwooleTW\Http\Concerns\InteractsWithWebsocket;
17
use SwooleTW\Http\Concerns\InteractsWithSwooleTable;
18
19
class Manager
20
{
21
    use InteractsWithWebsocket,
0 ignored issues
show
introduced by
The trait SwooleTW\Http\Concerns\InteractsWithWebsocket requires some properties which are not provided by SwooleTW\Http\Server\Manager: $fd, $connections
Loading history...
22
        InteractsWithSwooleTable,
23
        WithApplication;
24
25
    /**
26
     * Container.
27
     *
28
     * @var \Illuminate\Contracts\Container\Container
29
     */
30
    protected $container;
31
32
    /**
33
     * @var string
34
     */
35
    protected $framework;
36
37
    /**
38
     * @var string
39
     */
40
    protected $basePath;
41
42
    /**
43
     * Server events.
44
     *
45
     * @var array
46
     */
47
    protected $events = [
48
        'start', 'shutDown', 'workerStart', 'workerStop', 'packet',
49
        'bufferFull', 'bufferEmpty', 'task', 'finish', 'pipeMessage',
50
        'workerError', 'managerStart', 'managerStop', 'request',
51
    ];
52
53
    /**
54
     * HTTP server manager constructor.
55
     *
56
     * @param \Swoole\Http\Server $server
57
     * @param \Illuminate\Contracts\Container\Container $container
58
     * @param string $framework
59
     * @param string $basePath
60
     */
61
    public function __construct(Container $container, $framework, $basePath = null)
62
    {
63
        $this->container = $container;
64
        $this->setFramework($framework);
65
        $this->setBasepath($basePath);
66
        $this->initialize();
67
    }
68
69
    /**
70
     * Run swoole server.
71
     */
72
    public function run()
73
    {
74
        $this->container['swoole.server']->start();
75
    }
76
77
    /**
78
     * Stop swoole server.
79
     */
80
    public function stop()
81
    {
82
        $this->container['swoole.server']->shutdown();
83
    }
84
85
    /**
86
     * Initialize.
87
     */
88
    protected function initialize()
89
    {
90
        $this->createTables();
91
        $this->prepareWebsocket();
92
        $this->setSwooleServerListeners();
93
    }
94
95
    /**
96
     * Set swoole server listeners.
97
     */
98
    protected function setSwooleServerListeners()
99
    {
100
        foreach ($this->events as $event) {
101
            $listener = 'on' . ucfirst($event);
102
103
            if (method_exists($this, $listener)) {
104
                $this->container['swoole.server']->on($event, [$this, $listener]);
105
            } else {
106
                $this->container['swoole.server']->on($event, function () use ($event) {
107
                    $event = sprintf('swoole.%s', $event);
108
109
                    $this->container['events']->fire($event, func_get_args());
110
                });
111
            }
112
        }
113
    }
114
115
    /**
116
     * "onStart" listener.
117
     */
118
    public function onStart()
119
    {
120
        $this->setProcessName('master process');
121
        $this->createPidFile();
122
123
        $this->container['events']->fire('swoole.start', func_get_args());
124
    }
125
126
    /**
127
     * The listener of "managerStart" event.
128
     *
129
     * @return void
130
     */
131
    public function onManagerStart()
132
    {
133
        $this->setProcessName('manager process');
134
        $this->container['events']->fire('swoole.managerStart', func_get_args());
135
    }
136
137
    /**
138
     * "onWorkerStart" listener.
139
     */
140
    public function onWorkerStart($server)
141
    {
142
        $this->clearCache();
143
        $this->setProcessName('worker process');
144
145
        $this->container['events']->fire('swoole.workerStart', func_get_args());
146
147
        // don't init laravel app in task workers
148
        if ($server->taskworker) {
149
            return;
150
        }
151
152
        // clear events instance in case of repeated listeners in worker process
153
        Facade::clearResolvedInstance('events');
154
155
        // prepare laravel app
156
        $this->getApplication();
157
158
        // bind after setting laravel app
159
        $this->bindToLaravelApp();
160
161
        // prepare websocket handler and routes
162
        if ($this->isWebsocket) {
163
            $this->prepareWebsocketHandler();
164
            $this->loadWebsocketRoutes();
165
        }
166
    }
167
168
    /**
169
     * "onRequest" listener.
170
     *
171
     * @param \Swoole\Http\Request $swooleRequest
172
     * @param \Swoole\Http\Response $swooleResponse
173
     */
174
    public function onRequest($swooleRequest, $swooleResponse)
175
    {
176
        $this->app['events']->fire('swoole.request');
177
178
        $this->resetOnRequest();
179
        $handleStatic = $this->container['config']->get('swoole_http.handle_static_files', true);
180
        $publicPath = $this->container['config']->get('swoole_http.server.public_path', base_path('public'));
181
182
        try {
183
            // handle static file request first
184
            if ($handleStatic && Request::handleStatic($swooleRequest, $swooleResponse, $publicPath)) {
185
                return;
186
            }
187
            // transform swoole request to illuminate request
188
            $illuminateRequest = Request::make($swooleRequest)->toIlluminate();
189
190
            // set current request to sandbox
191
            $this->app['swoole.sandbox']->setRequest($illuminateRequest);
192
            // enable sandbox
193
            $this->app['swoole.sandbox']->enable();
194
195
            // handle request via laravel/lumen's dispatcher
196
            $illuminateResponse = $this->app['swoole.sandbox']->run($illuminateRequest);
197
            $response = Response::make($illuminateResponse, $swooleResponse);
198
            $response->send();
199
        } catch (Throwable $e) {
200
            try {
201
                $exceptionResponse = $this->app[ExceptionHandler::class]->render($illuminateRequest, $e);
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...
202
                $response = Response::make($exceptionResponse, $swooleResponse);
203
                $response->send();
204
            } catch (Throwable $e) {
205
                $this->logServerError($e);
206
            }
207
        } finally {
208
            // disable and recycle sandbox resource
209
            $this->app['swoole.sandbox']->disable();
210
        }
211
    }
212
213
    /**
214
     * Reset on every request.
215
     */
216
    protected function resetOnRequest()
217
    {
218
        // Reset websocket data
219
        if ($this->isWebsocket) {
220
            $this->app['swoole.websocket']->reset(true);
221
        }
222
    }
223
224
    /**
225
     * Set onTask listener.
226
     */
227
    public function onTask($server, $taskId, $srcWorkerId, $data)
228
    {
229
        $this->container['events']->fire('swoole.task', func_get_args());
230
231
        try {
232
            // push websocket message
233
            if (is_array($data)) {
234
                if ($this->isWebsocket
235
                    && array_key_exists('action', $data)
236
                    && $data['action'] === Websocket::PUSH_ACTION) {
237
                    $this->pushMessage($server, $data['data'] ?? []);
238
                }
239
            // push async task to queue
240
            } elseif (is_string($data)) {
241
                $decoded = json_decode($data, true);
242
243
                if (JSON_ERROR_NONE === json_last_error() && isset($decoded['job'])) {
244
                    (new SwooleTaskJob($this->container, $server, $data, $taskId, $srcWorkerId))->fire();
245
                }
246
            }
247
        } catch (Throwable $e) {
248
            $this->logServerError($e);
249
        }
250
    }
251
252
    /**
253
     * Set onFinish listener.
254
     */
255
    public function onFinish($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

255
    public function onFinish($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

255
    public function onFinish($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

255
    public function onFinish(/** @scrutinizer ignore-unused */ $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...
256
    {
257
        // task worker callback
258
        return;
259
    }
260
261
    /**
262
     * Set onShutdown listener.
263
     */
264
    public function onShutdown()
265
    {
266
        $this->removePidFile();
267
    }
268
269
    /**
270
     * Set bindings to Laravel app.
271
     */
272
    protected function bindToLaravelApp()
273
    {
274
        $this->bindSandbox();
275
        $this->bindSwooleTable();
276
277
        if ($this->isWebsocket) {
278
            $this->bindRoom();
279
            $this->bindWebsocket();
280
        }
281
    }
282
283
    /**
284
     * Bind sandbox to Laravel app container.
285
     */
286
    protected function bindSandbox()
287
    {
288
        $this->app->singleton(Sandbox::class, function ($app) {
289
            return new Sandbox($app, $this->framework);
290
        });
291
        $this->app->alias(Sandbox::class, 'swoole.sandbox');
292
    }
293
294
    /**
295
     * Gets pid file path.
296
     *
297
     * @return string
298
     */
299
    protected function getPidFile()
300
    {
301
        return $this->container['config']->get('swoole_http.server.options.pid_file');
302
    }
303
304
    /**
305
     * Create pid file.
306
     */
307
    protected function createPidFile()
308
    {
309
        $pidFile = $this->getPidFile();
310
        $pid = $this->container['swoole.server']->master_pid;
311
312
        file_put_contents($pidFile, $pid);
313
    }
314
315
    /**
316
     * Remove pid file.
317
     */
318
    protected function removePidFile()
319
    {
320
        $pidFile = $this->getPidFile();
321
322
        if (file_exists($pidFile)) {
323
            unlink($pidFile);
324
        }
325
    }
326
327
    /**
328
     * Clear APC or OPCache.
329
     */
330
    protected function clearCache()
331
    {
332
        if (function_exists('apc_clear_cache')) {
333
            apc_clear_cache();
334
        }
335
336
        if (function_exists('opcache_reset')) {
337
            opcache_reset();
338
        }
339
    }
340
341
    /**
342
     * Set process name.
343
     *
344
     * @codeCoverageIgnore
345
     * @param $process
346
     */
347
    protected function setProcessName($process)
348
    {
349
        // MacOS doesn't support modifying process name.
350
        if ($this->isMacOS() || $this->isInTesting()) {
351
            return;
352
        }
353
        $serverName = 'swoole_http_server';
354
        $appName = $this->container['config']->get('app.name', 'Laravel');
355
356
        $name = sprintf('%s: %s for %s', $serverName, $process, $appName);
357
358
        swoole_set_process_name($name);
359
    }
360
361
    /**
362
     * Indicates if the process is running in macOS.
363
     *
364
     * @return bool
365
     */
366
    protected function isMacOS()
367
    {
368
        return PHP_OS === 'Darwin';
369
    }
370
371
    /**
372
     * Indicates if it's in phpunit environment.
373
     *
374
     * @return bool
375
     */
376
    protected function isInTesting()
377
    {
378
        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...
379
    }
380
381
    /**
382
     * Log server error.
383
     *
384
     * @param Throwable
385
     */
386
    public function logServerError(Throwable $e)
387
    {
388
        $this->container[ExceptionHandler::class]->report($e);
389
    }
390
}
391