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 }