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 }