Completed
Push — master ( 3dfec0...098987 )
by Sam
03:00
created

WebSocketManager::getSubscribedEvents()   A

Complexity

Conditions 1
Paths 1

Size

Total Lines 7
Code Lines 4

Duplication

Lines 0
Ratio 0 %

Importance

Changes 1
Bugs 0 Features 0
Metric Value
c 1
b 0
f 0
dl 0
loc 7
rs 9.4285
cc 1
eloc 4
nc 1
nop 0
1
<?php
2
3
namespace Jalle19\StatusManager\Manager;
4
5
use Jalle19\StatusManager\Configuration\Configuration;
6
use Jalle19\StatusManager\Event\Events;
7
use Jalle19\StatusManager\Event\InstanceStatusUpdatesEvent;
8
use Jalle19\StatusManager\Exception\MalformedRequestException;
9
use Jalle19\StatusManager\Exception\UnhandledMessageException;
10
use Jalle19\StatusManager\Exception\UnknownRequestException;
11
use Jalle19\StatusManager\Message\AbstractMessage;
12
use Jalle19\StatusManager\Message\Factory as MessageFactory;
13
use Jalle19\StatusManager\Message\Handler\DelegatesMessagesTrait;
14
use Jalle19\StatusManager\Message\StatusUpdatesMessage;
15
use Psr\Log\LoggerInterface;
16
use Ratchet\ConnectionInterface;
17
use Ratchet\Http\HttpServer;
18
use Ratchet\MessageComponentInterface;
19
use Ratchet\Server\IoServer;
20
use Ratchet\WebSocket\WsServer;
21
use React\EventLoop\LoopInterface;
22
use React\Socket\Server as ServerSocket;
23
use Symfony\Component\EventDispatcher\EventDispatcher;
24
use Symfony\Component\EventDispatcher\EventSubscriberInterface;
25
26
/**
27
 * Handles events related to the WebSocket. Events are either triggered from Ratchet (onOpen etc.)
28
 * or from the event dispatcher in StatusManager.
29
 *
30
 * @package   Jalle19\StatusManager
31
 * @copyright Copyright &copy; Sam Stenvall 2016-
32
 * @license   https://www.gnu.org/licenses/gpl.html The GNU General Public License v2.0
33
 */
34
class WebSocketManager extends AbstractManager implements MessageComponentInterface, EventSubscriberInterface
35
{
36
37
	use DelegatesMessagesTrait;
38
39
	/**
40
	 * @var IoServer the Websocket server
41
	 */
42
	private $_websocket;
43
44
	/**
45
	 * @var \SplObjectStorage the connected clients
46
	 */
47
	private $_connectedClients;
48
49
50
	/**
51
	 * WebSocketManager constructor.
52
	 *
53
	 * @param Configuration   $configuration
54
	 * @param LoggerInterface $logger
55
	 * @param EventDispatcher $eventDispatcher
56
	 * @param LoopInterface   $loop
57
	 */
58
	public function __construct(
59
		Configuration $configuration,
60
		LoggerInterface $logger,
61
		EventDispatcher $eventDispatcher,
62
		LoopInterface $loop
63
	) {
64
		parent::__construct($configuration, $logger, $eventDispatcher);
65
66
		$this->_connectedClients = new \SplObjectStorage();
67
68
		// Create the socket to listen on
69
		$socket = new ServerSocket($loop);
70
		$socket->listen($configuration->getListenPort(), $configuration->getListenAddress());
71
72
		// Create the WebSocket server
73
		$this->_websocket = new IoServer(new HttpServer(new WsServer($this)), $socket, $loop);
74
	}
75
76
77
	/**
78
	 * @inheritdoc
79
	 */
80
	public static function getSubscribedEvents()
81
	{
82
		return [
83
			Events::MAIN_LOOP_STARTING      => 'onMainLoopStarted',
84
			Events::INSTANCE_STATUS_UPDATES => 'onInstanceStatusUpdates',
85
		];
86
	}
87
88
89
	/**
90
	 * Called right before the main loop is started
91
	 */
92
	public function onMainLoopStarted()
93
	{
94
		$this->logger->info('Starting the Websocket server on {address}:{port}', [
95
			'address' => $this->configuration->getListenAddress(),
96
			'port'    => $this->configuration->getListenPort(),
97
		]);
98
	}
99
100
101
	/**
102
	 * @param InstanceStatusUpdatesEvent $event
103
	 */
104
	public function onInstanceStatusUpdates(InstanceStatusUpdatesEvent $event)
105
	{
106
		$this->logger->debug('Broadcasting statuses to all clients');
107
		$message = new StatusUpdatesMessage($event->getInstanceStatusCollection());
108
109
		foreach ($this->_connectedClients as $client)
110
		{
111
			/* @var ConnectionInterface $client */
112
			$this->sendMessage($message, $client);
113
		}
114
	}
115
116
117
	/**
118
	 * @inheritdoc
119
	 */
120
	public function onOpen(ConnectionInterface $conn)
121
	{
122
		$this->logger->debug('Got client connection');
123
		$this->_connectedClients->attach($conn);
124
	}
125
126
127
	/**
128
	 * @inheritdoc
129
	 */
130
	public function onClose(ConnectionInterface $conn)
131
	{
132
		$this->logger->debug('Got client disconnect');
133
		$this->_connectedClients->detach($conn);
134
	}
135
136
137
	/**
138
	 * @inheritdoc
139
	 */
140
	public function onError(ConnectionInterface $conn, \Exception $e)
141
	{
142
		// TODO: Implement onError() method.
143
	}
144
145
146
	/**
147
	 * Dispatches incoming client messages to the appropriate handlers
148
	 *
149
	 * @param ConnectionInterface $from
150
	 * @param string              $msg
151
	 */
152
	public function onMessage(ConnectionInterface $from, $msg)
153
	{
154
		try
155
		{
156
			$message = MessageFactory::factory($msg);
157
158
			$this->logger->debug('Got message from client (type: {messageType})', [
159
				'messageType' => $message->getType(),
160
			]);
161
162
			// Attempt to delegate the message
163
			try
164
			{
165
				$this->sendMessage($this->tryDelegateMessage($message), $from);
166
			}
167
			catch (UnhandledMessageException $e)
168
			{
169
				$this->logger->error('Unhandled message (type: {messageType})', [
170
					'messageType' => $message->getType(),
171
				]);
172
			}
173
		}
174
		catch (MalformedRequestException $e)
175
		{
176
			$this->logger->error('Got malformed message from client (reason: {reason})', [
177
				'reason' => $e->getMessage(),
178
			]);
179
		}
180
		catch (UnknownRequestException $e)
181
		{
182
			// The server itself sometimes sends out messages that are received here, hence debug
183
			$this->logger->debug('Got unknown message from client');
184
		}
185
	}
186
187
188
	/**
189
	 * @param AbstractMessage     $message
190
	 * @param ConnectionInterface $target
191
	 */
192
	private function sendMessage(AbstractMessage $message, ConnectionInterface $target)
193
	{
194
		$target->send(json_encode($message));
195
	}
196
197
}
198