FRED™  3.0
FRED™: Framework for Rapid and Easy Development
Server.php
Go to the documentation of this file.
1 <?php
2 
3 namespace Rsi\Fred\Socket;
4 
5 class Server extends \Rsi\Thing implements \Ratchet\MessageComponentInterface{
6 
7  public $clients = []; //!< Array with all connected clients (key = resource ID, value = Client object).
8  public $id = null; //!< Resource ID for current client.
9 
10  protected $_loop = null;
11  protected $_client = null; //!< Client/public WebSocket.
12  protected $_local = null; //!< Local socket (raw).
13  protected $_server = null;
14 
15  protected $_socket = null;
16  protected $_tokens = []; //!< Array with allowed client tokens (key = token, value = array with keys 'time' and 'params').
17 
18  public function __construct($socket){
19  $this->_socket = $socket;
20  }
21  /**
22  * Client connection resource ID's.
23  * @param string $controller_name Get ID's for clients with this controller (empty = all).
24  * @return array
25  */
26  public function ids($controller_name = null){
27  $ids = [];
28  foreach($this->clients as $id => $client)
29  if(!$controller_name || ($client->controller->name == $controller_name)) $ids[] = $id;
30  return $ids;
31  }
32  /**
33  * Send data to a client.
34  * @param int $id Client connection resource ID.
35  * @param mixed $data
36  */
37  public function send($id,$data){
38  $this->clients[$id]->connection->send(json_encode($data));
39  }
40  /**
41  * Broadcast data to all clients.
42  * @param mixed $data
43  * @param string $controller_name Only to clients with this controller (empty = all).
44  * @param array $except_ids Exclude cients with these resource ID's.
45  * @return array Resource ID's that received the data.
46  */
47  public function broadcast($data,$controller_name = null,$except_ids = null){
48  $ids = array_diff($this->ids($controller_name),$except_ids ?: []);
49  foreach($ids as $id) $this->send($id,$data);
50  return $ids;
51  }
52  /**
53  * Create a WebSocket access token through a local connection.
54  * @param array $data Array with a key for the 'token'.
55  * @return array Key for 'time' until which the token is valid.
56  */
57  protected function localToken($data){
58  if($token = \Rsi\Record::get($data,'token')){
59  $this->_tokens[$token] = ['time' => $time = time() + $this->_socket->tokenTtl,'params' => $data];
60  return ['time' => $time];
61  }
62  }
63  /**
64  * Add or update client data (session data).
65  * @param array $data Array with key 'sessionId'. All other data is merged with the client data.
66  * @return array Key for 'count' (number of data sets modified).
67  */
68  protected function localParams($data){
69  $count = 0;
70  if($session_id = \Rsi\Record::get($data,'sessionId')){
71  foreach($this->_tokens as &$token_data) if($token_data['sessionId'] == $session_id){
72  $token_data = array_merge($token_data,$data);
73  $count++;
74  }
75  unset($token_data);
76  foreach($this->clients as $client) if($client->params['sessionId'] == $session_id){
77  $client->params = array_merge($client->params,$data);
78  $count++;
79  }
80  return ['count' => 0];
81  }
82  }
83  /**
84  * Active session ID's through a local connection.
85  * @param array $data Array with an optional key 'controller' (if set only the session ID' for clients with this controller
86  * are returned).
87  * @return array Key for 'sessionIds' (array with active session ID's).
88  */
89  protected function localSessionIds($data){
90  $controller_name = \Rsi\Record::get($data,'controller');
91  $session_ids = [];
92  foreach($this->clients as $id => $client)
93  if(!$controller_name || ($client->controller->name == $controller_name))
94  $session_ids[$id] = $client->params['sessionId'];
95  return ['sessionIds' => $session_ids];
96  }
97  /**
98  * Close one or more connections through a local connection.
99  * @param array $data Array with optional keys 'controller' (if set only the connections for clients with this controller
100  * are closed) and 'sessions' (if set only the connections for clients with these sesion ID's are closed).
101  * @return array Key for 'count' (number of sessions closed).
102  */
103  protected function localClose($data){
104  $controller_name = \Rsi\Record::get($data,'controller');
105  $session_ids = \Rsi\Record::get($data,'sessions');
106  $count = 0;
107  foreach($this->clients as $client) if(
108  (!$session_ids || in_array($client->params['sessionId'],$session_ids)) &&
109  (!$controller_name || ($client->controller->name == $controller_name))
110  ){
111  $client->connection->close();
112  $count++;
113  }
114  return ['count' => $count];
115  }
116  /**
117  * Request a shutdown of the WebSocket server through a local connection.
118  * @param array $data Expects nothing.
119  */
120  protected function localShutdown($data){
121  $this->log->notice('Shutdown requested',__FILE__,__LINE__);
122  $this->loop->stop();
123  }
124  /**
125  * Broadcast data to all clients through a local connection.
126  * @param array $data Array with a key 'data' (data to send to the clients), and optional keys 'controller' (if set only the
127  * clients with this controller will receive the data) and 'except' (if set clients with these sesion ID's will not receive
128  * the data).
129  * @return array Key for 'count' (number of clients that received the data).
130  */
131  protected function localBroadcast($data){
132  return ['count' => count($this->broadcast(
133  \Rsi\Record::get($data,'data'),
134  \Rsi\Record::get($data,'controller'),
135  \Rsi\Record::get($data,'except')
136  ))];
137  }
138  /**
139  * Local data handler.
140  * @param mixed $data Data received from HTTP server.
141  * @return mixed Response to HTTP server.
142  * @see send()
143  */
144  protected function onLocalData($data){
145  if($controller_name = \Rsi\Record::get($data,'controller')){
146  $_POST = $data;
147  $this->_socket->component('front')->controller($controller_name)->execute();
148  return $this->_socket->component('request')->data;
149  }
150  elseif(method_exists($this,$method = 'local' . ucfirst(\Rsi\Record::get($data,'action'))))
151  return call_user_func([$this,$method],$data);
152  return false;
153  }
154  /**
155  * Called on a new local connection.
156  * @param \React\Socket\ConnectionInterface $connection
157  */
158  public function onLocalConnection($connection){
159  $log = $this->log;
160  if(in_array($connection->getRemoteAddress(),$this->_socket->localClients)){
161  $log->debug('New local connection',__FILE__,__LINE__);
162  $connection->on('data',function($data) use ($connection,$log){
163  $log->debug('Received local data (' . strlen($data) . ' bytes)',['data' => $data]);
164  $connection->write(serialize($this->onLocalData(unserialize($data))));
165  });
166  }
167  else{
168  $log->notice('Local connection not allowed',__FILE__,__LINE__,['connection' => $connection]);
169  $connection->close();
170  }
171  }
172  /**
173  * Called on a new client connection.
174  * @param \Ratchet\ConnectionInterface $connection
175  */
176  public function onOpen(\Ratchet\ConnectionInterface $connection){
177  $log = $this->log;
178  $request = $connection->WebSocket->request;
179  if($origins = $this->_socket->clientOrigins){
180  $origin = parse_url($origin = (string)$request->getHeader('Origin'),PHP_URL_HOST) ?: $origin;
181  if(!in_array($origin,$origins)){
182  $log->notice('Origin not allowed',__FILE__,__LINE__,['connection' => $connection,'origin' => $origin]);
183  return $connection->close();
184  }
185  }
186  parse_str($request->getQuery(),$query);
187  $time = time();
188  foreach($this->_tokens as $token => $params) if($params['time'] < $time) unset($this->_tokens[$token]);
189  if(!array_key_exists($token = $query['token'] ?? null,$this->_tokens)){
190  $log->notice('Token too old ',__FILE__,__LINE__,['connection' => $connection]);
191  return $connection->close();
192  }
193  $_SESSION = $params = $this->_tokens[$token]['params'];
194  $_SERVER['REMOTE_ADDR'] = $connection->remoteAddress;
195  $router = $this->_socket->component('router');
196  $router->execute($request->getPath());
197  $controller = $this->_socket->component('front')->controller($router->controllerName);
198  $_SESSION = [];
199  unset($_SERVER['REMOTE_ADDR']);
200  $log->debug("New connection ({$connection->resourceId}) for {$controller->name}",__FILE__,__LINE__);
201  $this->clients[$connection->resourceId] = new Client($connection,$params,$controller);
202  unset($this->_tokens[$token]);
203  }
204 
205  public function onClose(\Ratchet\ConnectionInterface $connection){
206  $this->log->debug("Connection closed ({$connection->resourceId})",__FILE__,__LINE__);
207  unset($this->clients[$connection->resourceId]);
208  }
209 
210  public function onError(\Ratchet\ConnectionInterface $connection,\Exception $exception){
211  $this->log->error($exception->getMessage(),__FILE__,__LINE__);
212  $connection->close();
213  }
214 
215  public function onMessage(\Ratchet\ConnectionInterface $connection,$data){
216  $data = json_decode($data,true);
217  $this->log->debug("Message from connection {$connection->resourceId}",__FILE__,__LINE__,['data' => $data]);
218  $client = $this->clients[$this->id = $connection->resourceId];
219  $client->controller->reset();
220  $_POST = $data;
221  $_SESSION = $client->params;
222  $_SERVER['REMOTE_ADDR'] = $connection->remoteAddress;
223  $client->controller->execute();
224  $this->id = null;
225  $_POST = $_SESSION = [];
226  unset($_SERVER['REMOTE_ADDR']);
227  }
228  /**
229  * Run the WebSocket server.
230  */
231  public function run(){
232  $this->server->run();
233  }
234 
235  protected function getLocal(){
236  $this->getLoop();
237  return $this->_local;
238  }
239 
240  protected function getLog(){
241  return $this->_socket->component('log');
242  }
243 
244  protected function getLoop(){
245  if(!$this->_loop){
246  if(!$this->_socket->clientPort) throw new \Exception('No client port');
247  $this->_loop = \React\EventLoop\Factory::create();
248  $this->_client = new \React\Socket\Server($this->_loop);
249  $this->_client->listen($port = $this->_socket->clientLocalPort ?: $this->_socket->clientPort,'0.0.0.0');
250  $this->log->debug("Listening on port $port for client connections");
251  $this->_local = new \React\Socket\Server($this->_loop);
252  $this->_local->listen($port = $this->_socket->localPort,'0.0.0.0');
253  $this->log->debug("Listening on port $port for local connections");
254  $this->_local->on('connection',[$this,'onLocalConnection']);
255  }
256  return $this->_loop;
257  }
258 
259  protected function getClient(){
260  $this->getLoop();
261  return $this->_client;
262  }
263 
264  protected function getServer(){
265  if(!$this->_server) $this->_server = new \Ratchet\Server\IoServer(
266  new \Ratchet\Http\HttpServer(new \Ratchet\WebSocket\WsServer($this)),
267  $this->client,
268  $this->loop
269  );
270  return $this->_server;
271  }
272 
273 }
$_client
Client/public WebSocket.
Definition: Server.php:11
localSessionIds($data)
Active session ID&#39;s through a local connection.
Definition: Server.php:89
localBroadcast($data)
Broadcast data to all clients through a local connection.
Definition: Server.php:131
$clients
Array with all connected clients (key = resource ID, value = Client object).
Definition: Server.php:7
localToken($data)
Create a WebSocket access token through a local connection.
Definition: Server.php:57
localParams($data)
Add or update client data (session data).
Definition: Server.php:68
Basic object.
Definition: Thing.php:13
onOpen(\Ratchet\ConnectionInterface $connection)
Called on a new client connection.
Definition: Server.php:176
onMessage(\Ratchet\ConnectionInterface $connection, $data)
Definition: Server.php:215
onClose(\Ratchet\ConnectionInterface $connection)
Definition: Server.php:205
onLocalConnection($connection)
Called on a new local connection.
Definition: Server.php:158
broadcast($data, $controller_name=null, $except_ids=null)
Broadcast data to all clients.
Definition: Server.php:47
localShutdown($data)
Request a shutdown of the WebSocket server through a local connection.
Definition: Server.php:120
onLocalData($data)
Local data handler.
Definition: Server.php:144
run()
Run the WebSocket server.
Definition: Server.php:231
onError(\Ratchet\ConnectionInterface $connection,\Exception $exception)
Definition: Server.php:210
$_local
Local socket (raw).
Definition: Server.php:12
$id
Resource ID for current client.
Definition: Server.php:8
$_tokens
Array with allowed client tokens (key = token, value = array with keys &#39;time&#39; and &#39;params&#39;)...
Definition: Server.php:16
__construct($socket)
Definition: Server.php:18
ids($controller_name=null)
Client connection resource ID&#39;s.
Definition: Server.php:26
send($id, $data)
Send data to a client.
Definition: Server.php:37
localClose($data)
Close one or more connections through a local connection.
Definition: Server.php:103