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 }