1 module handlers.response;
2 
3 import std.json : JSONValue, JSONException, parseJSON, toJSON;
4 import std.conv : to;
5 import utils.debugging : debugPrint;
6 import std.string : cmp;
7 import std.stdio : writeln;
8 import connection.connection;
9 import base.types;
10 import std.socket : Socket, SocketOSException, AddressFamily, SocketType, ProtocolType, parseAddress;
11 import utils.message : receiveMessage, sendMessage;
12 import handlers.handler;
13 import std.string : split;
14 import server.server : BesterServer;
15 import handlers.commands : Command;
16 
17 /**
18 * The type of the command the message handler wants
19 * us to run
20 */
21 private enum CommandType : ubyte
22 {
23 	/* Simple message flow (always end point) */
24 	SEND_CLIENTS, SEND_SERVERS, SEND_HANDLER,
25 
26 	/* Unknown command */
27 	UNKNOWN
28 }
29 
30 /**
31 * Represents a response message from a handler.
32 */
33 public final class HandlerResponse
34 {
35 	/**
36 	* The message-handler's response.
37 	*/
38 	private JSONValue messageResponse;
39 
40 	/* The command to be executed */
41 	private CommandType commandType;
42 
43 	/* The handler that caused such a response to be illicited */
44 	private MessageHandler handler;
45 
46 	/* The associated server */
47 	private BesterServer server;	
48 
49 	/**
50 	* Constructs a new `HandlerResponse` object that represents the
51 	* message handler's response message and the execution of it.
52 	*/
53 	this(BesterServer server, MessageHandler handler, JSONValue messageResponse)
54 	{
55 		/* Set the message-handler's response message */
56 		this.messageResponse = messageResponse;
57 
58 		/* Set the handler who caused this reponse to occur */
59 		this.handler = handler;
60 
61 		/* Set the server associated with this message handler */
62 		this.server = server;
63 
64 		/* Attempt parsing the message and error checking it */
65 		parse(messageResponse);
66 	}
67 
68 	private void parse(JSONValue handlerResponse)
69 	{
70 		/**
71 		 * Handles the response sent back to the server from the
72 		 * message handler.
73 		 */
74 
75 		 /* Get the status */
76 		 ulong statusCode;
77 
78 		 /* Error? */
79 		 bool error;
80 		
81 		/* TODO: Bounds checking, type checking */
82 		try
83 		{
84 			/* Get the header block */
85 			JSONValue headerBlock = handlerResponse["header"];
86 		
87 			/* Get the status */
88 			statusCode = to!(ulong)(headerBlock["status"].str());
89 			debugPrint("Status code: " ~ to!(string)(statusCode));
90 		
91 			/* If the status is 0, then it is all fine */
92 			if(statusCode == 0)
93 			{
94 				debugPrint("Status is fine, the handler ran correctly");
95 						
96 				/* The command block */
97 				JSONValue commandBlock = headerBlock["command"];
98 						
99 				/**
100 				 * Get the command that the message handler wants the
101 				 * server to run.
102 				 */
103 				string serverCommand = commandBlock["type"].str;
104 				debugPrint("Handler->Server command: \"" ~ serverCommand ~ "\"");
105 						
106 				/* Check the command to be run */
107 				if(cmp(serverCommand, "sendClients") == 0)
108 				{
109 					/* Set the command type to SEND_CLIENTS */
110 					commandType = CommandType.SEND_CLIENTS;
111 
112 					/* TODO: Error check and do accesses JSON that would be done in `.execute` */
113 				}
114 				else if(cmp(serverCommand, "sendServers") == 0)
115 				{
116 					/* Set the command type to SEND_SERVERS */
117 					commandType = CommandType.SEND_SERVERS;
118 
119 					/* TODO: Error check and do accesses JSON that would be done in `.execute` */
120 				}
121 				else if(cmp(serverCommand, "sendHandler") == 0)
122 				{
123 					/* Set the command type to SEND_HAANDLER */
124 					commandType = CommandType.SEND_HANDLER;
125 
126 					/* TODO: Error check and do accesses JSON that would be done in `.execute` */
127 				}
128 				else
129 				{
130 					/* TODO: Error handling */
131 					debugPrint("The message handler is using an invalid command");
132 				}
133 			}
134 			else
135 			{
136 				/* If the message handler returned a response in error */
137 				debugPrint("Message handler returned an error code: " ~ to!(string)(statusCode));
138 				error = true;
139 			}
140 		}
141 		catch(JSONException exception)
142 		{
143 			debugPrint("<<< There was an error handling the response message >>>\n\n" ~ exception.toString());
144 			error = true;
145 		}
146 
147 		/**
148 		 * If an error was envountered anyway down the processing of the
149 		 * message-handler then raise a `ResponseError` exception.
150 		 */
151 		if(error)
152 		{
153 			throw new ResponseError(messageResponse, statusCode);
154 		}
155 	}
156 
157 	/**
158 	* Executes the command. Either `sendClients`, `sendServers`
159 	* or `sendHandler`.
160 	*/
161 	public void execute(BesterConnection originalRequester, string messageID)
162 	{
163 		/* If the command is SEND_CLIENTS */
164 		if(commandType == CommandType.SEND_CLIENTS)
165 		{
166 			/* Get the list of clients to send to */
167 			string[] clients;
168 			JSONValue[] clientList = messageResponse["header"]["command"]["data"].array();
169 			for(ulong i = 0; i < clientList.length; i++)
170 			{
171 				clients ~= clientList[i].str();
172 			}
173 
174 			debugPrint("Users wanting to send to: " ~ to!(string)(clients));
175 
176 			/* Find the users that are wanting to be sent to */
177 			BesterConnection[] connectionList = originalRequester.server.getClients(clients);
178 			//debugPrint("Users matched online on server: " ~ to!(string)(connectionList));
179 
180 			/* The fully response message to send back */
181 			JSONValue clientPayload;
182 
183 			/* Set the header of the response */
184 			JSONValue headerBlock;
185 			headerBlock["messageType"] = "receivedMessage";
186 			clientPayload["header"] = headerBlock;
187 
188 			/* Set the payload of the response */
189 			JSONValue payloadBlock;
190 			payloadBlock["data"] = messageResponse["data"];
191 			payloadBlock["type"] = handler.getPluginName();
192 			clientPayload["payload"] = payloadBlock;
193 
194 
195 			/**
196 			* Loop through each BesterConnection in connectionList and
197 			* send the message-handler payload response message to each
198 			* of them.
199 			*/
200 			bool allSuccess = true;
201 			for(ulong i = 0; i < connectionList.length; i++)
202 			{
203 				/* Get the conneciton */
204 				BesterConnection clientConnection = connectionList[i];
205 
206 				try
207 				{
208 					/* Get the client's socket */
209 					Socket clientSocket = clientConnection.getSocket();
210 					//debugPrint("IsAlive?: " ~ to!(string)(clientSocket.isAlive()));
211 					
212 					/* Send the message to the client */
213 					debugPrint("Sending handler's response to client \"" ~ clientConnection.toString() ~ "\"...");
214 					sendMessage(clientSocket, clientPayload);
215 					debugPrint("Sending handler's response to client \"" ~ clientConnection.toString() ~ "\"... [sent]");
216 				}
217 				catch(SocketOSException exception)
218 				{
219 					/**
220 					* If there was an error sending to the client, this can happen
221 					* if the client has disconnected but hasn't yet been removed from
222 					* the connections array and hence we try to send on a dead socket
223 					* or get the remoteAddress on a dead socket, which causes a
224 					* SocketOSException to be called.
225 					*/
226 					debugPrint("Attempted interacting with dead socket");
227 					allSuccess = false;
228 				}
229 			}
230 
231 			debugPrint("SEND_CLIENTS: Completed run");
232 
233 			/**
234 			* Send a status report here.
235 			*/
236 			originalRequester.sendStatusReport(cast(BesterConnection.StatusType)!allSuccess, messageID);
237 		}
238 		else if (commandType == CommandType.SEND_SERVERS)
239 		{
240 			/* Get the list of servers to send to */
241 			string[] servers;
242 			JSONValue[] serverList = messageResponse["header"]["command"]["data"].array();
243 			for(ulong i = 0; i < serverList.length; i++)
244 			{
245 				servers ~= serverList[i].str();
246 			}
247 													
248 			/* TODO: Implement me */
249 			writeln("Servers wanting to send to ", servers);
250 
251 
252 			/* The fully response message to send back */
253 			JSONValue serverPayload;
254 
255 			/* Set the header of the response */
256 			JSONValue headerBlock;
257 			headerBlock["scope"] = "server";
258 			serverPayload["header"] = headerBlock;
259 
260 			/* Set the payload of the response */
261 			JSONValue payloadBlock;
262 			payloadBlock["data"] = messageResponse["data"];
263 			payloadBlock["type"] = handler.getPluginName();
264 			serverPayload["payload"] = payloadBlock;
265 
266 
267 			/* Attempt connecting to each server and sending the payload */
268 			bool allSuccess = true;
269 			for(ulong i = 0; i < servers.length; i++)
270 			{
271 				/* Get the current server address and port */
272 				string serverString = servers[i];
273 				string host = serverString.split(":")[0];
274 				ushort port = to!(ushort)(serverString.split(":")[1]);
275 
276 				try
277 				{
278 					Socket serverConnection = new Socket(AddressFamily.INET, SocketType.STREAM, ProtocolType.TCP);
279 
280 					/* Connect to the server */
281 					debugPrint("Connecting to server \"" ~ serverConnection.toString() ~ "\"...");
282 					serverConnection.connect(parseAddress(host, port));
283 					debugPrint("Connecting to server \"" ~ serverConnection.toString() ~ "\"... [connected]");
284 
285 					/* Send the payload */
286 					debugPrint("Sending handler's response to server \"" ~ serverConnection.toString() ~ "\"...");
287 					sendMessage(serverConnection, serverPayload);
288 					debugPrint("Sending handler's response to server \"" ~ serverConnection.toString() ~ "\"... [sent]");
289 
290 					/* Close the connection to the server */
291 					serverConnection.close();
292 					debugPrint("Closed connection to server \"" ~ serverConnection.toString() ~ "\"");
293 				}
294 				catch(Exception e)
295 				{
296 					/* TODO: Be more specific with the above exception type */
297 					debugPrint("Error whilst sending payload to server: " ~ e.toString());
298 					allSuccess = false;
299 				}
300 			}
301 
302 			debugPrint("SEND_SERVERS: Completed run");
303 
304 			/**
305 			* Send a status report here.
306 			*/
307 			originalRequester.sendStatusReport(cast(BesterConnection.StatusType)!allSuccess, messageID);
308 		}
309 		else if (commandType == CommandType.SEND_HANDLER)
310 		{
311 			/* Name of the handler to send the message to */
312 			string handler = messageResponse["header"]["command"]["data"].str();
313 			debugPrint("Handler to forward to: " ~ handler);
314 
315 			/* Lookup the payloadType handler */
316 			MessageHandler chosenHandler = server.findHandler(handler);
317 
318 			/* Send the data to the message handler */
319 			HandlerResponse handlerResponse = chosenHandler.handleMessage(messageResponse["data"]);
320 
321 			/* Execute the code (this here, recursive) */
322 			handlerResponse.execute(originalRequester, messageID);
323 
324 			debugPrint("SEND_HANDLER: Completed run");
325 		}
326 		else
327 		{
328 			/* TODO: */
329 		}
330 	}
331 
332 	override public string toString()
333 	{
334 		return messageResponse.toPrettyString();
335 	}
336 }
337 
338 /**
339 * Represents an error in handling the response.
340 */
341 public final class ResponseError : BesterException
342 {
343 
344 	/* */
345 
346 	/* The status code that resulted in the response handling error */
347 	private ulong statusCode;
348 
349 	this(JSONValue messageResponse, ulong statusCode)
350 	{
351 		/* TODO: Set message afterwards again */
352 		super("");
353 	}
354 }