Completed
Push — master ( 468cfb...a6bc20 )
by Tim
06:21 queued 02:51
created

MultiThreadedServer::run()   F

Complexity

Conditions 30
Paths > 20000

Size

Total Lines 296
Code Lines 135

Duplication

Lines 0
Ratio 0 %

Code Coverage

Tests 0
CRAP Score 930

Importance

Changes 15
Bugs 2 Features 5
Metric Value
c 15
b 2
f 5
dl 0
loc 296
ccs 0
cts 176
cp 0
rs 2
cc 30
eloc 135
nc 419968
nop 0
crap 930

How to fix   Long Method    Complexity   

Long Method

Small methods make your code easier to understand, in particular if combined with a good name. Besides, if your method is small, finding a good name is usually much easier.

For example, if you find yourself adding comments to a method's body, this is usually a good sign to extract the commented part to a new method, and use the comment as a starting point when coming up with a good name for this new method.

Commonly applied refactorings include:

1
<?php
2
3
/**
4
 * \AppserverIo\Server\Servers\MultiThreadedServer
5
 *
6
 * NOTICE OF LICENSE
7
 *
8
 * This source file is subject to the Open Software License (OSL 3.0)
9
 * that is available through the world-wide-web at this URL:
10
 * http://opensource.org/licenses/osl-3.0.php
11
 *
12
 * PHP version 5
13
 *
14
 * @author    Johann Zelger <[email protected]>
15
 * @copyright 2015 TechDivision GmbH <[email protected]>
16
 * @license   http://opensource.org/licenses/osl-3.0.php Open Software License (OSL 3.0)
17
 * @link      https://github.com/appserver-io/server
18
 * @link      http://www.appserver.io
19
 */
20
21
namespace AppserverIo\Server\Servers;
22
23
use AppserverIo\Logger\LoggerUtils;
24
use AppserverIo\Server\Dictionaries\ServerStateKeys;
25
use AppserverIo\Server\Interfaces\ServerInterface;
26
use AppserverIo\Server\Interfaces\ServerContextInterface;
27
use AppserverIo\Server\Exceptions\ModuleNotFoundException;
28
use AppserverIo\Server\Exceptions\ConnectionHandlerNotFoundException;
29
use AppserverIo\Server\Interfaces\ModuleConfigurationAwareInterface;
30
31
/**
32
 * A multithreaded server implemenation.
33
 *
34
 * @author    Johann Zelger <[email protected]>
35
 * @copyright 2015 TechDivision GmbH <[email protected]>
36
 * @license   http://opensource.org/licenses/osl-3.0.php Open Software License (OSL 3.0)
37
 * @link      https://github.com/appserver-io/server
38
 * @link      http://www.appserver.io
39
 */
40
41
class MultiThreadedServer extends \Thread implements ServerInterface
42
{
43
44
    /**
45
     * Holds the server context instance
46
     *
47
     * @var \AppserverIo\Server\Interfaces\ServerContextInterface The server context instance
48
     */
49
    protected $serverContext;
50
51
    /**
52
     * TRUE if the server has been started successfully, else FALSE.
53
     *
54
     * @var \AppserverIo\Server\Dictionaries\ServerStateKeys
55
     */
56
    protected $serverState;
57
58
    /**
59
     * Constructs the server instance
60
     *
61
     * @param \AppserverIo\Server\Interfaces\ServerContextInterface $serverContext The server context instance
62
     */
63
    public function __construct(ServerContextInterface $serverContext)
64
    {
65
        // initialize the server state
66
        $this->serverState = ServerStateKeys::WAITING_FOR_INITIALIZATION;
67
        // set context
68
        $this->serverContext = $serverContext;
69
        // start server thread
70
        $this->start();
71
    }
72
73
    /**
74
     * Returns the config instance
75
     *
76
     * @return \AppserverIo\Server\Interfaces\ServerContextInterface
77
     */
78
    public function getServerContext()
79
    {
80
        return $this->serverContext;
81
    }
82
83
    /**
84
     * Shutdown the workers and stop the server.
85
     *
86
     * @return void
87
     */
88
    public function stop()
89
    {
90
        $this->synchronized(function ($self) {
91
            $self->serverState = ServerStateKeys::HALT;
92
        }, $this);
93
94
        do {
95
            // query whether application state key is SHUTDOWN or not
96
            $waitForShutdown = $this->synchronized(function ($self) {
97
                return $self->serverState !== ServerStateKeys::SHUTDOWN;
98
            }, $this);
99
100
            // wait one second more
101
            sleep(1);
102
0 ignored issues
show
Coding Style introduced by
Blank line found at end of control structure
Loading history...
103
        } while ($waitForShutdown);
104
    }
105
106
    /**
107
     * Starts the server's worker as defined in configuration
108
     *
109
     * @return void
110
     *
111
     * @throws \AppserverIo\Server\Exceptions\ModuleNotFoundException
112
     * @throws \AppserverIo\Server\Exceptions\ConnectionHandlerNotFoundException
113
     */
114
    public function run()
115
    {
116
        // set current dir to base dir for relative dirs
117
        chdir(SERVER_BASEDIR);
118
119
        // setup autoloader
120
        require SERVER_AUTOLOADER;
121
122
        // init server context
123
        $serverContext = $this->getServerContext();
124
125
        // init config var for shorter calls
126
        $serverConfig = $serverContext->getServerConfig();
127
128
        // init server name
129
        $serverName = $serverConfig->getName();
130
131
        // initialize the profile logger and the thread context
132
        $profileLogger = null;
133
        if ($serverContext->hasLogger(LoggerUtils::PROFILE)) {
134
            $profileLogger = $serverContext->getLogger(LoggerUtils::PROFILE);
135
            $profileLogger->appendThreadContext($serverName);
136
        }
137
138
        // init logger
139
        $logger = $serverContext->getLogger();
140
        $logger->debug(
141
            sprintf("starting %s (%s)", $serverName, __CLASS__)
142
        );
143
144
        try {
145
            // get class names
146
            $socketType = $serverConfig->getSocketType();
147
            $workerType = $serverConfig->getWorkerType();
148
            $streamContextType = $serverConfig->getStreamContextType();
149
150
            // init stream context for server connection
151
            $streamContext = new $streamContextType();
152
            // set socket backlog to 1024 for perform many concurrent connections
153
            $streamContext->setOption('socket', 'backlog', 1024);
154
155
            // check if ssl server config
156
            if ($serverConfig->getTransport() === 'ssl') {
157
                // path to local certificate file on filesystem. It must be a PEM encoded file which contains your
158
                // certificate and private key. It can optionally contain the certificate chain of issuers.
159
                $streamContext->setOption('ssl', 'local_cert', $this->realPath($serverConfig->getCertPath()));
160
161
                // query whether or not a passphrase has been set
162
                if ($passphrase = $serverConfig->getPassphrase()) {
163
                    $streamContext->setOption('ssl', 'passphrase', $passphrase);
164
                }
165
                // query whether or not DH param as been specified
166
                if ($dhParamPath = $serverConfig->getDhParamPath()) {
167
                    $streamContext->setOption('ssl', 'dh_param', $this->realPath($dhParamPath));
168
                }
169
                // query whether or not a private key as been specified
170
                if ($privateKeyPath = $serverConfig->getPrivateKeyPath()) {
171
                    $streamContext->setOption('ssl', 'local_pk', $this->realPath($privateKeyPath));
172
                }
173
                // set the passed crypto method
174
                if ($cryptoMethod = $serverConfig->getCryptoMethod()) {
175
                    $streamContext->setOption('ssl', 'crypto_method', $this->explodeConstants($cryptoMethod));
176
                }
177
                // set the passed peer name
178
                if ($peerName = $serverConfig->getPeerName()) {
179
                    $streamContext->setOption('ssl', 'peer_name', $peerName);
180
                }
181
                // set the ECDH curve to use
182
                if ($ecdhCurve = $serverConfig->getEcdhCurve()) {
183
                    $streamContext->setOption('ssl', 'ecdh_curve', $ecdhCurve);
184
                }
185
186
                // require verification of SSL certificate used and peer name
187
                $streamContext->setOption('ssl', 'verify_peer', $serverConfig->getVerifyPeer());
188
                $streamContext->setOption('ssl', 'verify_peer_name', $serverConfig->getVerifyPeerName());
189
                // allow self-signed certificates. requires verify_peer
190
                $streamContext->setOption('ssl', 'allow_self_signed', $serverConfig->getAllowSelfSigned());
191
                // if set, disable TLS compression this can help mitigate the CRIME attack vector
192
                $streamContext->setOption('ssl', 'disable_compression', $serverConfig->getDisableCompression());
193
                // optimizations for forward secrecy and ciphers
194
                $streamContext->setOption('ssl', 'honor_cipher_order', $serverConfig->getHonorCipherOrder());
195
                $streamContext->setOption('ssl', 'single_ecdh_use', $serverConfig->getSingleEcdhUse());
196
                $streamContext->setOption('ssl', 'single_dh_use', $serverConfig->getSingleDhUse());
197
                $streamContext->setOption('ssl', 'ciphers', $serverConfig->getCiphers());
198
199
                // set all domain specific certificates
200
                foreach ($serverConfig->getCertificates() as $certificate) {
201
                    // try to set ssl certificates
202
                    // validation checks are made there and we want the server started in case of invalid ssl context
203
                    try {
204
                        $streamContext->addSniServerCert($certificate['domain'], $certificate['certPath']);
205
                    } catch (\Exception $e) {
206
                        // log exception message
207
                        $logger->error($e->getMessage());
208
                    }
209
                }
210
            }
211
212
            // inject stream context to server context for further modification in modules init function
213
            $serverContext->injectStreamContext($streamContext);
0 ignored issues
show
Documentation introduced by
$streamContext is of type object, but the function expects a resource.

It seems like the type of the argument is not accepted by the function/method which you are calling.

In some cases, in particular if PHP’s automatic type-juggling kicks in this might be fine. In other cases, however this might be a bug.

We suggest to add an explicit type cast like in the following example:

function acceptsInteger($int) { }

$x = '123'; // string "123"

// Instead of
acceptsInteger($x);

// we recommend to use
acceptsInteger((integer) $x);
Loading history...
214
215
            // initialization has been successful
216
            $this->serverState = ServerStateKeys::INITIALIZATION_SUCCESSFUL;
217
218
            // init modules array
219
            $modules = array();
220
221
            // initiate server modules
222
            foreach ($serverConfig->getModules() as $moduleConfiguration) {
223
                // check if module type exists
224
                if (class_exists($moduleType = $moduleConfiguration->getType()) === false) {
225
                    throw new ModuleNotFoundException($moduleType);
226
                }
227
228
                // instantiate module type
229
                $module = new $moduleType();
230
231
                // query whether or not we've to inject the module configuration
232
                if ($module instanceof ModuleConfigurationAwareInterface) {
233
                    $module->injectModuleConfiguration($moduleConfiguration);
234
                }
235
236
                // append the initialized module to the list
237
                $modules[$moduleName = $module->getModuleName()] = $module;
238
239
                // debug log that the module has successfully been initialized
240
                $logger->debug(
241
                    sprintf("%s init %s module (%s)", $serverName, $moduleType::MODULE_NAME, $moduleType)
242
                );
243
244
                // init module with serverContext (this)
245
                $modules[$moduleName]->init($serverContext);
246
            }
247
248
            // modules has been initialized successfully
249
            $this->serverState = ServerStateKeys::MODULES_INITIALIZED;
250
251
            // init connection handler array
252
            $connectionHandlers = array();
253
            // initiate server connection handlers
254
            $connectionHandlersTypes = $serverConfig->getConnectionHandlers();
255
            foreach ($connectionHandlersTypes as $connectionHandlerType) {
256
                // check if conenction handler type exists
257
                if (!class_exists($connectionHandlerType)) {
258
                    throw new ConnectionHandlerNotFoundException($connectionHandlerType);
259
                }
260
                // instantiate connection handler type
261
                $connectionHandlers[$connectionHandlerType] = new $connectionHandlerType();
262
263
                $logger->debug(
264
                    sprintf("%s init connectionHandler (%s)", $serverName, $connectionHandlerType)
265
                );
266
267
                // init connection handler with serverContext (this)
268
                $connectionHandlers[$connectionHandlerType]->init($serverContext);
269
                // inject modules
270
                $connectionHandlers[$connectionHandlerType]->injectModules($modules);
271
            }
272
273
            // connection handlers has been initialized successfully
274
            $this->serverState = ServerStateKeys::CONNECTION_HANDLERS_INITIALIZED;
275
276
            // prepare the socket
277
            $localSocket = sprintf(
278
                '%s://%s:%d',
279
                $serverConfig->getTransport(),
280
                $serverConfig->getAddress(),
281
                $serverConfig->getPort()
282
            );
283
284
            // prepare the socket flags
285
            $flags = $this->explodeConstants($serverConfig->getFlags());
286
287
            // setup server bound on local adress
288
            $serverConnection = $socketType::getServerInstance($localSocket, $flags, $streamContext->getResource());
289
290
            // sockets has been started
291
            $this->serverState = ServerStateKeys::SERVER_SOCKET_STARTED;
292
293
            $logger->debug(
294
                sprintf("%s starting %s workers (%s)", $serverName, $serverConfig->getWorkerNumber(), $workerType)
295
            );
296
297
            // setup and start workers
298
            $workers = array();
299
            for ($i = 1; $i <= $serverConfig->getWorkerNumber(); ++$i) {
300
                $workers[$i] = new $workerType(
301
                    $serverConnection->getConnectionResource(),
302
                    $serverContext,
303
                    $connectionHandlers
304
                );
305
306
                $logger->debug(sprintf("Successfully started worker %s", $workers[$i]->getThreadId()));
307
            }
308
309
            // connection handlers has been initialized successfully
310
            $this->serverState = ServerStateKeys::WORKERS_INITIALIZED;
311
312
            $logger->info(
313
                sprintf("%s listing on %s:%s...", $serverName, $serverConfig->getAddress(), $serverConfig->getPort())
314
            );
315
316
            // watch dog for all workers to restart if it's needed while server is up
317
            while ($this->serverState === ServerStateKeys::WORKERS_INITIALIZED) {
318
                // iterate all workers
319
                for ($i = 1; $i <= $serverConfig->getWorkerNumber(); ++$i) {
320
                    // check if worker should be restarted
321
                    if ($workers[$i]->shouldRestart()) {
322
                        $logger->debug(
323
                            sprintf("%s restarting worker #%s (%s)", $serverName, $i, $workerType)
324
                        );
325
326
                        // unset origin worker ref
327
                        unset($workers[$i]);
328
                        // build up and start new worker instance
329
                        $workers[$i] = new $workerType(
330
                            $serverConnection->getConnectionResource(),
331
                            $serverContext,
332
                            $connectionHandlers
333
                        );
334
                    }
335
                }
336
337
                if ($profileLogger) {
338
                    // profile the worker shutdown beeing processed
339
                    $profileLogger->debug(sprintf('Server %s waiting for shutdown', $serverName));
340
                }
341
342
                // sleep for 1 second to lower system load
343
                usleep(1000000);
344
            }
345
346
            // print a message with the number of initialized workers
347
            $logger->debug(sprintf('Now shutdown server %s (%d workers)', $serverName, sizeof($workers)));
348
349
            // prepare the URL and the options for the shutdown requests
350
            $scheme = $serverConfig->getTransport() == 'tcp' ? 'http' : 'https';
351
352
            // prepare the URL for the request to shutdown the workers
353
            $url =  sprintf('%s://%s:%d', $scheme, $serverConfig->getAddress(), $serverConfig->getPort());
354
355
            // create a context for the HTTP/HTTPS connection
356
            $context  = stream_context_create(
357
                array(
358
                    'http' => array(
359
                        'method'  => 'GET',
360
                        'header'  => "Connection: close\r\n"
361
                    ),
362
                    'https' => array(
363
                        'method'  => 'GET',
364
                        'header'  => "Connection: close\r\n"
365
                    ),
366
                    'ssl' => array(
367
                        'verify_peer'      => false,
368
                        'verify_peer_name' => false
369
                    )
370
                )
371
            );
372
373
            // try to shutdown all workers
374
            while (sizeof($workers) > 0) {
375
                // iterate all workers
376
                for ($i = 1; $i <= $serverConfig->getWorkerNumber(); ++$i) {
377
                    // check if worker should be restarted
378
                    if (isset($workers[$i]) && $workers[$i]->shouldRestart()) {
379
                        // unset worker, it has been shutdown successfully
380
                        unset($workers[$i]);
381
                    } elseif (isset($workers[$i]) && $workers[$i]->shouldRestart() === false) {
382
                        // send a request to shutdown running worker
383
                        @file_get_contents($url, false, $context);
0 ignored issues
show
Security Best Practice introduced by
It seems like you do not handle an error condition here. This can introduce security issues, and is generally not recommended.

If you suppress an error, we recommend checking for the error condition explicitly:

// For example instead of
@mkdir($dir);

// Better use
if (@mkdir($dir) === false) {
    throw new \RuntimeException('The directory '.$dir.' could not be created.');
}
Loading history...
384
                        // don't flood the remaining workers
385
                        usleep(10000);
386
                    } else {
387
                        // send a debug log message that worker has been shutdown
388
                        $logger->debug("Worker $i successfully been shutdown ...");
389
                    }
390
                }
391
            }
392
0 ignored issues
show
Coding Style introduced by
Blank line found at end of control structure
Loading history...
393
        } catch (\Exception $e) {
394
            // log error message
395
            $logger->error($e->getMessage());
396
        }
397
398
        // close the server sockets if opened before
399
        if ($serverConnection) {
400
            $serverConnection->close();
0 ignored issues
show
Bug introduced by
The variable $serverConnection does not seem to be defined for all execution paths leading up to this point.

If you define a variable conditionally, it can happen that it is not defined for all execution paths.

Let’s take a look at an example:

function myFunction($a) {
    switch ($a) {
        case 'foo':
            $x = 1;
            break;

        case 'bar':
            $x = 2;
            break;
    }

    // $x is potentially undefined here.
    echo $x;
}

In the above example, the variable $x is defined if you pass “foo” or “bar” as argument for $a. However, since the switch statement has no default case statement, if you pass any other value, the variable $x would be undefined.

Available Fixes

  1. Check for existence of the variable explicitly:

    function myFunction($a) {
        switch ($a) {
            case 'foo':
                $x = 1;
                break;
    
            case 'bar':
                $x = 2;
                break;
        }
    
        if (isset($x)) { // Make sure it's always set.
            echo $x;
        }
    }
    
  2. Define a default value for the variable:

    function myFunction($a) {
        $x = ''; // Set a default which gets overridden for certain paths.
        switch ($a) {
            case 'foo':
                $x = 1;
                break;
    
            case 'bar':
                $x = 2;
                break;
        }
    
        echo $x;
    }
    
  3. Add a value for the missing path:

    function myFunction($a) {
        switch ($a) {
            case 'foo':
                $x = 1;
                break;
    
            case 'bar':
                $x = 2;
                break;
    
            // We add support for the missing case.
            default:
                $x = '';
                break;
        }
    
        echo $x;
    }
    
Loading history...
401
            // mark the server state as shutdown
402
            $this->synchronized(function ($self) {
403
                $self->serverState = ServerStateKeys::SHUTDOWN;
404
            }, $this);
405
        }
406
407
        // send a debug log message that connection has been closed and server has been shutdown
408
        $logger->info("Successfully closed connection and shutdown server $serverName");
409
    }
410
411
    /**
412
     * Explodes the constants from the passed string.
413
     *
414
     * @param string $values The string with the constants to explode
415
     *
416
     * @return integer The exploded constants
417
     */
418
    protected function explodeConstants($values)
419
    {
420
421
        // initialize the constants
422
        $constants = null;
423
424
        // explode the constants
425
        foreach (explode('|', $values) as $value) {
426
            $constant = trim($value);
427
            if (empty($constant) === false) {
428
                $constants += constant($constant);
429
            }
430
        }
431
432
        // return the constants
433
        return $constants;
434
    }
435
436
    /**
437
     * Return's the real path, if not already.
438
     *
439
     * @param string $path The path to return the real path for
440
     *
441
     * @return string The real path
442
     */
443
    protected function realPath($path)
444
    {
445
446
        // take care for the OS
447
        $path = str_replace('/', DIRECTORY_SEPARATOR, $path);
448
449
        // check if relative or absolute path was given
450
        if (strpos($path, '/') === false) {
451
            $path = SERVER_BASEDIR . $path;
452
        }
453
454
        // return the real path
455
        return $path;
456
    }
457
}
458