Completed
Push — master ( f97b5b...252fb7 )
by Sam
02:27
created

WebSocketManager::sendMessage()   A

Complexity

Conditions 1
Paths 1

Size

Total Lines 4
Code Lines 2

Duplication

Lines 0
Ratio 0 %

Importance

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