Completed
Push — master ( 4a85c2...e72a9a )
by Song
05:32
created

Server::handleLumenShutdown()   A

Complexity

Conditions 2
Paths 2

Size

Total Lines 12
Code Lines 7

Duplication

Lines 0
Ratio 0 %

Importance

Changes 1
Bugs 0 Features 0
Metric Value
c 1
b 0
f 0
dl 0
loc 12
rs 9.4285
cc 2
eloc 7
nc 2
nop 2
1
<?php
2
3
namespace Encore\LumenSwoole;
4
5
use Illuminate\Container\Container;
6
use Illuminate\Http\Request;
7
use Illuminate\Http\Response;
8
use Laravel\Lumen\Application;
9
use Laravel\Lumen\Exceptions\Handler;
10
use swoole_http_server as HttpServer;
11
use Symfony\Component\Debug\Exception\FatalErrorException;
12
13
class Server
14
{
15
    const VERSION = 'lumen-swoole 0.1.0';
16
17
    /**
18
     * @var \Laravel\Lumen\Application
19
     */
20
    protected $app;
21
22
    /**
23
     * @var string
24
     */
25
    protected $host = '127.0.0.1';
26
27
    /**
28
     * @var int
29
     */
30
    protected $port = 8083;
31
32
    /**
33
     * @var string
34
     */
35
    protected $pidFile = '';
36
37
    /**
38
     * @var HttpServer
39
     */
40
    protected $httpServer;
41
42
    /**
43
     * Http server options.
44
     *
45
     * @var array
46
     */
47
    protected $options = [];
48
49
    protected $appSnapshot = null;
50
51
    /**
52
     * @var array
53
     */
54
    public static $validServerOptions = [
55
        'reactor_num',
56
        'worker_num',
57
        'max_request',
58
        'max_conn',
59
        'task_worker_num',
60
        'task_ipc_mode',
61
        'task_max_request',
62
        'task_tmpdir',
63
        'dispatch_mode',
64
        'message_queue_key',
65
        'daemonize',
66
        'backlog',
67
        'log_file',
68
        'log_level',
69
        'heartbeat_check_interval',
70
        'heartbeat_idle_time',
71
        'open_eof_check',
72
        'open_eof_split',
73
        'package_eof',
74
        'open_length_check',
75
        'package_length_type',
76
        'package_max_length',
77
        'open_cpu_affinity',
78
        'cpu_affinity_ignore',
79
        'open_tcp_nodelay',
80
        'tcp_defer_accept',
81
        'ssl_cert_file',
82
        'ssl_method',
83
        'user',
84
        'group',
85
        'chroot',
86
        'pipe_buffer_size',
87
        'buffer_output_size',
88
        'socket_buffer_size',
89
        'enable_unsafe_event',
90
        'discard_timeout_request',
91
        'enable_reuse_port',
92
        'ssl_ciphers',
93
        'enable_delay_receive',
94
    ];
95
96
    /**
97
     * Create a new Server instance.
98
     *
99
     * @param string $host
100
     * @param int    $port
101
     */
102
    public function __construct($host = '127.0.0.1', $port = 8083)
103
    {
104
        $this->host = $host;
105
        $this->port = $port;
106
    }
107
108
    /**
109
     * Initialize the server.
110
     *
111
     * @return $this
112
     */
113
    public function initHttpServer()
114
    {
115
        if ($this->httpServer) {
116
            return $this;
117
        }
118
119
        $this->httpServer = new HttpServer($this->host, $this->port);
120
121
        $this->httpServer->on('Request', [$this, 'onRequest']);
122
        $this->httpServer->on('Start', [$this, 'onStart']);
123
        $this->httpServer->on('Shutdown', [$this, 'onShutdown']);
124
125
        return $this;
126
    }
127
    
128
    /**
129
     * Set application.
130
     *
131
     * @param \Laravel\Lumen\Application $app
132
     *
133
     * @return $this
134
     */
135
    public function setApplication($app)
136
    {
137
        $this->app = $app;
138
139
        return $this;
140
    }
141
142
    /**
143
     * Resolve application.
144
     *
145
     * @return void
146
     */
147
    protected function resolveApplication()
148
    {
149
        if (! $this->app) {
150
            require $this->basePath('bootstrap/app.php');
151
        }
152
153
        $this->snapshotApplication();
154
    }
155
156
    protected function snapshotApplication()
157
    {
158
        if (! $this->appSnapshot) {
159
            $this->appSnapshot = clone Application::getInstance();
160
        }
161
    }
162
163
    /**
164
     * Get the base path for the application.
165
     *
166
     * @param string|null $path
167
     *
168
     * @return string
169
     */
170
    public function basePath($path = null)
171
    {
172
        return getcwd().($path ? '/'.$path : $path);
173
    }
174
175
    /**
176
     * Start the server.
177
     *
178
     * @return void
179
     */
180
    public function run()
181
    {
182
        $this->initHttpServer();
183
184
        $this->resolveApplication();
185
186
        $this->pidFile = app()->storagePath('lumen-swoole.pid');
187
188
        if ($this->isRunning()) {
189
            throw new \Exception('The server is already running.');
190
        }
191
192
        if (!empty($this->options)) {
193
            $this->httpServer->set($this->options);
194
        }
195
196
        $this->httpServer->start();
197
    }
198
199
    /**
200
     * Determine if server is running.
201
     *
202
     * @return bool
203
     */
204
    public function isRunning()
205
    {
206
        if (!file_exists($this->pidFile)) {
207
            return false;
208
        }
209
210
        $pid = file_get_contents($this->pidFile);
211
212
        return (bool) posix_getpgid($pid);
213
    }
214
215
    /**
216
     * Set http server options.
217
     *
218
     * @param array $options
219
     *
220
     * @return $this
221
     */
222
    public function options($options = [])
223
    {
224
        $this->options = array_only($options, static::$validServerOptions);
225
226
        return $this;
227
    }
228
229
    /**
230
     * On request callback.
231
     *
232
     * @param \swoole_http_request  $request
233
     * @param \swoole_http_response $response
234
     */
235
    public function onRequest($request, $response)
236
    {
237
        $this->buildGlobals($request);
238
239
        $obContents = '';
240
241
        $request = Request::capture();
242
243
        register_shutdown_function([$this, 'handleLumenShutdown'], $request, $response);
244
245
        ob_start();
246
        try {
247
            $lumenResponse = Application::getInstance()->dispatch($request);
248
            $obContents = ob_get_contents();
249
        } catch (\Exception $e) {
250
            $lumenResponse = $this->handleLumenException($request, $e);
251
        }
252
        ob_end_clean();
253
254
        $lumenResponse = $this->appendObContents($lumenResponse, $obContents);
255
256
        $this->handleResponse($response, $lumenResponse);
257
    }
258
259
    /**
260
     * Build global variables.
261
     *
262
     * @param \swoole_http_request  $request
263
     * @return void
264
     */
265
    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...
266
    {
267
        foreach ($request->server as $key => $value) {
268
            $_SERVER[strtoupper($key)] = $value;
269
        }
270
271
        if (property_exists($request, 'get')) {
272
            $_GET = $request->get;
273
        }
274
275
        if (property_exists($request, 'post')) {
276
            $_POST = $request->post;
277
        }
278
279
        if (property_exists($request, 'cookie')) {
280
            $_COOKIE = $request->cookie;
281
        }
282
283
        if (property_exists($request, 'files')) {
284
            $_FILES = $request->files;
285
        }
286
287
        if (property_exists($request, 'header')) {
288
            foreach ($request->header as $key => $value) {
289
                $_SERVER['HTTP_'.strtoupper($key)] = $value;
290
            }
291
        }
292
    }
293
294
    /**
295
     * Append ob contents to response.
296
     *
297
     * @param Response $lumenResponse
298
     * @param string   $obContents
299
     * @return $this
300
     */
301
    protected function appendObContents(Response $lumenResponse, $obContents)
302
    {
303
        return $lumenResponse->setContent($obContents.$lumenResponse->getContent());
304
    }
305
306
    /**
307
     * Handle uncaught exception.
308
     *
309
     * @param Request    $request
310
     * @param \Exception $e
311
     * @return Response
312
     */
313
    public function handleLumenException($request, $e)
314
    {
315
        return (new Handler())->render($request, $e);
316
    }
317
318
    /**
319
     * Handle lumen shutdown.
320
     *
321
     * @param Request               $request
322
     * @param \swoole_http_response $response
323
     * @return void
324
     */
325
    public function handleLumenShutdown($request, $response)
326
    {
327
        if ($error = error_get_last()) {
328
            $lumenResponse = $this->handleLumenException($request, new FatalErrorException(
329
                $error['message'], $error['type'], 0, $error['file'], $error['line']
330
            ));
331
332
            $this->handleResponse($response, $lumenResponse);
333
        } else {
334
            $this->handleResponse($response, new Response(ob_get_contents()));
335
        }
336
    }
337
338
    /**
339
     * Response handler.
340
     *
341
     * @param \swoole_http_response $swooleResponse
342
     * @param Response              $response
343
     *
344
     * @return void
345
     */
346
    protected function handleResponse($swooleResponse, Response $response)
347
    {
348
        $swooleResponse->status($response->getStatusCode());
349
350
        foreach ($response->headers->allPreserveCase() as $name => $values) {
351
            foreach ($values as $value) {
352
                $swooleResponse->header($name, $value);
353
            }
354
        }
355
356
        // set cookies
357
        foreach ($response->headers->getCookies() as $cookie) {
358
            $swooleResponse->rawcookie(
359
                $cookie->getName(),
360
                $cookie->getValue(),
361
                $cookie->getExpiresTime(),
362
                $cookie->getPath(),
363
                $cookie->getDomain(),
364
                $cookie->isSecure(),
365
                $cookie->isHttpOnly()
366
            );
367
        }
368
369
        Application::setInstance(clone $this->appSnapshot);
370
371
        // send content & close
372
        $swooleResponse->end($response->getContent());
373
    }
374
375
    /**
376
     * Server start event callback.
377
     *
378
     * @param $server
379
     */
380
    public function onStart($server)
381
    {
382
        file_put_contents($this->pidFile, $server->master_pid);
383
    }
384
385
    /**
386
     * Server shutdown event callback.
387
     */
388
    public function onShutdown()
389
    {
390
        unlink($this->pidFile);
391
    }
392
}
393