Server   B
last analyzed

Complexity

Total Complexity 37

Size/Duplication

Total Lines 416
Duplicated Lines 0 %

Coupling/Cohesion

Components 1
Dependencies 7

Importance

Changes 6
Bugs 2 Features 1
Metric Value
wmc 37
lcom 1
cbo 7
dl 0
loc 416
rs 8.6
c 6
b 2
f 1

17 Methods

Rating   Name   Duplication   Size   Complexity  
A __construct() 0 5 1
A initHttpServer() 0 14 2
A setApplication() 0 6 1
A resolveApplication() 0 8 2
A snapshotApplication() 0 6 2
A basePath() 0 4 2
A run() 0 18 3
A isRunning() 0 10 2
A options() 0 6 1
B onRequest() 0 28 3
C buildGlobals() 0 28 8
A appendObContents() 0 4 1
A handleLumenException() 0 4 1
A handleLumenShutdown() 0 12 2
B handleResponse() 0 28 4
A onStart() 0 4 1
A onShutdown() 0 4 1
1
<?php
2
3
namespace Encore\LumenSwoole;
4
5
use Illuminate\Http\Request;
6
use Laravel\Lumen\Application;
7
use Laravel\Lumen\Exceptions\Handler;
8
use swoole_http_server as HttpServer;
9
use Symfony\Component\Debug\Exception\FatalErrorException;
10
use Symfony\Component\HttpFoundation\Response;
11
12
/**
13
 * Class Server.
14
 */
15
class Server
16
{
17
    /**
18
     * lumen-swoole version.
19
     */
20
    const VERSION = 'lumen-swoole 0.1.0';
21
22
    /**
23
     * @var \Laravel\Lumen\Application
24
     */
25
    protected $app;
26
27
    /**
28
     * Default host.
29
     *
30
     * @var string
31
     */
32
    protected $host = '127.0.0.1';
33
34
    /**
35
     * Default port.
36
     *
37
     * @var int
38
     */
39
    protected $port = 8083;
40
41
    /**
42
     * Pid file.
43
     *
44
     * @var string
45
     */
46
    protected $pidFile = '';
47
48
    /**
49
     * Http server instance.
50
     *
51
     * @var HttpServer
52
     */
53
    protected $httpServer;
54
55
    /**
56
     * Http server options.
57
     *
58
     * @var array
59
     */
60
    protected $options = [];
61
62
    /**
63
     * Application snapshot.
64
     *
65
     * @var null
66
     */
67
    protected $appSnapshot = null;
68
69
    /**
70
     * Valid swoole http server options.
71
     *
72
     * @see http://wiki.swoole.com/wiki/page/274.html
73
     *
74
     * @var array
75
     */
76
    public static $validServerOptions = [
77
        'reactor_num',
78
        'worker_num',
79
        'max_request',
80
        'max_conn',
81
        'task_worker_num',
82
        'task_ipc_mode',
83
        'task_max_request',
84
        'task_tmpdir',
85
        'dispatch_mode',
86
        'message_queue_key',
87
        'daemonize',
88
        'backlog',
89
        'log_file',
90
        'log_level',
91
        'heartbeat_check_interval',
92
        'heartbeat_idle_time',
93
        'open_eof_check',
94
        'open_eof_split',
95
        'package_eof',
96
        'open_length_check',
97
        'package_length_type',
98
        'package_max_length',
99
        'open_cpu_affinity',
100
        'cpu_affinity_ignore',
101
        'open_tcp_nodelay',
102
        'tcp_defer_accept',
103
        'ssl_cert_file',
104
        'ssl_method',
105
        'user',
106
        'group',
107
        'chroot',
108
        'pipe_buffer_size',
109
        'buffer_output_size',
110
        'socket_buffer_size',
111
        'enable_unsafe_event',
112
        'discard_timeout_request',
113
        'enable_reuse_port',
114
        'ssl_ciphers',
115
        'enable_delay_receive',
116
    ];
117
118
    /**
119
     * If shutdown function registered.
120
     *
121
     * @var bool
122
     */
123
    protected $shutdownFunctionRegistered = false;
124
125
    /**
126
     * Create a new Server instance.
127
     *
128
     * @param string $host
129
     * @param int    $port
130
     */
131
    public function __construct($host = '127.0.0.1', $port = 8083)
132
    {
133
        $this->host = $host;
134
        $this->port = $port;
135
    }
136
137
    /**
138
     * Initialize the server.
139
     *
140
     * @return $this
141
     */
142
    public function initHttpServer()
143
    {
144
        if ($this->httpServer) {
145
            return $this;
146
        }
147
148
        $this->httpServer = new HttpServer($this->host, $this->port);
149
150
        $this->httpServer->on('Request', [$this, 'onRequest']);
151
        $this->httpServer->on('Start', [$this, 'onStart']);
152
        $this->httpServer->on('Shutdown', [$this, 'onShutdown']);
153
154
        return $this;
155
    }
156
157
    /**
158
     * Set application.
159
     *
160
     * @param \Laravel\Lumen\Application $app
161
     *
162
     * @return $this
163
     */
164
    public function setApplication($app)
165
    {
166
        $this->app = $app;
167
168
        return $this;
169
    }
170
171
    /**
172
     * Resolve application.
173
     *
174
     * @return void
175
     */
176
    protected function resolveApplication()
177
    {
178
        if (!$this->app) {
179
            require $this->basePath('bootstrap/app.php');
180
        }
181
182
        $this->snapshotApplication();
183
    }
184
185
    protected function snapshotApplication()
186
    {
187
        if (!$this->appSnapshot) {
188
            $this->appSnapshot = clone Application::getInstance();
0 ignored issues
show
Documentation Bug introduced by
It seems like clone \Laravel\Lumen\Application::getInstance() of type object<Laravel\Lumen\Application> is incompatible with the declared type null of property $appSnapshot.

Our type inference engine has found an assignment to a property that is incompatible with the declared type of that property.

Either this assignment is in error or the assigned type should be added to the documentation/type hint for that property..

Loading history...
189
        }
190
    }
191
192
    /**
193
     * Get the base path for the application.
194
     *
195
     * @param string|null $path
196
     *
197
     * @return string
198
     */
199
    public function basePath($path = null)
200
    {
201
        return getcwd().($path ? '/'.$path : $path);
202
    }
203
204
    /**
205
     * Start the server.
206
     *
207
     * @return void
208
     */
209
    public function run()
210
    {
211
        $this->initHttpServer();
212
213
        $this->resolveApplication();
214
215
        $this->pidFile = app()->storagePath('lumen-swoole.pid');
216
217
        if ($this->isRunning()) {
218
            throw new \Exception('The server is already running.');
219
        }
220
221
        if (!empty($this->options)) {
222
            $this->httpServer->set($this->options);
223
        }
224
225
        $this->httpServer->start();
226
    }
227
228
    /**
229
     * Determine if server is running.
230
     *
231
     * @return bool
232
     */
233
    public function isRunning()
234
    {
235
        if (!file_exists($this->pidFile)) {
236
            return false;
237
        }
238
239
        $pid = file_get_contents($this->pidFile);
240
241
        return (bool) posix_getpgid($pid);
242
    }
243
244
    /**
245
     * Set http server options.
246
     *
247
     * @param array $options
248
     *
249
     * @return $this
250
     */
251
    public function options($options = [])
252
    {
253
        $this->options = array_only($options, static::$validServerOptions);
254
255
        return $this;
256
    }
257
258
    /**
259
     * On request callback.
260
     *
261
     * @param \swoole_http_request  $request
262
     * @param \swoole_http_response $response
263
     */
264
    public function onRequest($request, $response)
265
    {
266
        $this->buildGlobals($request);
267
268
        $obContents = '';
269
270
        $request = Request::capture();
271
272
        if (!$this->shutdownFunctionRegistered) {
273
            register_shutdown_function([$this, 'handleLumenShutdown'], $request, $response);
274
            $this->shutdownFunctionRegistered = true;
275
        }
276
277
        ob_start();
278
279
        try {
280
            $lumenResponse = Application::getInstance()->dispatch($request);
281
            $lumenResponse->prepare($request);
282
            $obContents = ob_get_contents();
283
        } catch (\Exception $e) {
284
            $lumenResponse = $this->handleLumenException($request, $e);
285
        }
286
        ob_end_clean();
287
288
        $lumenResponse = $this->appendObContents($lumenResponse, $obContents);
289
290
        $this->handleResponse($response, $lumenResponse);
291
    }
292
293
    /**
294
     * Build global variables.
295
     *
296
     * @param \swoole_http_request $request
297
     *
298
     * @return void
299
     */
300
    protected function buildGlobals($request)
0 ignored issues
show
Coding Style introduced by
buildGlobals uses the super-global variable $_SERVER which is generally not recommended.

Instead of super-globals, we recommend to explicitly inject the dependencies of your class. This makes your code less dependent on global state and it becomes generally more testable:

// Bad
class Router
{
    public function generate($path)
    {
        return $_SERVER['HOST'].$path;
    }
}

// Better
class Router
{
    private $host;

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

    public function generate($path)
    {
        return $this->host.$path;
    }
}

class Controller
{
    public function myAction(Request $request)
    {
        // Instead of
        $page = isset($_GET['page']) ? intval($_GET['page']) : 1;

        // Better (assuming you use the Symfony2 request)
        $page = $request->query->get('page', 1);
    }
}
Loading history...
Coding Style introduced by
buildGlobals uses the super-global variable $_GET which is generally not recommended.

Instead of super-globals, we recommend to explicitly inject the dependencies of your class. This makes your code less dependent on global state and it becomes generally more testable:

// Bad
class Router
{
    public function generate($path)
    {
        return $_SERVER['HOST'].$path;
    }
}

// Better
class Router
{
    private $host;

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

    public function generate($path)
    {
        return $this->host.$path;
    }
}

class Controller
{
    public function myAction(Request $request)
    {
        // Instead of
        $page = isset($_GET['page']) ? intval($_GET['page']) : 1;

        // Better (assuming you use the Symfony2 request)
        $page = $request->query->get('page', 1);
    }
}
Loading history...
Coding Style introduced by
buildGlobals uses the super-global variable $_POST which is generally not recommended.

Instead of super-globals, we recommend to explicitly inject the dependencies of your class. This makes your code less dependent on global state and it becomes generally more testable:

// Bad
class Router
{
    public function generate($path)
    {
        return $_SERVER['HOST'].$path;
    }
}

// Better
class Router
{
    private $host;

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

    public function generate($path)
    {
        return $this->host.$path;
    }
}

class Controller
{
    public function myAction(Request $request)
    {
        // Instead of
        $page = isset($_GET['page']) ? intval($_GET['page']) : 1;

        // Better (assuming you use the Symfony2 request)
        $page = $request->query->get('page', 1);
    }
}
Loading history...
Coding Style introduced by
buildGlobals uses the super-global variable $_COOKIE which is generally not recommended.

Instead of super-globals, we recommend to explicitly inject the dependencies of your class. This makes your code less dependent on global state and it becomes generally more testable:

// Bad
class Router
{
    public function generate($path)
    {
        return $_SERVER['HOST'].$path;
    }
}

// Better
class Router
{
    private $host;

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

    public function generate($path)
    {
        return $this->host.$path;
    }
}

class Controller
{
    public function myAction(Request $request)
    {
        // Instead of
        $page = isset($_GET['page']) ? intval($_GET['page']) : 1;

        // Better (assuming you use the Symfony2 request)
        $page = $request->query->get('page', 1);
    }
}
Loading history...
Coding Style introduced by
buildGlobals uses the super-global variable $_FILES which is generally not recommended.

Instead of super-globals, we recommend to explicitly inject the dependencies of your class. This makes your code less dependent on global state and it becomes generally more testable:

// Bad
class Router
{
    public function generate($path)
    {
        return $_SERVER['HOST'].$path;
    }
}

// Better
class Router
{
    private $host;

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

    public function generate($path)
    {
        return $this->host.$path;
    }
}

class Controller
{
    public function myAction(Request $request)
    {
        // Instead of
        $page = isset($_GET['page']) ? intval($_GET['page']) : 1;

        // Better (assuming you use the Symfony2 request)
        $page = $request->query->get('page', 1);
    }
}
Loading history...
301
    {
302
        foreach ($request->server as $key => $value) {
303
            $_SERVER[strtoupper($key)] = $value;
304
        }
305
306
        if (property_exists($request, 'get')) {
307
            $_GET = $request->get;
308
        }
309
310
        if (property_exists($request, 'post')) {
311
            $_POST = $request->post;
312
        }
313
314
        if (property_exists($request, 'cookie')) {
315
            $_COOKIE = $request->cookie;
316
        }
317
318
        if (property_exists($request, 'files')) {
319
            $_FILES = $request->files;
320
        }
321
322
        if (property_exists($request, 'header')) {
323
            foreach ($request->header as $key => $value) {
324
                $_SERVER['HTTP_'.strtoupper($key)] = $value;
325
            }
326
        }
327
    }
328
329
    /**
330
     * Append ob contents to response.
331
     *
332
     * @param Response $lumenResponse
333
     * @param string   $obContents
334
     *
335
     * @return $this
336
     */
337
    protected function appendObContents(Response $lumenResponse, $obContents)
338
    {
339
        return $lumenResponse->setContent($obContents.$lumenResponse->getContent());
340
    }
341
342
    /**
343
     * Handle uncaught exception.
344
     *
345
     * @param Request    $request
346
     * @param \Exception $e
347
     *
348
     * @return Response
349
     */
350
    public function handleLumenException($request, $e)
351
    {
352
        return (new Handler())->render($request, $e);
353
    }
354
355
    /**
356
     * Handle lumen shutdown.
357
     *
358
     * @param Request               $request
359
     * @param \swoole_http_response $response
360
     *
361
     * @return void
362
     */
363
    public function handleLumenShutdown($request, $response)
364
    {
365
        if ($error = error_get_last()) {
366
            $lumenResponse = $this->handleLumenException($request, new FatalErrorException(
367
                $error['message'], $error['type'], 0, $error['file'], $error['line']
368
            ));
369
370
            $this->handleResponse($response, $lumenResponse);
371
        } else {
372
            $this->handleResponse($response, new Response(ob_get_contents()));
373
        }
374
    }
375
376
    /**
377
     * Response handler.
378
     *
379
     * @param \swoole_http_response $swooleResponse
380
     * @param Response              $response
381
     *
382
     * @return void
383
     */
384
    protected function handleResponse($swooleResponse, Response $response)
385
    {
386
        $swooleResponse->status($response->getStatusCode());
387
388
        foreach ($response->headers->allPreserveCase() as $name => $values) {
389
            foreach ($values as $value) {
390
                $swooleResponse->header($name, $value);
391
            }
392
        }
393
394
        // set cookies
395
        foreach ($response->headers->getCookies() as $cookie) {
396
            $swooleResponse->rawcookie(
397
                $cookie->getName(),
398
                $cookie->getValue(),
399
                $cookie->getExpiresTime(),
400
                $cookie->getPath(),
401
                $cookie->getDomain(),
402
                $cookie->isSecure(),
403
                $cookie->isHttpOnly()
404
            );
405
        }
406
407
        Application::setInstance(clone $this->appSnapshot);
408
409
        // send content & close
410
        $swooleResponse->end($response->getContent());
411
    }
412
413
    /**
414
     * Server start event callback.
415
     *
416
     * @param $server
417
     */
418
    public function onStart($server)
419
    {
420
        file_put_contents($this->pidFile, $server->master_pid);
421
    }
422
423
    /**
424
     * Server shutdown event callback.
425
     */
426
    public function onShutdown()
427
    {
428
        unlink($this->pidFile);
429
    }
430
}
431