besterd/source/connection/connection.d

372 lines
9.5 KiB
D
Raw Normal View History

2020-04-24 17:48:22 +02:00
module connection.connection;
2020-04-16 15:02:34 +02:00
import utils.debugging : debugPrint;
import std.conv : to;
import std.socket : Socket, AddressFamily, SocketType, ProtocolType, parseAddress, SocketFlags, Address;
2020-04-16 17:05:56 +02:00
import core.thread : Thread;
2020-04-17 18:47:05 +02:00
import std.stdio : writeln, File;
2020-04-18 15:25:22 +02:00
import std.json : JSONValue, parseJSON, JSONException, JSONType, toJSON;
import std.string : cmp;
import handlers.handler;
2020-04-24 17:48:22 +02:00
import listeners.listener;
import server.server;
import handlers.response;
import connection.message;
import base.net;
2020-04-28 12:35:22 +02:00
import base.types;
public final class BesterConnection : Thread
2020-04-16 17:05:56 +02:00
{
/* The socket to the client */
private Socket clientConnection;
2020-04-16 23:25:22 +02:00
/* The server backend */
2020-04-25 18:58:32 +02:00
public BesterServer server;
2020-04-16 23:25:22 +02:00
2020-04-19 18:53:16 +02:00
/* The client's credentials */
2020-04-26 15:01:48 +02:00
private string username;
private string password;
2020-04-19 18:53:16 +02:00
2020-04-26 17:01:24 +02:00
/* The connection scope */
public enum Scope
{
CLIENT,
SERVER,
UNKNOWN
}
/* The type of this connection */
private Scope connectionType = Scope.UNKNOWN;
2020-04-26 15:01:48 +02:00
public Scope getType()
{
2020-04-26 15:01:48 +02:00
return connectionType;
}
public Socket getSocket()
{
return clientConnection;
}
2020-04-16 23:25:22 +02:00
this(Socket clientConnection, BesterServer server)
2020-04-16 17:05:56 +02:00
{
/* Save socket and set thread worker function pointer */
super(&run);
this.clientConnection = clientConnection;
2020-04-16 23:25:22 +02:00
this.server = server;
debugPrint("New client handler spawned for " ~ clientConnection.remoteAddress().toAddrString());
2020-04-16 17:05:56 +02:00
}
2020-04-25 18:58:32 +02:00
override public string toString()
{
return username ~ "@" ~ clientConnection.remoteAddress().toAddrString();
2020-04-25 18:58:32 +02:00
}
public string[] getCredentials()
{
2020-04-26 15:01:48 +02:00
return [username, password];
2020-04-25 18:58:32 +02:00
}
2020-04-16 17:05:56 +02:00
/* Read/send loop */
private void run()
{
debugPrint("<<< Begin read/send loop >>>");
while(clientConnection.isAlive())
2020-04-16 16:39:31 +02:00
{
/* Received JSON message */
JSONValue receivedMessage;
2020-04-28 12:35:22 +02:00
/* Attempt to receive a message */
try
{
2020-04-28 12:35:22 +02:00
/* Receive a message */
receiveMessage(clientConnection, receivedMessage);
/**
2020-04-28 12:35:22 +02:00
* If the message was received successfully then
* process the message. */
processMessage(receivedMessage);
}
2020-04-28 12:35:22 +02:00
catch(BesterException exception)
{
2020-04-28 12:35:22 +02:00
debugPrint("Error in read/write loop: " ~ exception.toString());
break;
}
2020-04-16 18:46:26 +02:00
}
debugPrint("<<< End read/send loop >>>");
2020-04-16 18:46:26 +02:00
}
2020-04-26 18:43:57 +02:00
/**
* Sends the payload to the designated message handler and gets
* the response message from the handler and returns it.
*/
private JSONValue handlerRun(MessageHandler chosenHandler, JSONValue payload)
2020-04-19 14:36:55 +02:00
{
2020-04-28 12:35:22 +02:00
/* TODO: If unix sock is down, this just hangs, we should see if the socket file exists first */
2020-04-19 22:00:53 +02:00
/* Handler's UNIX domain socket */
Socket handlerSocket = chosenHandler.getNewSocket();
2020-04-26 13:16:55 +02:00
/* Send the payload to the message handler */
2020-04-19 22:00:53 +02:00
debugPrint("Sending payload over to handler for \"" ~ chosenHandler.getPluginName() ~ "\".");
sendMessage(handlerSocket, payload);
2020-04-19 14:36:55 +02:00
2020-04-26 18:43:57 +02:00
/* Get the payload sent from the message handler in response */
2020-04-19 22:00:53 +02:00
debugPrint("Waiting for response from handler for \"" ~ chosenHandler.getPluginName() ~ "\".");
JSONValue response;
2020-04-28 12:35:22 +02:00
try
{
receiveMessage(handlerSocket, response);
}
catch(NetworkException exception)
{
2020-04-28 12:35:22 +02:00
/* TODO: Implement error handling here and send a repsonse back (Issue: https://github.com/besterprotocol/besterd/issues/10) */
debugPrint("Error communicating with message handler");
}
finally
{
/* Always close the socket */
handlerSocket.close();
debugPrint("Closed UNIX domain socket to handler");
}
2020-04-19 22:00:53 +02:00
return response;
2020-04-19 14:36:55 +02:00
}
2020-04-26 19:53:59 +02:00
/* TODO: Comment [], rename [] */
2020-04-19 20:32:52 +02:00
private bool dispatchMessage(Scope scopeField, JSONValue payloadBlock)
2020-04-18 22:34:26 +02:00
{
/* Status of dispatch */
bool dispatchStatus = true;
/* TODO: Bounds checking, type checking */
/* Get the payload type */
string payloadType = payloadBlock["type"].str;
debugPrint("Payload type is \"" ~ payloadType ~ "\"");
/* Get the payload data */
JSONValue payloadData = payloadBlock["data"];
/* Lookup the payloadType handler */
2020-04-19 14:45:53 +02:00
MessageHandler chosenHandler = server.findHandler(payloadType);
2020-04-18 22:50:47 +02:00
/* Check if the payload is a built-in command */
2020-04-19 14:31:24 +02:00
if(cmp(payloadType, "builtin") == 0)
{
/* TODO: Implement me */
2020-04-19 14:33:44 +02:00
debugPrint("Built-in payload type");
2020-04-19 20:32:52 +02:00
/**
* Built-in commands follow the structure of
* "command" : {"type" : "cmdType", "command" : ...}
*/
JSONValue commandBlock = payloadData["command"];
string commandType = commandBlock["type"].str;
2020-04-19 21:55:08 +02:00
JSONValue command = commandBlock["args"];
2020-04-19 20:32:52 +02:00
2020-04-19 21:03:55 +02:00
/* If the command is `close` */
2020-04-26 15:10:48 +02:00
if(cmp(commandType, "close") == 0)
2020-04-19 21:03:55 +02:00
{
debugPrint("Closing socket...");
2020-04-19 21:55:08 +02:00
this.clientConnection.close();
2020-04-19 21:03:55 +02:00
}
else
{
debugPrint("Invalid built-in command type");
/* TODO: Generate error response */
2020-04-19 20:32:52 +02:00
}
}
/* If an external handler is found (i.e. not a built-in command) */
else if(chosenHandler)
{
/* TODO: Implement me */
2020-04-19 14:33:44 +02:00
debugPrint("Chosen handler for payload type \"" ~ payloadType ~ "\" is " ~ chosenHandler.getPluginName());
2020-04-19 14:36:55 +02:00
try
{
/* TODO: Collect return value */
HandlerResponse handlerResponse = new HandlerResponse(handlerRun(chosenHandler, payloadData));
2020-04-24 17:12:25 +02:00
/* TODO: Continue here, we will make all error handling do on construction as to make this all more compact */
debugPrint("<<< Message Handler [" ~ chosenHandler.getPluginName() ~ "] response >>>\n\n" ~ handlerResponse.toString());
/* Execute the message handler's command */
handlerResponse.execute(this);
}
catch(ResponseError e)
{
/* In the case of an error with the message handler, send an error to the client/server */
/* TODO: Send error here */
2020-04-28 18:19:27 +02:00
//JSONValue errorResponse;
//errorResponse["dd"] = 2;
//debugPrint("Response error");
}
catch(Exception e)
{
debugPrint("fhjhfsdjhfdjhgsdkjh UUUUH:" ~e.toString());
}
2020-04-28 12:35:22 +02:00
debugPrint("Handler section done (for client)");
/* TODO: Handle response */
}
else
{
/* TODO: Implement error handling */
2020-04-19 14:33:44 +02:00
debugPrint("No handler available for payload type \"" ~ payloadType ~ "\"");
}
2020-04-18 22:34:26 +02:00
return dispatchStatus;
}
/**
* Given the headerBlock, this returns the requested scope
* of the connection.
*/
private Scope getConnectionScope(JSONValue headerBlock)
{
/* TODO: Type checking and bounds checking */
/* Get the scope block */
JSONValue scopeBlock = headerBlock["scope"];
string scopeString = scopeBlock.str();
if(cmp(scopeString, "client") == 0)
{
return Scope.CLIENT;
}
else if(cmp(scopeString, "server") == 0)
{
return Scope.SERVER;
}
return Scope.UNKNOWN;
}
2020-04-18 22:34:26 +02:00
2020-04-16 18:46:26 +02:00
/* Process the received message */
private void processMessage(JSONValue jsonMessage)
2020-04-16 18:46:26 +02:00
{
2020-04-18 22:34:26 +02:00
/* Attempt to convert the message to JSON */
try
{
/* Convert message to JSON */
debugPrint("<<< Received JSON >>>\n\n" ~ jsonMessage.toPrettyString());
/* TODO: Bounds checking, type checking */
/* Get the header */
JSONValue headerBlock = jsonMessage["header"];
/**
* Check to see if this connection is currently "untyped".
*
* If it is then we set the type.
*/
if(connectionType == Scope.UNKNOWN)
{
/* Get the scope of the message */
Scope scopeField = getConnectionScope(headerBlock);
/* TODO: Authenticate if client, else do ntohing for server */
/* Decide what action to take depending on the scope */
if(scopeField == Scope.UNKNOWN)
{
/* If the host-provided `scope` field was invalid */
debugPrint("Host provided scope was UNKNOWN");
/* TODO: Send message back about an invalid scope */
2020-04-26 15:01:48 +02:00
/* Close the connection */
clientConnection.close();
}
else if(scopeField == Scope.CLIENT)
{
/**
* If the host-provided `scope` field is `Scope.CLIENT`
* then we must attempt authentication, if it fails
* send the client a message back and then close the
* connection.
*/
/* Get the authentication block */
JSONValue authenticationBlock = headerBlock["authentication"];
/* Get the username and password */
string username = authenticationBlock["username"].str(), password = authenticationBlock["password"].str();
/* Attempt authentication */
bool authenticationStatus = server.authenticate(username, password);
/* Check if the authentication was successful or not */
if(authenticationStatus)
{
/**
* If the authentication was successful then store the
* client's credentials.
*/
this.username = username;
this.password = password;
}
else
{
/**
* If the authentication was unsuccessful then send a
* message to the client stating so and close the connection.
*/
debugPrint("Authenticating the user failed, sending error and closing connection.");
/* TODO : Send error message to client */
/* Close the connection */
clientConnection.close();
2020-04-26 20:35:37 +02:00
/* TODO: Throw exception here */
2020-04-26 15:01:48 +02:00
}
}
2020-04-26 20:35:37 +02:00
else if(scopeField == Scope.SERVER)
{
debugPrint("Server scope enabled");
}
/* Set the connection type to `scopeField` */
connectionType = scopeField;
}
else
{
/* TODO: Implement worker here */
}
2020-04-19 13:25:32 +02:00
/* Get the payload block */
JSONValue payloadBlock = jsonMessage["payload"];
debugPrint("<<< Payload is >>>\n\n" ~ payloadBlock.toPrettyString());
2020-04-18 22:34:26 +02:00
2020-04-19 18:53:16 +02:00
/* Dispatch the message */
2020-04-26 15:10:48 +02:00
bool dispatchStatus = dispatchMessage(connectionType, payloadBlock);
2020-04-19 18:53:16 +02:00
if(dispatchStatus)
2020-04-16 19:16:38 +02:00
{
2020-04-19 18:53:16 +02:00
debugPrint("Dispatch succeeded");
2020-04-16 19:16:38 +02:00
}
else
{
2020-04-19 18:53:16 +02:00
/* TODO: Error handling */
debugPrint("Dispatching failed...");
}
2020-04-16 18:46:26 +02:00
}
2020-04-19 18:53:16 +02:00
/* If thr attempt to convert the message to JSON fails */
2020-04-16 18:46:26 +02:00
catch(JSONException exception)
{
2020-04-19 18:53:16 +02:00
debugPrint("<<< There was an error whilst parsing the JSON message >>>\n\n"~exception.toString());
2020-04-16 16:39:31 +02:00
}
}
2020-04-26 19:53:59 +02:00
}