1 module cheetah.socketserver; 2 3 import vibe.core.net : listenTCP; 4 import vibe.core.net : TCPListener, TCPConnection; 5 6 import cheetah.socketeventtype; 7 import cheetah.socketeventchain; 8 import cheetah.socketevent; 9 import cheetah.socketclient; 10 import cheetah.socketeventargs; 11 12 /// Wrapper for a tcp socket server. 13 class SocketServer(T) { 14 private: 15 /// The IP Address tied to the server. 16 string _ip; 17 18 /// The port tied to the server. 19 ushort _port; 20 21 /// All listeners tied to the server. 22 TCPListener[] _listeners; 23 24 /// The event args tied to the server. 25 SocketEventArgs!T _eventArgs; 26 27 /// The start events. 28 SocketEventChain!T _startEvents; 29 30 /// The stop events. 31 SocketEventChain!T _stopEvents; 32 33 /// The connect events. 34 SocketEventChain!T _connectEvents; 35 36 /// The disconnect events. 37 SocketEventChain!T _disconnectEvents; 38 39 /// The receive events. 40 SocketEventChain!T _receiveEvents; 41 42 /// The error events. 43 SocketEventChain!T _errorEvents; 44 45 /// Boolean determining whether the connect events should be copied. 46 bool _copyConnectEvents; 47 48 /// Boolean determining whether the disconnect events should be copied. 49 bool _copyDisconnectEvents; 50 51 /// Boolean determining whether the receive events should be copied. 52 bool _copyReceiveEvents; 53 54 /// Boolean determining whether the error events should be copied. 55 bool _copyErrorEvents; 56 57 /// Boolean determining whether the server is running or not. 58 bool _running; 59 60 /// Boolean determining whether clients should be stored or not. 61 bool _storeClients; 62 63 /// A collection of clients. 64 (SocketClient!T)[size_t] _clients; 65 66 /// The next client id. 67 size_t _nextClientId; 68 69 public: 70 /** 71 * Creates a new tcp socket server. 72 * Params: 73 * ip = The IP address to listen for connections at. 74 * port = The port to listen for connections at. 75 */ 76 this(string ip, ushort port) { 77 _ip = ip; 78 _port = port; 79 _eventArgs = new SocketEventArgs!T(this, null); 80 } 81 82 /** 83 * Creates a new tcp socket server. 84 * Params: 85 * port = The port to listen for connections at. 86 */ 87 this(ushort port) { 88 _port = port; 89 _eventArgs = new SocketEventArgs!T(this, null); 90 } 91 92 @property { 93 /// Gets a boolean determining whether the connect events should be copied or not. 94 bool copyConnectEvents() { return _copyConnectEvents; } 95 96 /** 97 * Sets a boolean determining whether the connect events should be copied or not. 98 * Params: 99 * shouldCopy = Boolean determining whether the connect events should be copied or not. 100 */ 101 void copyConnectEvents(bool shouldCopy) { 102 _copyConnectEvents = shouldCopy; 103 } 104 105 /// Gets a boolean determining whether the disconnect events should be copied or not. 106 bool copyDisconnectEvents() { return _copyDisconnectEvents; } 107 108 /** 109 * Sets a boolean determining whether the disconnect events should be copied or not. 110 * Params: 111 * shouldCopy = Boolean determining whether the disconnect events should be copied or not. 112 */ 113 void copyDisconnectEvents(bool shouldCopy) { 114 _copyDisconnectEvents = shouldCopy; 115 } 116 117 /// Gets a boolean determining whether the receive events should be copied or not. 118 bool copyReceiveEvents() { return _copyReceiveEvents; } 119 120 /** 121 * Sets a boolean determining whether the receive events should be copied or not. 122 * Params: 123 * shouldCopy = Boolean determining whether the receive events should be copied or not. 124 */ 125 void copyReceiveEvents(bool shouldCopy) { 126 _copyReceiveEvents = shouldCopy; 127 } 128 129 /// Gets a boolean determining whether the error events should be copied or not. 130 bool copyErrorEvents() { return _copyErrorEvents; } 131 132 /** 133 * Sets a boolean determining whether the error events should be copied or not. 134 * Params: 135 * shouldCopy = Boolean determining whether the error events should be copied or not. 136 */ 137 void copyErrorEvents(bool shouldCopy) { 138 _copyErrorEvents = shouldCopy; 139 } 140 141 /// Gets a boolean determining whether clients should be stored or not. 142 bool storeClients() { return _storeClients; } 143 144 /** 145 * Sets a boolean determining whether clients should be stored or not. 146 * Params: 147 * shouldStore = Boolean determining whether clients should be stored or not. 148 */ 149 void storeClients(bool shouldStore) { 150 _storeClients = shouldStore; 151 } 152 } 153 154 /** 155 * Attachs an event. 156 * Params: 157 * eventType = The event type to attach. 158 * event = The event handler to attach. 159 */ 160 void attach(SocketEventType eventType, SocketEvent!T event) { 161 final switch (eventType) { 162 case SocketEventType.start: { 163 _startEvents = new SocketEventChain!T([event]); 164 break; 165 } 166 case SocketEventType.stop: { 167 _stopEvents = new SocketEventChain!T([event]); 168 break; 169 } 170 case SocketEventType.connect: { 171 _connectEvents = new SocketEventChain!T([event]); 172 break; 173 } 174 case SocketEventType.disconnect: { 175 _disconnectEvents = new SocketEventChain!T([event]); 176 break; 177 } 178 case SocketEventType.receive: { 179 _receiveEvents = new SocketEventChain!T([event]); 180 break; 181 } 182 case SocketEventType.error: { 183 _errorEvents = new SocketEventChain!T([event]); 184 break; 185 } 186 } 187 } 188 189 /** 190 * Attachs a chain of events. 191 * Params: 192 * eventType = The event type to attach. 193 * events = The chain of event handlers to attach. 194 */ 195 void attach(SocketEventType eventType, SocketEvent!T[] events) { 196 final switch (eventType) { 197 case SocketEventType.start: { 198 _startEvents = new SocketEventChain!T(events); 199 break; 200 } 201 case SocketEventType.stop: { 202 _stopEvents = new SocketEventChain!T(events); 203 break; 204 } 205 case SocketEventType.connect: { 206 _connectEvents = new SocketEventChain!T(events); 207 break; 208 } 209 case SocketEventType.disconnect: { 210 _disconnectEvents = new SocketEventChain!T(events); 211 break; 212 } 213 case SocketEventType.receive: { 214 _receiveEvents = new SocketEventChain!T(events); 215 break; 216 } 217 case SocketEventType.error: { 218 _errorEvents = new SocketEventChain!T(events); 219 break; 220 } 221 } 222 } 223 224 /** 225 * Moves onto the next event handler in a chain. 226 * Params: 227 * eventType = The event type to move onto next event handler. 228 */ 229 void moveNext(SocketEventType eventType) { 230 final switch (eventType) { 231 case SocketEventType.start: { 232 _startEvents.moveNext(); 233 break; 234 } 235 case SocketEventType.stop: { 236 _stopEvents.moveNext(); 237 break; 238 } 239 case SocketEventType.connect: { 240 _connectEvents.moveNext(); 241 break; 242 } 243 case SocketEventType.disconnect: { 244 _disconnectEvents.moveNext(); 245 break; 246 } 247 case SocketEventType.receive: { 248 _receiveEvents.moveNext(); 249 break; 250 } 251 case SocketEventType.error: { 252 _errorEvents.moveNext(); 253 break; 254 } 255 } 256 } 257 258 /// Starts the server. 259 void start() { 260 if (_running) { 261 return; 262 } 263 264 try { 265 fireEvent(SocketEventType.start, _eventArgs); 266 } 267 catch (Exception e) { 268 report(e, this); 269 } 270 catch (Throwable e) { 271 report(new Exception(e.toString()), this); 272 273 throw e; 274 } 275 276 setup(); 277 278 _running = true; 279 } 280 281 /// Stops the server. 282 void stop() { 283 if (!_running) { 284 return; 285 } 286 287 try { 288 foreach (listener; _listeners) { 289 listener.stopListening(); 290 } 291 } 292 catch (Exception e) { 293 report(e, this); 294 } 295 catch (Throwable e) { 296 report(new Exception(e.toString()), this); 297 298 throw e; 299 } 300 301 _running = false; 302 } 303 304 /** 305 * Gets a client by its client id. 306 * Params: 307 * clientId = The id of the client to retrieve. 308 * Returns: 309 * The client associated with the client id or null if no client is found by the id. 310 */ 311 auto getClient(size_t clientId) { 312 return _clients.get(clientId, null); 313 } 314 315 /// Gets all clients. 316 auto getClients() { 317 return _clients.values; 318 } 319 320 package(cheetah) { 321 /** 322 * Fires a socket event. 323 * Params: 324 * eventType = The event type to fire. 325 * e = The event args to pass to the handler. 326 */ 327 void fireEvent(SocketEventType eventType, SocketEventArgs!T e) { 328 final switch (eventType) { 329 case SocketEventType.start: { 330 if (_startEvents) _startEvents(e); 331 break; 332 } 333 case SocketEventType.stop: { 334 if (_stopEvents) _stopEvents(e); 335 break; 336 } 337 case SocketEventType.connect: { 338 if (_connectEvents) _connectEvents(e); 339 break; 340 } 341 case SocketEventType.disconnect: { 342 if (_disconnectEvents) _disconnectEvents(e); 343 break; 344 } 345 case SocketEventType.receive: { 346 if (_receiveEvents) _receiveEvents(e); 347 break; 348 } 349 case SocketEventType.error: { 350 if (_errorEvents) _errorEvents(e); 351 break; 352 } 353 } 354 } 355 356 /** 357 * Reports an error to the error handler. 358 * Params: 359 * error = The error to handle. 360 * server = The server tied to the error. 361 * client = The client tied to the error. 362 */ 363 void report(Exception error, SocketServer!T server = null, SocketClient!T client = null) { 364 fireEvent(SocketEventType.error, new SocketEventArgs!T(server, client, error)); 365 } 366 367 /** 368 * Removes a client from the internal client storage. 369 * Params: 370 * clientId = The client id to remove. 371 */ 372 void removeClient(size_t clientId) { 373 if (!_storeClients) { 374 return; 375 } 376 377 _clients.remove(clientId); 378 } 379 } 380 381 private: 382 /// Sets up the listeners for the server. 383 void setup() { 384 try { 385 if (_ip) { 386 _listeners = [listenTCP(_port, &handleConnections, _ip)]; 387 } 388 else { 389 _listeners = listenTCP(_port, &handleConnections); 390 } 391 } 392 catch (Exception e) { 393 report(e, this); 394 } 395 catch (Throwable e) { 396 report(new Exception(e.toString()), this); 397 398 throw e; 399 } 400 } 401 402 /** 403 * Handles a connection. 404 * Params: 405 * connection = The low-level tcp connection that has connected. 406 */ 407 void handleConnections(TCPConnection connection) { 408 auto client = new SocketClient!T(this, connection); 409 client.setupEvents( 410 _copyConnectEvents ? _connectEvents : null, 411 _copyDisconnectEvents ? _disconnectEvents : null, 412 _copyReceiveEvents ? _receiveEvents : null, 413 _copyErrorEvents ? _errorEvents : null 414 ); 415 416 if (_storeClients) { 417 client.clientId = _nextClientId; 418 _nextClientId++; 419 420 _clients[client.clientId] = client; 421 } 422 423 client.fireEvent(SocketEventType.connect, client.eventArgs); 424 425 client.process(); 426 } 427 }