Completed
Pull Request — master (#1105)
by Tim
42:55
created

Telnet::shutdown()   A

Complexity

Conditions 4
Paths 3

Size

Total Lines 15

Duplication

Lines 0
Ratio 0 %

Code Coverage

Tests 0
CRAP Score 20

Importance

Changes 0
Metric Value
cc 4
nc 3
nop 0
dl 0
loc 15
rs 9.7666
c 0
b 0
f 0
ccs 0
cts 9
cp 0
crap 20
1
<?php
2
3
/**
4
 * AppserverIo\Appserver\Core\Consoles\Telnet
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    Tim Wagner <[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/appserver
18
 * @link      http://www.appserver.io
19
 */
20
21
namespace AppserverIo\Appserver\Core\Consoles;
22
23
use AppserverIo\Appserver\Core\Commands\CommandFactory;
24
use AppserverIo\Appserver\Core\Commands\Helper\Arguments;
25
use AppserverIo\Psr\Cli\ConsoleInterface;
26
use AppserverIo\Psr\Cli\Configuration\ConsoleConfigurationInterface;
27
use AppserverIo\Psr\ApplicationServer\ApplicationServerInterface;
28
29
/**
30
 * A Telnet based management console implementation using a React PHP socket server.
31
 *
32
 * @author    Tim Wagner <[email protected]>
33
 * @copyright 2015 TechDivision GmbH <[email protected]>
34
 * @license   http://opensource.org/licenses/osl-3.0.php Open Software License (OSL 3.0)
35
 * @link      https://github.com/appserver-io/appserver
36
 * @link      http://www.appserver.io
37
 */
38
class Telnet extends \Thread implements ConsoleInterface
39
{
40
41
    /**
42
     * The configuration parameter name for the port to listen to.
43
     *
44
     * @var string
45
     */
46
    const PARAM_PORT = 'port';
47
48
    /**
49
     * The configuration parameter name for the IP address to listen to.
50
     *
51
     * @var string
52
     */
53
    const PARAM_ADDRESS = 'address';
54
55
    /**
56
     * appserver.io written in ASCII art.
57
     *
58
     * @var string
59
     */
60
    protected static $logo = '                                                    _
61
  ____ _____  ____  ________  ______   _____  _____(_)___
62
 / __ `/ __ \/ __ \/ ___/ _ \/ ___/ | / / _ \/ ___/ / __ \
63
/ /_/ / /_/ / /_/ (__  )  __/ /   | |/ /  __/ /  / / /_/ /
64
\__,_/ .___/ .___/____/\___/_/    |___/\___/_(_)/_/\____/
65
    /_/   /_/
66
67
';
68
69
    /**
70
     * Initialize and start the management console.
71
     *
72
     * @param \AppserverIo\Psr\ApplicationServer\ApplicationServerInterface    $applicationServer The reference to the server
73
     * @param \AppserverIo\Psr\Cli\Configuration\ConsoleConfigurationInterface $consoleNode       The console configuration
74
     *
75
     * @return void
76
     */
77
    public function __construct(ApplicationServerInterface $applicationServer, ConsoleConfigurationInterface $consoleNode)
78
    {
79
        $this->applicationServer = $applicationServer;
80
        $this->consoleNode = $consoleNode;
81
        $this->start(PTHREADS_INHERIT_ALL);
82
    }
83
84
    /**
85
     * Return's the console name.
86
     *
87
     * @return string The console name
88
     */
89
    public function getName()
90
    {
91
        return $this->consoleNode->getName();
92
    }
93
94
    /**
95
     * Returns the port to listen to.
96
     *
97
     * @return integer The port to listen to
98
     */
99
    protected function getPort()
100
    {
101
        return $this->consoleNode->getParam(Telnet::PARAM_PORT);
102
    }
103
104
    /**
105
     * Returns the IP address to listen to.
106
     *
107
     * @return integer The IP address to listen to
108
     */
109
    protected function getAddress()
110
    {
111
        return $this->consoleNode->getParam(Telnet::PARAM_ADDRESS);
112
    }
113
114
    /**
115
     * Shutdown handler that checks for fatal/user errors.
116
     *
117
     * @return void
118
     */
119
    public function shutdown()
120
    {
121
        // check if there was a fatal error caused shutdown
122
        if ($lastError = error_get_last()) {
123
            // initialize type + message
124
            $type = 0;
125
            $message = '';
126
            // extract the last error values
127
            extract($lastError);
128
            // query whether we've a fatal/user error
129
            if ($type === E_ERROR || $type === E_USER_ERROR) {
130
                echo $message . PHP_EOL;
131
            }
132
        }
133
    }
134
135
    /**
136
     * Stop the console and closes all connections.
137
     *
138
     * @return void
139
     */
140
    public function stop()
141
    {
142
        $this->kill();
143
    }
144
145
    /**
146
     * The thread's run() method that runs asynchronously.
147
     *
148
     * @return void
149
     * @link http://www.php.net/manual/en/thread.run.php
150
     */
151
    public function run()
152
    {
153
154
        // register a shutdown handler for controlled shutdown
155
        register_shutdown_function(array(&$this, 'shutdown'));
156
157
        // we need the autloader again
158
        require SERVER_AUTOLOADER;
159
160
        // create a reference to the application server instance
161
        $applicationServer = $this->applicationServer;
162
163
        // initialize the event loop and the socket server
164
        $loop = \React\EventLoop\Factory::create();
165
        $socket = new \React\Socket\Server($loop);
166
167
        // wait for connections
168
        $socket->on('connection', function ($conn) use ($applicationServer) {
169
            // wait for user input => usually a command
170
            $conn->on('data', function ($data) use ($conn, $applicationServer) {
171
                try {
172
                    // extract command name and parameters
173
                    $params = Arguments::split($data);
174
                    $commandName = array_shift($params);
175
176
                    try {
177
                        // initialize and execute the command
178
                        $command = CommandFactory::factory($commandName, array($conn, $applicationServer));
179
                        $command->execute($params);
180
0 ignored issues
show
Coding Style introduced by
Blank line found at end of control structure
Loading history...
181
                    } catch (\ReflectionException $re) {
182
                        $conn->write(sprintf("Unknown command %sERROR\n", $commandName));
183
                    }
184
0 ignored issues
show
Coding Style introduced by
Blank line found at end of control structure
Loading history...
185
                } catch (\Exception $e) {
186
                    $conn->write("{$e->__toString()}ERROR\n");
187
                }
188
            });
189
        });
190
191
        // listen to the management socket
192
        $socket->listen($this->getPort(), $this->getAddress());
193
194
        // start the event loop and the socket server, but disable warnings as some React warnings cannot (or won't) be dealt with.
195
        // Specifically the warning if a client disconnects unexpectedly or does not even connect to begin with ("Interrupted system call") is unevitable
196
        // @see https://github.com/reactphp/react/pull/297
197
        // @see https://github.com/reactphp/react/issues/296
198
        // @see http://php.net/manual/de/function.stream-select.php
199
        $currentReportingLevel = error_reporting();
200
        error_reporting(E_ALL ^ E_WARNING);
201
        $loop->run();
202
        error_reporting($currentReportingLevel);
203
    }
204
}
205