Restart work on project - new ideals
This commit is contained in:
parent
0f75841600
commit
56315632e0
137
source/app.d
137
source/app.d
|
@ -1,140 +1,5 @@
|
||||||
module besterd;
|
module besterd;
|
||||||
|
|
||||||
import server.server : BesterServer;
|
|
||||||
import std.conv : to;
|
|
||||||
import std.socket : SocketOSException, parseAddress, UnixAddress;
|
|
||||||
import utils.debugging : dprint;
|
|
||||||
import std.stdio : File, writeln;
|
|
||||||
import std.json : parseJSON, JSONValue;
|
|
||||||
import listeners.listener : BesterListener, BesterListenerException;
|
|
||||||
import listeners.types : TCP4Listener, TCP6Listener, UNIXListener;
|
|
||||||
import std.file : exists;
|
|
||||||
|
|
||||||
void main(string[] args)
|
void main(string[] args)
|
||||||
{
|
{
|
||||||
/* Make sure we have atleast two arguments */
|
}
|
||||||
if(args.length >= 2)
|
|
||||||
{
|
|
||||||
/* Get the second argument for configuration file path */
|
|
||||||
string configFilePath = args[1];
|
|
||||||
|
|
||||||
/* Start thhe server if the file exists, else show an error */
|
|
||||||
if(exists(configFilePath))
|
|
||||||
{
|
|
||||||
startServer(args[1]);
|
|
||||||
}
|
|
||||||
else
|
|
||||||
{
|
|
||||||
writeln("Provided server configuration file \"" ~ configFilePath ~ "\" does not exist");
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
/* TODO: Add usage check and arguments before this */
|
|
||||||
|
|
||||||
dprint("Main finished, remaining threads are keeping this open if it hangs");
|
|
||||||
|
|
||||||
/* TODO: Install signal handler so a shutdown can trigger shutdown */
|
|
||||||
}
|
|
||||||
|
|
||||||
private JSONValue getConfig(string configurationFilePath)
|
|
||||||
{
|
|
||||||
/* TODO: Open the file here */
|
|
||||||
File configFile;
|
|
||||||
configFile.open(configurationFilePath);
|
|
||||||
|
|
||||||
/* The file buffer */
|
|
||||||
byte[] fileBuffer;
|
|
||||||
|
|
||||||
/* Allocate the buffer to be the size of the file */
|
|
||||||
fileBuffer.length = configFile.size();
|
|
||||||
|
|
||||||
/* Read the content of the file */
|
|
||||||
/* TODO: Error handling ErrnoException */
|
|
||||||
fileBuffer = configFile.rawRead(fileBuffer);
|
|
||||||
configFile.close();
|
|
||||||
|
|
||||||
JSONValue config;
|
|
||||||
|
|
||||||
/* TODO: JSON error checking */
|
|
||||||
config = parseJSON(cast(string)fileBuffer);
|
|
||||||
|
|
||||||
return config;
|
|
||||||
}
|
|
||||||
|
|
||||||
private BesterListener[] getListeners(BesterServer server, JSONValue networkBlock)
|
|
||||||
{
|
|
||||||
BesterListener[] listeners;
|
|
||||||
|
|
||||||
/* TODO: Error handling and get keys and clean up for formality */
|
|
||||||
|
|
||||||
try
|
|
||||||
{
|
|
||||||
/* Look for IPv4 TCP block */
|
|
||||||
JSONValue inet4TCPBlock = networkBlock["tcp4"];
|
|
||||||
dprint("<<< IPv4 TCP Block >>>\n" ~ inet4TCPBlock.toPrettyString());
|
|
||||||
string inet4Address = inet4TCPBlock["address"].str();
|
|
||||||
ushort inet4Port = to!(ushort)(inet4TCPBlock["port"].str());
|
|
||||||
TCP4Listener tcp4Listener = new TCP4Listener(server, parseAddress(inet4Address, inet4Port));
|
|
||||||
listeners ~= tcp4Listener;
|
|
||||||
|
|
||||||
/* Look for IPv6 TCP block */
|
|
||||||
JSONValue inet6TCPBlock = networkBlock["tcp6"];
|
|
||||||
dprint("<<< IPv6 TCP Block >>>\n" ~ inet6TCPBlock.toPrettyString());
|
|
||||||
string inet6Address = inet6TCPBlock["address"].str();
|
|
||||||
ushort inet6Port = to!(ushort)(inet6TCPBlock["port"].str());
|
|
||||||
TCP6Listener tcp6Listener = new TCP6Listener(server, parseAddress(inet6Address, inet6Port));
|
|
||||||
listeners ~= tcp6Listener;
|
|
||||||
|
|
||||||
/* Look for UNIX Domain block */
|
|
||||||
JSONValue unixDomainBlock = networkBlock["unix"];
|
|
||||||
dprint("<<< UNIX Domain Block >>>\n" ~ unixDomainBlock.toPrettyString());
|
|
||||||
string unixAddress = unixDomainBlock["address"].str();
|
|
||||||
// UNIXListener unixListener = new UNIXListener(server, new UnixAddress(unixAddress));
|
|
||||||
// listeners ~= unixListener;
|
|
||||||
}
|
|
||||||
catch(BesterListenerException e)
|
|
||||||
{
|
|
||||||
|
|
||||||
}
|
|
||||||
|
|
||||||
|
|
||||||
return listeners;
|
|
||||||
}
|
|
||||||
|
|
||||||
private void startServer(string configurationFilePath)
|
|
||||||
{
|
|
||||||
/* The server configuration */
|
|
||||||
JSONValue serverConfiguration = getConfig(configurationFilePath);
|
|
||||||
dprint("<<< Bester.d configuration >>>\n" ~ serverConfiguration.toPrettyString());
|
|
||||||
|
|
||||||
try
|
|
||||||
{
|
|
||||||
/* The server */
|
|
||||||
BesterServer server = null;
|
|
||||||
|
|
||||||
/* TODO: Bounds anc type checking */
|
|
||||||
|
|
||||||
/* Get the network block */
|
|
||||||
JSONValue networkBlock = serverConfiguration["network"];
|
|
||||||
|
|
||||||
/* Create the Bester server */
|
|
||||||
server = new BesterServer(serverConfiguration);
|
|
||||||
|
|
||||||
/* TODO: Get keys */
|
|
||||||
BesterListener[] listeners = getListeners(server, networkBlock);
|
|
||||||
|
|
||||||
for(ulong i = 0; i < listeners.length; i++)
|
|
||||||
{
|
|
||||||
/* Add listener */
|
|
||||||
server.addListener(listeners[i]);
|
|
||||||
}
|
|
||||||
|
|
||||||
/* Start running the server (starts the listeners) */
|
|
||||||
server.run();
|
|
||||||
}
|
|
||||||
catch(SocketOSException exception)
|
|
||||||
{
|
|
||||||
dprint("Error binding: " ~ exception.toString());
|
|
||||||
}
|
|
||||||
|
|
||||||
}
|
|
|
@ -1,12 +0,0 @@
|
||||||
module base.net;
|
|
||||||
|
|
||||||
import base.types;
|
|
||||||
import std.socket : Socket;
|
|
||||||
|
|
||||||
public class NetworkException : BesterException
|
|
||||||
{
|
|
||||||
this(Socket socketResponsible)
|
|
||||||
{
|
|
||||||
super("Socket error with: " ~ socketResponsible.remoteAddress().toAddrString());
|
|
||||||
}
|
|
||||||
}
|
|
|
@ -1,10 +0,0 @@
|
||||||
module base.types;
|
|
||||||
|
|
||||||
public class BesterException : Exception
|
|
||||||
{
|
|
||||||
this(string message)
|
|
||||||
{
|
|
||||||
/* TODO: Implement me */
|
|
||||||
super(message);
|
|
||||||
}
|
|
||||||
}
|
|
|
@ -1,535 +0,0 @@
|
||||||
module connection.connection;
|
|
||||||
|
|
||||||
import utils.debugging : debugPrint; /* TODO: Stephen */
|
|
||||||
import std.conv : to;
|
|
||||||
import std.socket : Socket, AddressFamily, SocketType, ProtocolType, parseAddress, SocketFlags, Address;
|
|
||||||
import core.thread : Thread;
|
|
||||||
import std.stdio : writeln, File;
|
|
||||||
import std.json : JSONValue, parseJSON, JSONException, JSONType, toJSON;
|
|
||||||
import std.string : cmp;
|
|
||||||
import handlers.handler : MessageHandler;
|
|
||||||
import server.server : BesterServer;
|
|
||||||
import handlers.response : ResponseError, HandlerResponse;
|
|
||||||
import utils.message : receiveMessage, sendMessage;
|
|
||||||
import base.net : NetworkException;
|
|
||||||
import base.types : BesterException;
|
|
||||||
|
|
||||||
public final class BesterConnection : Thread
|
|
||||||
{
|
|
||||||
|
|
||||||
/* The socket to the client */
|
|
||||||
private Socket clientConnection;
|
|
||||||
|
|
||||||
/* The server backend */
|
|
||||||
public BesterServer server;
|
|
||||||
|
|
||||||
/* The client's credentials */
|
|
||||||
private string username;
|
|
||||||
private string password;
|
|
||||||
|
|
||||||
/* If the connection is active */
|
|
||||||
private bool isActive = true;
|
|
||||||
|
|
||||||
/* The connection scope */
|
|
||||||
public enum Scope
|
|
||||||
{
|
|
||||||
CLIENT,
|
|
||||||
SERVER,
|
|
||||||
UNKNOWN
|
|
||||||
}
|
|
||||||
|
|
||||||
/* The type of this connection */
|
|
||||||
private Scope connectionType = Scope.UNKNOWN;
|
|
||||||
|
|
||||||
/* Get the type of the connection */
|
|
||||||
public Scope getType()
|
|
||||||
{
|
|
||||||
return connectionType;
|
|
||||||
}
|
|
||||||
|
|
||||||
/* Get the socket */
|
|
||||||
public Socket getSocket()
|
|
||||||
{
|
|
||||||
return clientConnection;
|
|
||||||
}
|
|
||||||
|
|
||||||
this(Socket clientConnection, BesterServer server)
|
|
||||||
{
|
|
||||||
/* Save socket and set thread worker function pointer */
|
|
||||||
super(&run);
|
|
||||||
this.clientConnection = clientConnection;
|
|
||||||
this.server = server;
|
|
||||||
|
|
||||||
debugPrint("New client handler spawned for " ~ clientConnection.remoteAddress().toAddrString());
|
|
||||||
}
|
|
||||||
|
|
||||||
/**
|
|
||||||
* Shutdown the BesterConnection by stopping
|
|
||||||
* the read-write loop and closing the socket.
|
|
||||||
*/
|
|
||||||
public void shutdown()
|
|
||||||
{
|
|
||||||
/* TODO: Send message posssibly, think about this for listeners and informers (etc.) too */
|
|
||||||
isActive = false;
|
|
||||||
}
|
|
||||||
|
|
||||||
override public string toString()
|
|
||||||
{
|
|
||||||
return username ~ "@" ~ clientConnection.remoteAddress().toAddrString();
|
|
||||||
}
|
|
||||||
|
|
||||||
/**
|
|
||||||
* Returns an array of the username and password.
|
|
||||||
*/
|
|
||||||
public string[] getCredentials()
|
|
||||||
{
|
|
||||||
return [username, password];
|
|
||||||
}
|
|
||||||
|
|
||||||
/* Read/send loop */
|
|
||||||
private void run()
|
|
||||||
{
|
|
||||||
debugPrint("<<< Begin read/send loop >>>");
|
|
||||||
while(isActive)
|
|
||||||
{
|
|
||||||
/* Received JSON message */
|
|
||||||
JSONValue receivedMessage;
|
|
||||||
|
|
||||||
/* Attempt to receive a message */
|
|
||||||
try
|
|
||||||
{
|
|
||||||
/* Receive a message */
|
|
||||||
receiveMessage(clientConnection, receivedMessage);
|
|
||||||
|
|
||||||
/**
|
|
||||||
* If the message was received successfully then
|
|
||||||
* process the message. */
|
|
||||||
processMessage(receivedMessage);
|
|
||||||
|
|
||||||
/* Check if this is a server connection, if so, end the connection */
|
|
||||||
if(connectionType == Scope.SERVER)
|
|
||||||
{
|
|
||||||
debugPrint("Server connection done, closing BesterConnection.");
|
|
||||||
shutdown();
|
|
||||||
}
|
|
||||||
}
|
|
||||||
catch(BesterException exception)
|
|
||||||
{
|
|
||||||
debugPrint("Error in read/write loop: " ~ exception.toString());
|
|
||||||
break;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
debugPrint("<<< End read/send loop >>>");
|
|
||||||
|
|
||||||
/* Close the socket */
|
|
||||||
clientConnection.close();
|
|
||||||
|
|
||||||
/* TODO: Remove myself from the connections array */
|
|
||||||
}
|
|
||||||
|
|
||||||
/**
|
|
||||||
* Destructor for BesterConnection
|
|
||||||
*
|
|
||||||
* Upon the client disconnecting, the only reference to
|
|
||||||
* this object should be through the `connections` array
|
|
||||||
* in the instance of BesterServer but because that will
|
|
||||||
* be removed in the end of the `run` function call.
|
|
||||||
*
|
|
||||||
* And because the thread ends thereafter, there will be
|
|
||||||
* no reference there either.
|
|
||||||
*
|
|
||||||
* Only then will this function be called by the garbage-
|
|
||||||
* collector, this will provide the remaining clean ups.
|
|
||||||
*/
|
|
||||||
~this()
|
|
||||||
{
|
|
||||||
debugPrint("Destructor for \"" ~ this.toString() ~ "\" running...");
|
|
||||||
|
|
||||||
/* Close the socket to the client */
|
|
||||||
clientConnection.close();
|
|
||||||
debugPrint("Closed socket to client");
|
|
||||||
|
|
||||||
debugPrint("Destructor finished");
|
|
||||||
}
|
|
||||||
|
|
||||||
/* TODO: Comment [], rename [] */
|
|
||||||
|
|
||||||
/**
|
|
||||||
* Dispatches the message to the correct message handler.
|
|
||||||
*
|
|
||||||
* Returns `true` on success or partial success, `false`
|
|
||||||
* on fatal protocol error.
|
|
||||||
*/
|
|
||||||
private bool dispatchMessage(Scope scopeField, JSONValue payloadBlock)
|
|
||||||
{
|
|
||||||
/* The payload type */
|
|
||||||
string payloadType;
|
|
||||||
|
|
||||||
/* The payload data */
|
|
||||||
JSONValue payloadData;
|
|
||||||
|
|
||||||
/* The payload tag */
|
|
||||||
string payloadTag;
|
|
||||||
|
|
||||||
/* Attempt to parse protocol-critical fields */
|
|
||||||
try
|
|
||||||
{
|
|
||||||
/* Get the payload type */
|
|
||||||
payloadType = payloadBlock["type"].str;
|
|
||||||
debugPrint("Payload type is \"" ~ payloadType ~ "\"");
|
|
||||||
|
|
||||||
/* Get the payload data */
|
|
||||||
payloadData = payloadBlock["data"];
|
|
||||||
|
|
||||||
/* Get the payload tag */
|
|
||||||
payloadTag = payloadBlock["id"].str();
|
|
||||||
}
|
|
||||||
catch(JSONException e)
|
|
||||||
{
|
|
||||||
debugPrint("Fatal error when processing packet, missing fields");
|
|
||||||
return false;
|
|
||||||
}
|
|
||||||
|
|
||||||
/* Lookup the payloadType handler */
|
|
||||||
MessageHandler chosenHandler = server.findHandler(payloadType);
|
|
||||||
|
|
||||||
/* Check if it is a dummy type */
|
|
||||||
if(cmp(payloadType, "dummy") == 0)
|
|
||||||
{
|
|
||||||
/* Construct a dummy response */
|
|
||||||
JSONValue dummyMessage;
|
|
||||||
|
|
||||||
/* Construct a header block */
|
|
||||||
JSONValue headerBlock;
|
|
||||||
headerBlock["status"] = "0";
|
|
||||||
|
|
||||||
/* Attach the header block */
|
|
||||||
dummyMessage["header"] = headerBlock;
|
|
||||||
|
|
||||||
/* Construct the payload block */
|
|
||||||
JSONValue dummyPayloadBlock;
|
|
||||||
dummyPayloadBlock["data"] = null;
|
|
||||||
dummyPayloadBlock["type"] = payloadType;
|
|
||||||
dummyPayloadBlock["id"] = payloadTag;
|
|
||||||
|
|
||||||
/* Attach the payload block */
|
|
||||||
dummyMessage["payload"] = dummyPayloadBlock;
|
|
||||||
|
|
||||||
try
|
|
||||||
{
|
|
||||||
/* Send the message */
|
|
||||||
sendMessage(clientConnection, dummyMessage);
|
|
||||||
}
|
|
||||||
catch(NetworkException e)
|
|
||||||
{
|
|
||||||
debugPrint("Error sending status message, fatal closing connection");
|
|
||||||
/* TODO: We should deactivate the connection when this happens */
|
|
||||||
return false;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
/* Check if the payload is a built-in command */
|
|
||||||
else if(cmp(payloadType, "builtin") == 0)
|
|
||||||
{
|
|
||||||
/* TODO: Implement me */
|
|
||||||
debugPrint("Built-in payload type");
|
|
||||||
|
|
||||||
/**
|
|
||||||
* Built-in commands follow the structure of
|
|
||||||
* "command" : {"type" : "cmdType", "command" : ...}
|
|
||||||
*/
|
|
||||||
JSONValue commandBlock = payloadData["command"];
|
|
||||||
string commandType = commandBlock["type"].str;
|
|
||||||
//JSONValue command = commandBlock["args"];
|
|
||||||
|
|
||||||
/* If the command is `close` */
|
|
||||||
if(cmp(commandType, "close") == 0)
|
|
||||||
{
|
|
||||||
debugPrint("Closing socket...");
|
|
||||||
isActive = false;
|
|
||||||
|
|
||||||
// sendStatus(0, JSONValue());
|
|
||||||
}
|
|
||||||
else
|
|
||||||
{
|
|
||||||
debugPrint("Invalid built-in command type");
|
|
||||||
/* TODO: Generate error response */
|
|
||||||
// dispatchStatus = false;
|
|
||||||
|
|
||||||
/* TODO: Send a response as the "builtin" message handler */
|
|
||||||
}
|
|
||||||
}
|
|
||||||
/* If an external handler is found (i.e. not a built-in command) */
|
|
||||||
else if(chosenHandler)
|
|
||||||
{
|
|
||||||
/* TODO: Implement me */
|
|
||||||
debugPrint("Chosen handler for payload type \"" ~ payloadType ~ "\" is " ~ chosenHandler.getPluginName());
|
|
||||||
|
|
||||||
try
|
|
||||||
{
|
|
||||||
/* Provide the handler the message and let it process it and send us a reply */
|
|
||||||
HandlerResponse handlerResponse = chosenHandler.handleMessage(payloadData);
|
|
||||||
|
|
||||||
/* 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 (as per its reply) and pass in the tag */
|
|
||||||
handlerResponse.execute(this, payloadTag);
|
|
||||||
}
|
|
||||||
catch(ResponseError e)
|
|
||||||
{
|
|
||||||
/* In the case of an error with the message handler, send an error to the client/server */
|
|
||||||
|
|
||||||
/* TODO: Clean up comments */
|
|
||||||
|
|
||||||
/* Send error message to client */
|
|
||||||
sendStatusReport(StatusType.FAILURE, payloadTag);
|
|
||||||
}
|
|
||||||
/* TODO: Be more specific with errors and reporting in the future */
|
|
||||||
catch(Exception e)
|
|
||||||
{
|
|
||||||
/* TODO: Remove me */
|
|
||||||
debugPrint("fhjhfsdjhfdjhgsdkjh UUUUH:" ~e.toString());
|
|
||||||
|
|
||||||
/* Send error message to client */
|
|
||||||
sendStatusReport(StatusType.FAILURE, payloadTag);
|
|
||||||
}
|
|
||||||
|
|
||||||
debugPrint("Handler section done (for client)");
|
|
||||||
}
|
|
||||||
/* If no message handler for the specified type could be found */
|
|
||||||
else
|
|
||||||
{
|
|
||||||
/* TODO: Implement error handling */
|
|
||||||
debugPrint("No handler available for payload type \"" ~ payloadType ~ "\"");
|
|
||||||
|
|
||||||
/* Send error message to client */
|
|
||||||
sendStatusReport(StatusType.FAILURE, payloadTag);
|
|
||||||
}
|
|
||||||
|
|
||||||
return true;
|
|
||||||
}
|
|
||||||
|
|
||||||
/**
|
|
||||||
* Type of the status report.
|
|
||||||
* Either 0 (for success) or 1 (for failure).
|
|
||||||
*/
|
|
||||||
public enum StatusType
|
|
||||||
{
|
|
||||||
SUCCESS,
|
|
||||||
FAILURE
|
|
||||||
}
|
|
||||||
|
|
||||||
/**
|
|
||||||
* Send a status report for the message with id
|
|
||||||
* `id` of type `StatusType`.
|
|
||||||
*/
|
|
||||||
public void sendStatusReport(StatusType statusType, string id)
|
|
||||||
{
|
|
||||||
/* Construct the response */
|
|
||||||
JSONValue statusMessage;
|
|
||||||
|
|
||||||
/* Construct the header block */
|
|
||||||
JSONValue headerBlock;
|
|
||||||
headerBlock["status"] = statusType == 0 ? "good" : "bad";
|
|
||||||
headerBlock["messageType"] = "statusReport";
|
|
||||||
|
|
||||||
/* Attach the header block */
|
|
||||||
statusMessage["header"] = headerBlock;
|
|
||||||
|
|
||||||
/* Create the payload block */
|
|
||||||
JSONValue payloadBlock;
|
|
||||||
payloadBlock["id"] = id;
|
|
||||||
|
|
||||||
/* Attach the payload block */
|
|
||||||
statusMessage["payload"] = payloadBlock;
|
|
||||||
|
|
||||||
try
|
|
||||||
{
|
|
||||||
/* Send the message */
|
|
||||||
sendMessage(clientConnection, statusMessage);
|
|
||||||
}
|
|
||||||
catch(NetworkException e)
|
|
||||||
{
|
|
||||||
debugPrint("Error sending status message");
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
/**
|
|
||||||
* 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;
|
|
||||||
}
|
|
||||||
|
|
||||||
|
|
||||||
/**
|
|
||||||
* Sends an error message on fatal error.
|
|
||||||
* Used before client shutdown on such
|
|
||||||
* an error.
|
|
||||||
*/
|
|
||||||
private void sendFatalMessage()
|
|
||||||
{
|
|
||||||
/* TODO: Implement me */
|
|
||||||
}
|
|
||||||
|
|
||||||
/* Process the received message */
|
|
||||||
private void processMessage(JSONValue jsonMessage)
|
|
||||||
{
|
|
||||||
/* Attempt to convert the message to JSON */
|
|
||||||
try
|
|
||||||
{
|
|
||||||
/* Convert message to JSON */
|
|
||||||
debugPrint("<<< Received JSON >>>\n\n" ~ jsonMessage.toPrettyString());
|
|
||||||
|
|
||||||
/* 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 */
|
|
||||||
|
|
||||||
/* Send fatal message */
|
|
||||||
sendFatalMessage();
|
|
||||||
|
|
||||||
/* Stop the read/write loop */
|
|
||||||
shutdown();
|
|
||||||
return;
|
|
||||||
}
|
|
||||||
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.
|
|
||||||
*/
|
|
||||||
debugPrint("Client scope enabled");
|
|
||||||
|
|
||||||
|
|
||||||
bool authenticationStatus;
|
|
||||||
|
|
||||||
/* The `authentication` block */
|
|
||||||
JSONValue authenticationBlock = headerBlock["authentication"];
|
|
||||||
|
|
||||||
/* Get the username and password */
|
|
||||||
string username = authenticationBlock["username"].str(), password = authenticationBlock["password"].str();
|
|
||||||
|
|
||||||
/* Attempt authentication */
|
|
||||||
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;
|
|
||||||
|
|
||||||
/* Send error message to client */
|
|
||||||
// sendStatus(5, JSONValue());
|
|
||||||
|
|
||||||
/* TODO: Send authentication success */
|
|
||||||
sendStatusReport(StatusType.SUCCESS, "auth_special");
|
|
||||||
}
|
|
||||||
/* If authentication failed due to malformed message or incorrect details */
|
|
||||||
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.");
|
|
||||||
|
|
||||||
/* Send fatal message */
|
|
||||||
sendFatalMessage();
|
|
||||||
|
|
||||||
/* Stop the read/write loop */
|
|
||||||
shutdown();
|
|
||||||
return;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
else if(scopeField == Scope.SERVER)
|
|
||||||
{
|
|
||||||
debugPrint("Server scope enabled");
|
|
||||||
}
|
|
||||||
|
|
||||||
/* Set the connection type to `scopeField` */
|
|
||||||
connectionType = scopeField;
|
|
||||||
|
|
||||||
if(connectionType == Scope.CLIENT)
|
|
||||||
{
|
|
||||||
return;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
/* Get the `payload` block */
|
|
||||||
JSONValue payloadBlock = jsonMessage["payload"];
|
|
||||||
debugPrint("<<< Payload is >>>\n\n" ~ payloadBlock.toPrettyString());
|
|
||||||
|
|
||||||
/**
|
|
||||||
* Dispatch the message. If a fatal failure is
|
|
||||||
* detected then the connection will be shutdown.
|
|
||||||
*/
|
|
||||||
if(dispatchMessage(connectionType, payloadBlock))
|
|
||||||
{
|
|
||||||
debugPrint("Dispatch succeeded");
|
|
||||||
}
|
|
||||||
else
|
|
||||||
{
|
|
||||||
debugPrint("Dispatch failed, deactivating connection...");
|
|
||||||
|
|
||||||
/* Send fatal message */
|
|
||||||
sendFatalMessage();
|
|
||||||
|
|
||||||
/* Shutdown the connection */
|
|
||||||
shutdown();
|
|
||||||
}
|
|
||||||
}
|
|
||||||
/* If the attempt to convert the message to JSON fails */
|
|
||||||
catch(JSONException exception)
|
|
||||||
{
|
|
||||||
debugPrint("Fatal format error, deactivating connection...");
|
|
||||||
|
|
||||||
/* Send fatal message */
|
|
||||||
sendFatalMessage();
|
|
||||||
|
|
||||||
/* Shutdown the connection */
|
|
||||||
shutdown();
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
|
@ -1,11 +0,0 @@
|
||||||
module handlers.commands;
|
|
||||||
|
|
||||||
import std.json : JSONValue;
|
|
||||||
|
|
||||||
public class Command
|
|
||||||
{
|
|
||||||
JSONValue execute()
|
|
||||||
{
|
|
||||||
return JSONValue();
|
|
||||||
}
|
|
||||||
}
|
|
|
@ -1,217 +0,0 @@
|
||||||
module handlers.handler;
|
|
||||||
|
|
||||||
import std.stdio : writeln;
|
|
||||||
import std.socket : Socket, AddressFamily, parseAddress, SocketType, SocketOSException, UnixAddress;
|
|
||||||
import std.json : JSONValue, JSONType;
|
|
||||||
import utils.debugging : debugPrint;
|
|
||||||
import handlers.response;
|
|
||||||
import base.net;
|
|
||||||
import utils.message : sendMessage, receiveMessage;
|
|
||||||
import server.server : BesterServer;
|
|
||||||
import std.process : spawnProcess, Pid;
|
|
||||||
|
|
||||||
public final class MessageHandler
|
|
||||||
{
|
|
||||||
/* The path to the message handler executable */
|
|
||||||
private string executablePath;
|
|
||||||
|
|
||||||
/* The UNIX domain socket */
|
|
||||||
private Socket domainSocket;
|
|
||||||
|
|
||||||
/* The pluginName/type */
|
|
||||||
private string pluginName;
|
|
||||||
|
|
||||||
/* The UNIX domain socket path */
|
|
||||||
public string socketPath;
|
|
||||||
|
|
||||||
/* The BesterServer being used */
|
|
||||||
public BesterServer server;
|
|
||||||
|
|
||||||
/* The PID of the process */
|
|
||||||
private Pid pid;
|
|
||||||
|
|
||||||
public Socket getSocket()
|
|
||||||
{
|
|
||||||
return domainSocket;
|
|
||||||
}
|
|
||||||
|
|
||||||
this(BesterServer server, string executablePath, string socketPath, string pluginName)
|
|
||||||
{
|
|
||||||
/* Set the plugin name */
|
|
||||||
this.pluginName = pluginName;
|
|
||||||
|
|
||||||
/* Set the socket path */
|
|
||||||
this.socketPath = socketPath;
|
|
||||||
|
|
||||||
/* Set the server this handler is associated with */
|
|
||||||
this.server = server;
|
|
||||||
|
|
||||||
/* Start the message handler */
|
|
||||||
startHandlerExecutable();
|
|
||||||
}
|
|
||||||
|
|
||||||
private void startHandlerExecutable()
|
|
||||||
{
|
|
||||||
// pid = spawnProcess(executablePath);
|
|
||||||
}
|
|
||||||
|
|
||||||
public string getPluginName()
|
|
||||||
{
|
|
||||||
return pluginName;
|
|
||||||
}
|
|
||||||
|
|
||||||
public Socket getNewSocket()
|
|
||||||
{
|
|
||||||
/* Create the UNIX domain socket */
|
|
||||||
Socket domainSocket = new Socket(AddressFamily.UNIX, SocketType.STREAM);
|
|
||||||
|
|
||||||
/* Connect to the socket at the socket path */
|
|
||||||
domainSocket.connect(new UnixAddress(socketPath));
|
|
||||||
|
|
||||||
return domainSocket;
|
|
||||||
}
|
|
||||||
|
|
||||||
private static string[] getAvailableTypes(JSONValue handlerBlock)
|
|
||||||
{
|
|
||||||
/* Available types as strings */
|
|
||||||
string[] availableTypesStrings;
|
|
||||||
|
|
||||||
/* Get the available handlers */
|
|
||||||
JSONValue availableTypes;
|
|
||||||
|
|
||||||
/* TODO: Bounds check */
|
|
||||||
availableTypes = handlerBlock["availableTypes"];
|
|
||||||
|
|
||||||
/* Make sure it is an array */
|
|
||||||
if(availableTypes.type == JSONType.array)
|
|
||||||
{
|
|
||||||
/* Get the array of available types */
|
|
||||||
JSONValue[] availableTypesArray = availableTypes.array;
|
|
||||||
|
|
||||||
for(uint i = 0; i < availableTypesArray.length; i++)
|
|
||||||
{
|
|
||||||
/* Make sure that it is a string */
|
|
||||||
if(availableTypesArray[i].type == JSONType.string)
|
|
||||||
{
|
|
||||||
/* Add the type handler to the list of available types */
|
|
||||||
availableTypesStrings ~= availableTypesArray[i].str;
|
|
||||||
debugPrint("Module wanted: " ~ availableTypesArray[i].str);
|
|
||||||
}
|
|
||||||
else
|
|
||||||
{
|
|
||||||
/* TODO: Error handling here */
|
|
||||||
debugPrint("Available type not of type JSON string");
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
else
|
|
||||||
{
|
|
||||||
/* TODO: Error handling */
|
|
||||||
}
|
|
||||||
|
|
||||||
return availableTypesStrings;
|
|
||||||
}
|
|
||||||
|
|
||||||
private static string[2] getConfigurationArray(string pluginName, JSONValue typeMapBlock)
|
|
||||||
{
|
|
||||||
/* The configuration string */
|
|
||||||
string[2] configurationString;
|
|
||||||
|
|
||||||
/* The module block */
|
|
||||||
JSONValue moduleBlock;
|
|
||||||
|
|
||||||
/* TODO: Bounds check */
|
|
||||||
moduleBlock = typeMapBlock[pluginName];
|
|
||||||
|
|
||||||
/* Module block mst be of tpe JSON object */
|
|
||||||
if(moduleBlock.type == JSONType.object)
|
|
||||||
{
|
|
||||||
/* TODO: Set the executable path */
|
|
||||||
configurationString[0] = moduleBlock["handlerBinary"].str;
|
|
||||||
|
|
||||||
/* TODO: Set the UNIX domain socket path */
|
|
||||||
configurationString[1] = moduleBlock["unixDomainSocketPath"].str;
|
|
||||||
}
|
|
||||||
else
|
|
||||||
{
|
|
||||||
/* TODO: Error handling */
|
|
||||||
}
|
|
||||||
|
|
||||||
return configurationString;
|
|
||||||
}
|
|
||||||
|
|
||||||
/* TODO: Implement me */
|
|
||||||
public static MessageHandler[] constructHandlers(BesterServer server, JSONValue handlerBlock)
|
|
||||||
{
|
|
||||||
/* List of loaded message handlers */
|
|
||||||
MessageHandler[] handlers;
|
|
||||||
|
|
||||||
/* TODO: Throwing error from inside this function */
|
|
||||||
string[] availableTypes = getAvailableTypes(handlerBlock);
|
|
||||||
|
|
||||||
for(uint i = 0; i < availableTypes.length; i++)
|
|
||||||
{
|
|
||||||
/* Load module */
|
|
||||||
string pluginName = availableTypes[i];
|
|
||||||
debugPrint("Loading module \"" ~ pluginName ~ "\"...");
|
|
||||||
|
|
||||||
try
|
|
||||||
{
|
|
||||||
JSONValue typeMap;
|
|
||||||
|
|
||||||
/* TODO: Bounds check */
|
|
||||||
typeMap = handlerBlock["typeMap"];
|
|
||||||
|
|
||||||
string[2] configuration = getConfigurationArray(pluginName, typeMap);
|
|
||||||
debugPrint("Module executable at: \"" ~ configuration[0] ~ "\"");
|
|
||||||
debugPrint("Module socket path at: \"" ~ configuration[1] ~ "\"");
|
|
||||||
MessageHandler constructedMessageHandler = new MessageHandler(server, configuration[0], configuration[1], pluginName);
|
|
||||||
handlers ~= constructedMessageHandler;
|
|
||||||
debugPrint("Module \"" ~ pluginName ~ "\" loaded");
|
|
||||||
}
|
|
||||||
catch(SocketOSException exception)
|
|
||||||
{
|
|
||||||
debugPrint("Error whilst loading module \"" ~ pluginName ~ "\": " ~ exception.toString());
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
return handlers;
|
|
||||||
}
|
|
||||||
|
|
||||||
/**
|
|
||||||
* Sends the payload to the designated message handler and gets
|
|
||||||
* the response message from the handler and returns it.
|
|
||||||
*/
|
|
||||||
public HandlerResponse handleMessage(JSONValue payload)
|
|
||||||
{
|
|
||||||
/* TODO: If unix sock is down, this just hangs, we should see if the socket file exists first */
|
|
||||||
/* Handler's UNIX domain socket */
|
|
||||||
Socket handlerSocket = getNewSocket();
|
|
||||||
|
|
||||||
/* Send the payload to the message handler */
|
|
||||||
debugPrint("Sending payload over to handler for \"" ~ getPluginName() ~ "\".");
|
|
||||||
sendMessage(handlerSocket, payload);
|
|
||||||
|
|
||||||
/* Get the payload sent from the message handler in response */
|
|
||||||
debugPrint("Waiting for response from handler for \"" ~ getPluginName() ~ "\".");
|
|
||||||
JSONValue response;
|
|
||||||
|
|
||||||
try
|
|
||||||
{
|
|
||||||
receiveMessage(handlerSocket, response);
|
|
||||||
}
|
|
||||||
catch(NetworkException exception)
|
|
||||||
{
|
|
||||||
/* 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");
|
|
||||||
}
|
|
||||||
|
|
||||||
return new HandlerResponse(server, this, response);
|
|
||||||
}
|
|
||||||
}
|
|
|
@ -1,354 +0,0 @@
|
||||||
module handlers.response;
|
|
||||||
|
|
||||||
import std.json : JSONValue, JSONException, parseJSON, toJSON;
|
|
||||||
import std.conv : to;
|
|
||||||
import utils.debugging : debugPrint;
|
|
||||||
import std.string : cmp;
|
|
||||||
import std.stdio : writeln;
|
|
||||||
import connection.connection;
|
|
||||||
import base.types;
|
|
||||||
import std.socket : Socket, SocketOSException, AddressFamily, SocketType, ProtocolType, parseAddress;
|
|
||||||
import utils.message : receiveMessage, sendMessage;
|
|
||||||
import handlers.handler;
|
|
||||||
import std.string : split;
|
|
||||||
import server.server : BesterServer;
|
|
||||||
import handlers.commands : Command;
|
|
||||||
|
|
||||||
/**
|
|
||||||
* The type of the command the message handler wants
|
|
||||||
* us to run
|
|
||||||
*/
|
|
||||||
private enum CommandType : ubyte
|
|
||||||
{
|
|
||||||
/* Simple message flow (always end point) */
|
|
||||||
SEND_CLIENTS, SEND_SERVERS, SEND_HANDLER,
|
|
||||||
|
|
||||||
/* Unknown command */
|
|
||||||
UNKNOWN
|
|
||||||
}
|
|
||||||
|
|
||||||
/**
|
|
||||||
* Represents a response message from a handler.
|
|
||||||
*/
|
|
||||||
public final class HandlerResponse
|
|
||||||
{
|
|
||||||
/**
|
|
||||||
* The message-handler's response.
|
|
||||||
*/
|
|
||||||
private JSONValue messageResponse;
|
|
||||||
|
|
||||||
/* The command to be executed */
|
|
||||||
private CommandType commandType;
|
|
||||||
|
|
||||||
/* The handler that caused such a response to be illicited */
|
|
||||||
private MessageHandler handler;
|
|
||||||
|
|
||||||
/* The associated server */
|
|
||||||
private BesterServer server;
|
|
||||||
|
|
||||||
/**
|
|
||||||
* Constructs a new `HandlerResponse` object that represents the
|
|
||||||
* message handler's response message and the execution of it.
|
|
||||||
*/
|
|
||||||
this(BesterServer server, MessageHandler handler, JSONValue messageResponse)
|
|
||||||
{
|
|
||||||
/* Set the message-handler's response message */
|
|
||||||
this.messageResponse = messageResponse;
|
|
||||||
|
|
||||||
/* Set the handler who caused this reponse to occur */
|
|
||||||
this.handler = handler;
|
|
||||||
|
|
||||||
/* Set the server associated with this message handler */
|
|
||||||
this.server = server;
|
|
||||||
|
|
||||||
/* Attempt parsing the message and error checking it */
|
|
||||||
parse(messageResponse);
|
|
||||||
}
|
|
||||||
|
|
||||||
private void parse(JSONValue handlerResponse)
|
|
||||||
{
|
|
||||||
/**
|
|
||||||
* Handles the response sent back to the server from the
|
|
||||||
* message handler.
|
|
||||||
*/
|
|
||||||
|
|
||||||
/* Get the status */
|
|
||||||
ulong statusCode;
|
|
||||||
|
|
||||||
/* Error? */
|
|
||||||
bool error;
|
|
||||||
|
|
||||||
/* TODO: Bounds checking, type checking */
|
|
||||||
try
|
|
||||||
{
|
|
||||||
/* Get the header block */
|
|
||||||
JSONValue headerBlock = handlerResponse["header"];
|
|
||||||
|
|
||||||
/* Get the status */
|
|
||||||
statusCode = to!(ulong)(headerBlock["status"].str());
|
|
||||||
debugPrint("Status code: " ~ to!(string)(statusCode));
|
|
||||||
|
|
||||||
/* If the status is 0, then it is all fine */
|
|
||||||
if(statusCode == 0)
|
|
||||||
{
|
|
||||||
debugPrint("Status is fine, the handler ran correctly");
|
|
||||||
|
|
||||||
/* The command block */
|
|
||||||
JSONValue commandBlock = headerBlock["command"];
|
|
||||||
|
|
||||||
/**
|
|
||||||
* Get the command that the message handler wants the
|
|
||||||
* server to run.
|
|
||||||
*/
|
|
||||||
string serverCommand = commandBlock["type"].str;
|
|
||||||
debugPrint("Handler->Server command: \"" ~ serverCommand ~ "\"");
|
|
||||||
|
|
||||||
/* Check the command to be run */
|
|
||||||
if(cmp(serverCommand, "sendClients") == 0)
|
|
||||||
{
|
|
||||||
/* Set the command type to SEND_CLIENTS */
|
|
||||||
commandType = CommandType.SEND_CLIENTS;
|
|
||||||
|
|
||||||
/* TODO: Error check and do accesses JSON that would be done in `.execute` */
|
|
||||||
}
|
|
||||||
else if(cmp(serverCommand, "sendServers") == 0)
|
|
||||||
{
|
|
||||||
/* Set the command type to SEND_SERVERS */
|
|
||||||
commandType = CommandType.SEND_SERVERS;
|
|
||||||
|
|
||||||
/* TODO: Error check and do accesses JSON that would be done in `.execute` */
|
|
||||||
}
|
|
||||||
else if(cmp(serverCommand, "sendHandler") == 0)
|
|
||||||
{
|
|
||||||
/* Set the command type to SEND_HAANDLER */
|
|
||||||
commandType = CommandType.SEND_HANDLER;
|
|
||||||
|
|
||||||
/* TODO: Error check and do accesses JSON that would be done in `.execute` */
|
|
||||||
}
|
|
||||||
else
|
|
||||||
{
|
|
||||||
/* TODO: Error handling */
|
|
||||||
debugPrint("The message handler is using an invalid command");
|
|
||||||
}
|
|
||||||
}
|
|
||||||
else
|
|
||||||
{
|
|
||||||
/* If the message handler returned a response in error */
|
|
||||||
debugPrint("Message handler returned an error code: " ~ to!(string)(statusCode));
|
|
||||||
error = true;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
catch(JSONException exception)
|
|
||||||
{
|
|
||||||
debugPrint("<<< There was an error handling the response message >>>\n\n" ~ exception.toString());
|
|
||||||
error = true;
|
|
||||||
}
|
|
||||||
|
|
||||||
/**
|
|
||||||
* If an error was envountered anyway down the processing of the
|
|
||||||
* message-handler then raise a `ResponseError` exception.
|
|
||||||
*/
|
|
||||||
if(error)
|
|
||||||
{
|
|
||||||
throw new ResponseError(messageResponse, statusCode);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
/**
|
|
||||||
* Executes the command. Either `sendClients`, `sendServers`
|
|
||||||
* or `sendHandler`.
|
|
||||||
*/
|
|
||||||
public void execute(BesterConnection originalRequester, string messageID)
|
|
||||||
{
|
|
||||||
/* If the command is SEND_CLIENTS */
|
|
||||||
if(commandType == CommandType.SEND_CLIENTS)
|
|
||||||
{
|
|
||||||
/* Get the list of clients to send to */
|
|
||||||
string[] clients;
|
|
||||||
JSONValue[] clientList = messageResponse["header"]["command"]["data"].array();
|
|
||||||
for(ulong i = 0; i < clientList.length; i++)
|
|
||||||
{
|
|
||||||
clients ~= clientList[i].str();
|
|
||||||
}
|
|
||||||
|
|
||||||
debugPrint("Users wanting to send to: " ~ to!(string)(clients));
|
|
||||||
|
|
||||||
/* Find the users that are wanting to be sent to */
|
|
||||||
BesterConnection[] connectionList = originalRequester.server.getClients(clients);
|
|
||||||
//debugPrint("Users matched online on server: " ~ to!(string)(connectionList));
|
|
||||||
|
|
||||||
/* The fully response message to send back */
|
|
||||||
JSONValue clientPayload;
|
|
||||||
|
|
||||||
/* Set the header of the response */
|
|
||||||
JSONValue headerBlock;
|
|
||||||
headerBlock["messageType"] = "receivedMessage";
|
|
||||||
clientPayload["header"] = headerBlock;
|
|
||||||
|
|
||||||
/* Set the payload of the response */
|
|
||||||
JSONValue payloadBlock;
|
|
||||||
payloadBlock["data"] = messageResponse["data"];
|
|
||||||
payloadBlock["type"] = handler.getPluginName();
|
|
||||||
clientPayload["payload"] = payloadBlock;
|
|
||||||
|
|
||||||
|
|
||||||
/**
|
|
||||||
* Loop through each BesterConnection in connectionList and
|
|
||||||
* send the message-handler payload response message to each
|
|
||||||
* of them.
|
|
||||||
*/
|
|
||||||
bool allSuccess = true;
|
|
||||||
for(ulong i = 0; i < connectionList.length; i++)
|
|
||||||
{
|
|
||||||
/* Get the conneciton */
|
|
||||||
BesterConnection clientConnection = connectionList[i];
|
|
||||||
|
|
||||||
try
|
|
||||||
{
|
|
||||||
/* Get the client's socket */
|
|
||||||
Socket clientSocket = clientConnection.getSocket();
|
|
||||||
//debugPrint("IsAlive?: " ~ to!(string)(clientSocket.isAlive()));
|
|
||||||
|
|
||||||
/* Send the message to the client */
|
|
||||||
debugPrint("Sending handler's response to client \"" ~ clientConnection.toString() ~ "\"...");
|
|
||||||
sendMessage(clientSocket, clientPayload);
|
|
||||||
debugPrint("Sending handler's response to client \"" ~ clientConnection.toString() ~ "\"... [sent]");
|
|
||||||
}
|
|
||||||
catch(SocketOSException exception)
|
|
||||||
{
|
|
||||||
/**
|
|
||||||
* If there was an error sending to the client, this can happen
|
|
||||||
* if the client has disconnected but hasn't yet been removed from
|
|
||||||
* the connections array and hence we try to send on a dead socket
|
|
||||||
* or get the remoteAddress on a dead socket, which causes a
|
|
||||||
* SocketOSException to be called.
|
|
||||||
*/
|
|
||||||
debugPrint("Attempted interacting with dead socket");
|
|
||||||
allSuccess = false;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
debugPrint("SEND_CLIENTS: Completed run");
|
|
||||||
|
|
||||||
/**
|
|
||||||
* Send a status report here.
|
|
||||||
*/
|
|
||||||
originalRequester.sendStatusReport(cast(BesterConnection.StatusType)!allSuccess, messageID);
|
|
||||||
}
|
|
||||||
else if (commandType == CommandType.SEND_SERVERS)
|
|
||||||
{
|
|
||||||
/* Get the list of servers to send to */
|
|
||||||
string[] servers;
|
|
||||||
JSONValue[] serverList = messageResponse["header"]["command"]["data"].array();
|
|
||||||
for(ulong i = 0; i < serverList.length; i++)
|
|
||||||
{
|
|
||||||
servers ~= serverList[i].str();
|
|
||||||
}
|
|
||||||
|
|
||||||
/* TODO: Implement me */
|
|
||||||
writeln("Servers wanting to send to ", servers);
|
|
||||||
|
|
||||||
|
|
||||||
/* The fully response message to send back */
|
|
||||||
JSONValue serverPayload;
|
|
||||||
|
|
||||||
/* Set the header of the response */
|
|
||||||
JSONValue headerBlock;
|
|
||||||
headerBlock["scope"] = "server";
|
|
||||||
serverPayload["header"] = headerBlock;
|
|
||||||
|
|
||||||
/* Set the payload of the response */
|
|
||||||
JSONValue payloadBlock;
|
|
||||||
payloadBlock["data"] = messageResponse["data"];
|
|
||||||
payloadBlock["type"] = handler.getPluginName();
|
|
||||||
serverPayload["payload"] = payloadBlock;
|
|
||||||
|
|
||||||
|
|
||||||
/* Attempt connecting to each server and sending the payload */
|
|
||||||
bool allSuccess = true;
|
|
||||||
for(ulong i = 0; i < servers.length; i++)
|
|
||||||
{
|
|
||||||
/* Get the current server address and port */
|
|
||||||
string serverString = servers[i];
|
|
||||||
string host = serverString.split(":")[0];
|
|
||||||
ushort port = to!(ushort)(serverString.split(":")[1]);
|
|
||||||
|
|
||||||
try
|
|
||||||
{
|
|
||||||
Socket serverConnection = new Socket(AddressFamily.INET, SocketType.STREAM, ProtocolType.TCP);
|
|
||||||
|
|
||||||
/* Connect to the server */
|
|
||||||
debugPrint("Connecting to server \"" ~ serverConnection.toString() ~ "\"...");
|
|
||||||
serverConnection.connect(parseAddress(host, port));
|
|
||||||
debugPrint("Connecting to server \"" ~ serverConnection.toString() ~ "\"... [connected]");
|
|
||||||
|
|
||||||
/* Send the payload */
|
|
||||||
debugPrint("Sending handler's response to server \"" ~ serverConnection.toString() ~ "\"...");
|
|
||||||
sendMessage(serverConnection, serverPayload);
|
|
||||||
debugPrint("Sending handler's response to server \"" ~ serverConnection.toString() ~ "\"... [sent]");
|
|
||||||
|
|
||||||
/* Close the connection to the server */
|
|
||||||
serverConnection.close();
|
|
||||||
debugPrint("Closed connection to server \"" ~ serverConnection.toString() ~ "\"");
|
|
||||||
}
|
|
||||||
catch(Exception e)
|
|
||||||
{
|
|
||||||
/* TODO: Be more specific with the above exception type */
|
|
||||||
debugPrint("Error whilst sending payload to server: " ~ e.toString());
|
|
||||||
allSuccess = false;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
debugPrint("SEND_SERVERS: Completed run");
|
|
||||||
|
|
||||||
/**
|
|
||||||
* Send a status report here.
|
|
||||||
*/
|
|
||||||
originalRequester.sendStatusReport(cast(BesterConnection.StatusType)!allSuccess, messageID);
|
|
||||||
}
|
|
||||||
else if (commandType == CommandType.SEND_HANDLER)
|
|
||||||
{
|
|
||||||
/* Name of the handler to send the message to */
|
|
||||||
string handler = messageResponse["header"]["command"]["data"].str();
|
|
||||||
debugPrint("Handler to forward to: " ~ handler);
|
|
||||||
|
|
||||||
/* Lookup the payloadType handler */
|
|
||||||
MessageHandler chosenHandler = server.findHandler(handler);
|
|
||||||
|
|
||||||
/* Send the data to the message handler */
|
|
||||||
HandlerResponse handlerResponse = chosenHandler.handleMessage(messageResponse["data"]);
|
|
||||||
|
|
||||||
/* Execute the code (this here, recursive) */
|
|
||||||
handlerResponse.execute(originalRequester, messageID);
|
|
||||||
|
|
||||||
debugPrint("SEND_HANDLER: Completed run");
|
|
||||||
}
|
|
||||||
else
|
|
||||||
{
|
|
||||||
/* TODO: */
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
override public string toString()
|
|
||||||
{
|
|
||||||
return messageResponse.toPrettyString();
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
/**
|
|
||||||
* Represents an error in handling the response.
|
|
||||||
*/
|
|
||||||
public final class ResponseError : BesterException
|
|
||||||
{
|
|
||||||
|
|
||||||
/* */
|
|
||||||
|
|
||||||
/* The status code that resulted in the response handling error */
|
|
||||||
private ulong statusCode;
|
|
||||||
|
|
||||||
this(JSONValue messageResponse, ulong statusCode)
|
|
||||||
{
|
|
||||||
/* TODO: Set message afterwards again */
|
|
||||||
super("");
|
|
||||||
}
|
|
||||||
}
|
|
|
@ -1,101 +0,0 @@
|
||||||
module listeners.listener;
|
|
||||||
|
|
||||||
import utils.debugging : debugPrint;
|
|
||||||
import std.conv : to;
|
|
||||||
import std.socket : Socket, AddressFamily, SocketType, ProtocolType, parseAddress, SocketFlags, Address;
|
|
||||||
import core.thread : Thread;
|
|
||||||
import std.stdio : writeln, File;
|
|
||||||
import std.json : JSONValue, parseJSON, JSONException, JSONType, toJSON;
|
|
||||||
import std.string : cmp;
|
|
||||||
import handlers.handler;
|
|
||||||
import server.server;
|
|
||||||
import connection.connection;
|
|
||||||
import base.types : BesterException;
|
|
||||||
|
|
||||||
/**
|
|
||||||
* Represents a server listener which is a method
|
|
||||||
* by which conections to the server (client or server)
|
|
||||||
* can be made.
|
|
||||||
*/
|
|
||||||
public class BesterListener : Thread
|
|
||||||
{
|
|
||||||
/* The associated BesterServer */
|
|
||||||
private BesterServer server;
|
|
||||||
|
|
||||||
/* The server's socket */
|
|
||||||
private Socket serverSocket;
|
|
||||||
|
|
||||||
/* Whether or not the listener is active */
|
|
||||||
private bool active = true;
|
|
||||||
|
|
||||||
/* the address of this listener */
|
|
||||||
protected Address address;
|
|
||||||
|
|
||||||
this(BesterServer besterServer)
|
|
||||||
{
|
|
||||||
/* Set the function address to be called as the worker function */
|
|
||||||
super(&run);
|
|
||||||
|
|
||||||
/* Set this listener's BesterServer */
|
|
||||||
this.server = besterServer;
|
|
||||||
}
|
|
||||||
|
|
||||||
/**
|
|
||||||
* Set the server socket.
|
|
||||||
*/
|
|
||||||
public void setServerSocket(Socket serverSocket)
|
|
||||||
{
|
|
||||||
/* Set the server socket */
|
|
||||||
this.serverSocket = serverSocket;
|
|
||||||
|
|
||||||
/* Set the address */
|
|
||||||
address = serverSocket.localAddress();
|
|
||||||
}
|
|
||||||
|
|
||||||
/**
|
|
||||||
* Get the server socket.
|
|
||||||
*/
|
|
||||||
public Socket getServerSocket()
|
|
||||||
{
|
|
||||||
return serverSocket;
|
|
||||||
}
|
|
||||||
|
|
||||||
/**
|
|
||||||
* Start listen loop.
|
|
||||||
*/
|
|
||||||
public void run()
|
|
||||||
{
|
|
||||||
serverSocket.listen(1); /* TODO: This value */
|
|
||||||
debugPrint("Server listen loop started");
|
|
||||||
|
|
||||||
/* Loop receive and dispatch connections whilst active */
|
|
||||||
while(active)
|
|
||||||
{
|
|
||||||
/* Wait for an incoming connection */
|
|
||||||
Socket clientConnection = serverSocket.accept();
|
|
||||||
|
|
||||||
/* Create a new client connection handler and start its thread */
|
|
||||||
BesterConnection besterConnection = new BesterConnection(clientConnection, server);
|
|
||||||
besterConnection.start();
|
|
||||||
|
|
||||||
/* Add this client to the list of connected clients */
|
|
||||||
server.addConnection(besterConnection);
|
|
||||||
}
|
|
||||||
|
|
||||||
/* Close the socket */
|
|
||||||
serverSocket.close();
|
|
||||||
}
|
|
||||||
|
|
||||||
public void shutdown()
|
|
||||||
{
|
|
||||||
active = false;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
public final class BesterListenerException : BesterException
|
|
||||||
{
|
|
||||||
this(BesterListener e)
|
|
||||||
{
|
|
||||||
super("Could not bind to: " ~ e.toString());
|
|
||||||
}
|
|
||||||
}
|
|
|
@ -1,105 +0,0 @@
|
||||||
module listeners.types;
|
|
||||||
|
|
||||||
import listeners.listener : BesterListener, BesterListenerException;
|
|
||||||
import server.server : BesterServer;
|
|
||||||
import std.socket : Socket, Address, AddressFamily, SocketType, SocketException;
|
|
||||||
|
|
||||||
/**
|
|
||||||
* Represents a stream socket listener over UNIX
|
|
||||||
* domain sockets.
|
|
||||||
*/
|
|
||||||
public final class UNIXListener : BesterListener
|
|
||||||
{
|
|
||||||
this(BesterServer besterServer, Address address)
|
|
||||||
{
|
|
||||||
super(besterServer);
|
|
||||||
try
|
|
||||||
{
|
|
||||||
setServerSocket(setupUNIXSocket(address));
|
|
||||||
}
|
|
||||||
catch(SocketException e)
|
|
||||||
{
|
|
||||||
throw new BesterListenerException(this);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
private Socket setupUNIXSocket(Address address)
|
|
||||||
{
|
|
||||||
Socket unixSocket = new Socket(AddressFamily.UNIX, SocketType.STREAM);
|
|
||||||
unixSocket.bind(address);
|
|
||||||
return unixSocket;
|
|
||||||
}
|
|
||||||
|
|
||||||
override public string toString()
|
|
||||||
{
|
|
||||||
string address = "unix://"~super.address.toAddrString();
|
|
||||||
return address;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
/**
|
|
||||||
* Represents a stream socket listener over TCP
|
|
||||||
* on IPv4.
|
|
||||||
*/
|
|
||||||
public final class TCP4Listener : BesterListener
|
|
||||||
{
|
|
||||||
this(BesterServer besterServer, Address address)
|
|
||||||
{
|
|
||||||
super(besterServer);
|
|
||||||
try
|
|
||||||
{
|
|
||||||
setServerSocket(setupTCP4Socket(address));
|
|
||||||
}
|
|
||||||
catch(SocketException e)
|
|
||||||
{
|
|
||||||
throw new BesterListenerException(this);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
private Socket setupTCP4Socket(Address address)
|
|
||||||
{
|
|
||||||
Socket tcp4Socket = new Socket(AddressFamily.INET, SocketType.STREAM);
|
|
||||||
tcp4Socket.bind(address);
|
|
||||||
return tcp4Socket;
|
|
||||||
}
|
|
||||||
|
|
||||||
override public string toString()
|
|
||||||
{
|
|
||||||
string address = "tcp4://"~super.address.toAddrString()~":"~super.address.toPortString();
|
|
||||||
return address;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
/**
|
|
||||||
* Represents a stream socket listener over TCP
|
|
||||||
* on IPv6.
|
|
||||||
*/
|
|
||||||
public final class TCP6Listener : BesterListener
|
|
||||||
{
|
|
||||||
this(BesterServer besterServer, Address address)
|
|
||||||
{
|
|
||||||
super(besterServer);
|
|
||||||
try
|
|
||||||
{
|
|
||||||
setServerSocket(setupTCP6Socket(address));
|
|
||||||
}
|
|
||||||
catch(SocketException e)
|
|
||||||
{
|
|
||||||
throw new BesterListenerException(this);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
private Socket setupTCP6Socket(Address address)
|
|
||||||
{
|
|
||||||
Socket tcp6Socket = new Socket(AddressFamily.INET6, SocketType.STREAM);
|
|
||||||
tcp6Socket.bind(address);
|
|
||||||
return tcp6Socket;
|
|
||||||
}
|
|
||||||
|
|
||||||
override public string toString()
|
|
||||||
{
|
|
||||||
string address = "tcp6://"~super.address.toAddrString()~":"~super.address.toPortString();
|
|
||||||
return address;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
|
@ -1,23 +0,0 @@
|
||||||
module server.accounts.base;
|
|
||||||
|
|
||||||
/**
|
|
||||||
* This represents the accounts management system of
|
|
||||||
* the server. It is only an abstract class.
|
|
||||||
*/
|
|
||||||
public abstract class BesterDataStore
|
|
||||||
{
|
|
||||||
/**
|
|
||||||
* Creates a new account with the given `username` and
|
|
||||||
* `password`.
|
|
||||||
*/
|
|
||||||
public abstract void createAccount(string username, string password);
|
|
||||||
|
|
||||||
/**
|
|
||||||
* Check if the user, `username`, exists in the database.
|
|
||||||
*/
|
|
||||||
public abstract bool userExists(string username);
|
|
||||||
|
|
||||||
public abstract bool authenticate(string username, string password);
|
|
||||||
|
|
||||||
public abstract void shutdown();
|
|
||||||
}
|
|
|
@ -1,118 +0,0 @@
|
||||||
module server.accounts.redis;
|
|
||||||
|
|
||||||
import vibe.vibe;
|
|
||||||
import server.accounts.base : BesterDataStore;
|
|
||||||
import utils.debugging : debugPrint;
|
|
||||||
|
|
||||||
/**
|
|
||||||
* This represents a Redis datastore for the Bester
|
|
||||||
* server's account management system.
|
|
||||||
*/
|
|
||||||
public final class RedisDataStore : BesterDataStore
|
|
||||||
{
|
|
||||||
|
|
||||||
/**
|
|
||||||
* Redis client.
|
|
||||||
*/
|
|
||||||
private RedisClient redisClient;
|
|
||||||
|
|
||||||
/**
|
|
||||||
* Redis database with the account information
|
|
||||||
*/
|
|
||||||
private RedisDatabase redisDatabase;
|
|
||||||
|
|
||||||
this(string address, ushort port)
|
|
||||||
{
|
|
||||||
/* Opens a connection to the redis server */
|
|
||||||
initializeRedis(address, port);
|
|
||||||
}
|
|
||||||
|
|
||||||
private void initializeRedis(string address, ushort port)
|
|
||||||
{
|
|
||||||
redisClient = new RedisClient(address, port);
|
|
||||||
redisDatabase = redisClient.getDatabase(0);
|
|
||||||
// createAccount("deavmi", "poes");
|
|
||||||
|
|
||||||
}
|
|
||||||
|
|
||||||
override public bool userExists(string username)
|
|
||||||
{
|
|
||||||
/* TODO: Implement me */
|
|
||||||
return redisDatabase.exists(username);
|
|
||||||
// return true;
|
|
||||||
}
|
|
||||||
|
|
||||||
override public bool authenticate(string username, string password)
|
|
||||||
{
|
|
||||||
debugPrint(redisClient.info());
|
|
||||||
debugPrint(redisDatabase.keys("*"));
|
|
||||||
/* Check if a key exists with the `username` */
|
|
||||||
bool accountExists = redisDatabase.exists(username);
|
|
||||||
debugPrint(accountExists);
|
|
||||||
if(accountExists)
|
|
||||||
{
|
|
||||||
/**
|
|
||||||
* Check within the key if the subkey and value pair exists.
|
|
||||||
* `(username) [password: <password>], ...`
|
|
||||||
*/
|
|
||||||
if(redisDatabase.hexists(username, "password"))
|
|
||||||
{
|
|
||||||
/* Get the password sub-field */
|
|
||||||
string passwordDB = redisDatabase.hget(username, "password");
|
|
||||||
if(cmp(password, passwordDB) == 0)
|
|
||||||
{
|
|
||||||
return true;
|
|
||||||
}
|
|
||||||
else
|
|
||||||
{
|
|
||||||
return false;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
else
|
|
||||||
{
|
|
||||||
/* TODO: Raise exception for missing password sub-key */
|
|
||||||
}
|
|
||||||
}
|
|
||||||
else
|
|
||||||
{
|
|
||||||
/* TODO: Raise exception for non-existent account */
|
|
||||||
}
|
|
||||||
|
|
||||||
/* TODO: Remove */
|
|
||||||
return false;
|
|
||||||
}
|
|
||||||
|
|
||||||
override public void createAccount(string username, string password)
|
|
||||||
{
|
|
||||||
/* TODO: Implement me */
|
|
||||||
|
|
||||||
/* Check if a key exists with the `username` */
|
|
||||||
bool accountExists = redisDatabase.exists(username);
|
|
||||||
|
|
||||||
if(!accountExists)
|
|
||||||
{
|
|
||||||
/**
|
|
||||||
* Create the new account.
|
|
||||||
* This involves creating a new key named `username`
|
|
||||||
* with a field named `"password"` matching to the value
|
|
||||||
* of `password`.
|
|
||||||
*/
|
|
||||||
redisDatabase.hset(username, "password", password);
|
|
||||||
}
|
|
||||||
else
|
|
||||||
{
|
|
||||||
/* TODO: Raise exception for an already existing account */
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
public void f()
|
|
||||||
{
|
|
||||||
|
|
||||||
}
|
|
||||||
|
|
||||||
override public void shutdown()
|
|
||||||
{
|
|
||||||
/* TODO: Should we shutdown the server? */
|
|
||||||
redisClient.shutdown();
|
|
||||||
}
|
|
||||||
}
|
|
|
@ -1,217 +0,0 @@
|
||||||
module server.informer.client;
|
|
||||||
|
|
||||||
import core.thread : Thread;
|
|
||||||
import server.server : BesterServer;
|
|
||||||
import std.socket;
|
|
||||||
import utils.message : receiveMessage, sendMessage;
|
|
||||||
import std.json;
|
|
||||||
import utils.debugging;
|
|
||||||
import std.string;
|
|
||||||
import std.conv : to;
|
|
||||||
import connection.connection : BesterConnection;
|
|
||||||
|
|
||||||
/**
|
|
||||||
* Represents a handler's connection to the
|
|
||||||
* Bester informer socket, runs as a seperate
|
|
||||||
* thread with a read, dispatch, write loop
|
|
||||||
* to handle commands and responses to the handler
|
|
||||||
* from the server.
|
|
||||||
*/
|
|
||||||
public final class BesterInformerClient : Thread
|
|
||||||
{
|
|
||||||
/* The associated `BesterServer` */
|
|
||||||
private BesterServer server;
|
|
||||||
|
|
||||||
/* The socket to the handler */
|
|
||||||
private Socket handlerSocket;
|
|
||||||
|
|
||||||
/* If the connection is still active or not */
|
|
||||||
private bool active = true;
|
|
||||||
|
|
||||||
/**
|
|
||||||
* Constructs a new `BesterInformerClient` with the
|
|
||||||
* associated BesterServer, `server`, and handler
|
|
||||||
* socket, `handlerSocket`.
|
|
||||||
*/
|
|
||||||
this(BesterServer server, Socket handlerSocket)
|
|
||||||
{
|
|
||||||
super(&worker);
|
|
||||||
this.server = server;
|
|
||||||
this.handlerSocket = handlerSocket;
|
|
||||||
}
|
|
||||||
|
|
||||||
/**
|
|
||||||
* Run's the command specified in `commandBlock` and sets the
|
|
||||||
* response in the variable pointed to by `result`.
|
|
||||||
*/
|
|
||||||
private bool runCommand(JSONValue commandBlock, ref JSONValue result)
|
|
||||||
{
|
|
||||||
try
|
|
||||||
{
|
|
||||||
/* Get the command type */
|
|
||||||
string commandType = commandBlock["type"].str();
|
|
||||||
debugPrint("CommandType: " ~ commandType);
|
|
||||||
|
|
||||||
/* Check if the command if `listClients` */
|
|
||||||
if(cmp(commandType, "listClients") == 0)
|
|
||||||
{
|
|
||||||
/* Create a JSON list of strings */
|
|
||||||
result = listClients(server);
|
|
||||||
}
|
|
||||||
/* Check if the command is `isClient` */
|
|
||||||
else if(cmp(commandType, "isClient") == 0)
|
|
||||||
{
|
|
||||||
/* The username to match */
|
|
||||||
string username = commandBlock["data"].str();
|
|
||||||
result = isClient(server, username);
|
|
||||||
}
|
|
||||||
/* Check if the command is `serverInfo` */
|
|
||||||
else if(cmp(commandType, "serverInfo") == 0)
|
|
||||||
{
|
|
||||||
result = getServerInfo(server);
|
|
||||||
}
|
|
||||||
/* Check if the command is `quit` */
|
|
||||||
else if (cmp(commandType, "quit") == 0)
|
|
||||||
{
|
|
||||||
/* Set the connection to inactive */
|
|
||||||
active = false;
|
|
||||||
result = null; /* TODO: JSOn default value */
|
|
||||||
}
|
|
||||||
/* If the command is invalid */
|
|
||||||
else
|
|
||||||
{
|
|
||||||
result = null;
|
|
||||||
return false;
|
|
||||||
}
|
|
||||||
|
|
||||||
debugPrint(result.toPrettyString());
|
|
||||||
return true;
|
|
||||||
}
|
|
||||||
catch(JSONException e)
|
|
||||||
{
|
|
||||||
return false;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
private void worker()
|
|
||||||
{
|
|
||||||
/* TODO: Implement me */
|
|
||||||
while(active)
|
|
||||||
{
|
|
||||||
/* Receive a message */
|
|
||||||
JSONValue handlerCommand;
|
|
||||||
receiveMessage(handlerSocket, handlerCommand);
|
|
||||||
|
|
||||||
/* The response to send */
|
|
||||||
JSONValue handlerResponse;
|
|
||||||
|
|
||||||
/* Respose from `runCommand` */
|
|
||||||
JSONValue runCommandData;
|
|
||||||
|
|
||||||
/* Attempt to get the JSON */
|
|
||||||
try
|
|
||||||
{
|
|
||||||
/* Get the command type */
|
|
||||||
string commandType = handlerCommand["command"]["type"].str();
|
|
||||||
debugPrint("Command: " ~ commandType);
|
|
||||||
|
|
||||||
/* Dispatch to the correct command and return a status */
|
|
||||||
bool commandStatus = runCommand(handlerCommand["command"], runCommandData);
|
|
||||||
|
|
||||||
/* Set the data */
|
|
||||||
handlerResponse["data"] = runCommandData;
|
|
||||||
|
|
||||||
/* Set the `status` field to `"0"` */
|
|
||||||
handlerResponse["status"] = commandStatus ? "0" : "1";
|
|
||||||
}
|
|
||||||
catch(JSONException e)
|
|
||||||
{
|
|
||||||
/* Set the `status` field to `"1"` */
|
|
||||||
handlerResponse["status"] = "1";
|
|
||||||
}
|
|
||||||
|
|
||||||
/* Send the response to the handler */
|
|
||||||
sendMessage(handlerSocket, handlerResponse);
|
|
||||||
}
|
|
||||||
|
|
||||||
/* Close the socket */
|
|
||||||
handlerSocket.close();
|
|
||||||
}
|
|
||||||
|
|
||||||
/**
|
|
||||||
* Shutdown the informer client.
|
|
||||||
*/
|
|
||||||
public void shutdown()
|
|
||||||
{
|
|
||||||
active = false;
|
|
||||||
}
|
|
||||||
|
|
||||||
/**
|
|
||||||
* This functions returns `string[]` where each element
|
|
||||||
* contains the username of the locally connected client.
|
|
||||||
*/
|
|
||||||
public static string[] listClients(BesterServer server)
|
|
||||||
{
|
|
||||||
string[] clientList;
|
|
||||||
|
|
||||||
for(ulong i = 0; i < server.clients.length; i++)
|
|
||||||
{
|
|
||||||
/* Make sure only to add client connections */
|
|
||||||
BesterConnection connection = server.clients[i];
|
|
||||||
if(connection.getType() == BesterConnection.Scope.CLIENT)
|
|
||||||
{
|
|
||||||
clientList ~= [connection.getCredentials()[0]];
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
return clientList;
|
|
||||||
}
|
|
||||||
|
|
||||||
/**
|
|
||||||
* This function returns `true` if the provided username
|
|
||||||
* matches a locally connected client, `false` otherwise.
|
|
||||||
*/
|
|
||||||
public static bool isClient(BesterServer server, string username)
|
|
||||||
{
|
|
||||||
for(ulong i = 0; i < server.clients.length; i++)
|
|
||||||
{
|
|
||||||
/* Make sure only to match client connections */
|
|
||||||
BesterConnection connection = server.clients[i];
|
|
||||||
if(connection.getType() == BesterConnection.Scope.CLIENT && cmp(connection.getCredentials[0], username))
|
|
||||||
{
|
|
||||||
return true;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
return false;
|
|
||||||
}
|
|
||||||
|
|
||||||
/**
|
|
||||||
* This function returns server information.
|
|
||||||
*/
|
|
||||||
public static JSONValue getServerInfo(BesterServer server)
|
|
||||||
{
|
|
||||||
/* Server information */
|
|
||||||
JSONValue serverInfo;
|
|
||||||
|
|
||||||
/* Create the `listeners` block */
|
|
||||||
JSONValue listenersBlock;
|
|
||||||
|
|
||||||
for(ulong i = 0; i < server.listeners.length; i++)
|
|
||||||
{
|
|
||||||
JSONValue listener;
|
|
||||||
listener["address"] = server.listeners[i].toString();
|
|
||||||
listenersBlock["listener"~to!(string)(i)] = listener;
|
|
||||||
}
|
|
||||||
|
|
||||||
|
|
||||||
/* TODO: Load additional information from `server.conf`'s `admin[info]` block */
|
|
||||||
|
|
||||||
/* TODO: Use as is number, no string */
|
|
||||||
serverInfo["clientCount"] = to!(string)(server.clients.length);
|
|
||||||
serverInfo["adminInfo"] = server.getAdminInfo();
|
|
||||||
serverInfo["listeners"] = listenersBlock;
|
|
||||||
|
|
||||||
return serverInfo;
|
|
||||||
}
|
|
||||||
}
|
|
|
@ -1,83 +0,0 @@
|
||||||
module server.informer.informer;
|
|
||||||
|
|
||||||
import core.thread : Thread;
|
|
||||||
import server.server : BesterServer;
|
|
||||||
import std.socket;
|
|
||||||
import server.informer.client : BesterInformerClient;
|
|
||||||
|
|
||||||
/**
|
|
||||||
* The `BesterInformer` allows handlers to query (out-of-band)
|
|
||||||
* information regarding this bester server.
|
|
||||||
*
|
|
||||||
* This is useful when the message handler requires additional
|
|
||||||
* information before it sends its final message response.
|
|
||||||
*/
|
|
||||||
public final class BesterInformer : Thread
|
|
||||||
{
|
|
||||||
/* The associated `BesterServer` */
|
|
||||||
private BesterServer server;
|
|
||||||
|
|
||||||
/* Informer socket */
|
|
||||||
private Socket informerSocket;
|
|
||||||
|
|
||||||
/* Whether or not the informer server is active */
|
|
||||||
private bool active = true;
|
|
||||||
|
|
||||||
/* Connected clients */
|
|
||||||
private BesterInformerClient[] informerClients;
|
|
||||||
|
|
||||||
/**
|
|
||||||
* Constructs a new `BesterInformer` object
|
|
||||||
* which will accept incoming connections
|
|
||||||
* (upon calling `.start`) from handlers,
|
|
||||||
* over a UNIX domain socket, requesting
|
|
||||||
* server information.
|
|
||||||
*/
|
|
||||||
this(BesterServer server)
|
|
||||||
{
|
|
||||||
/* Set the worker function */
|
|
||||||
super(&worker);
|
|
||||||
|
|
||||||
/* Setup the socket */
|
|
||||||
informerSocket = new Socket(AddressFamily.UNIX, SocketType.STREAM);
|
|
||||||
informerSocket.bind(new UnixAddress("bInformer"));
|
|
||||||
informerSocket.listen(1); /* TODO: Value */
|
|
||||||
|
|
||||||
/* Set the associated BesterServer */
|
|
||||||
this.server = server;
|
|
||||||
}
|
|
||||||
|
|
||||||
/**
|
|
||||||
* Client send-receive loop.
|
|
||||||
* Accepts incoming connections to the informer
|
|
||||||
* and dispatches them to a worker thread.
|
|
||||||
*/
|
|
||||||
private void worker()
|
|
||||||
{
|
|
||||||
while(active)
|
|
||||||
{
|
|
||||||
/* Accept the queued incoming connection */
|
|
||||||
Socket handlerSocket = informerSocket.accept();
|
|
||||||
|
|
||||||
/**
|
|
||||||
* Create a new worker for the informer client, adds it
|
|
||||||
* to the client queue and dispatches its worker thread.
|
|
||||||
*/
|
|
||||||
BesterInformerClient newInformer = new BesterInformerClient(server, handlerSocket);
|
|
||||||
informerClients ~= newInformer;
|
|
||||||
newInformer.start();
|
|
||||||
}
|
|
||||||
|
|
||||||
/* Close the informer socket */
|
|
||||||
informerSocket.close();
|
|
||||||
}
|
|
||||||
|
|
||||||
public void shutdown()
|
|
||||||
{
|
|
||||||
for(ulong i = 0; i < informerClients.length; i++)
|
|
||||||
{
|
|
||||||
informerClients[i].shutdown();
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
}
|
|
|
@ -1,356 +0,0 @@
|
||||||
module server.server;
|
|
||||||
|
|
||||||
import utils.debugging : debugPrint;
|
|
||||||
import std.conv : to;
|
|
||||||
import std.socket : Socket, AddressFamily, SocketType, ProtocolType, parseAddress;
|
|
||||||
import core.thread : Thread;
|
|
||||||
import core.sync.mutex;
|
|
||||||
import std.stdio : writeln, File;
|
|
||||||
import std.json : JSONValue, parseJSON, JSONException, JSONType, toJSON;
|
|
||||||
import std.string : cmp, strip;
|
|
||||||
import handlers.handler : MessageHandler;
|
|
||||||
import listeners.listener : BesterListener;
|
|
||||||
import connection.connection : BesterConnection;
|
|
||||||
import server.informer.informer : BesterInformer;
|
|
||||||
import server.accounts.base : BesterDataStore;
|
|
||||||
import server.accounts.redis : RedisDataStore;
|
|
||||||
|
|
||||||
/**
|
|
||||||
* Represents an instance of a Bester server.
|
|
||||||
*/
|
|
||||||
public final class BesterServer
|
|
||||||
{
|
|
||||||
/**
|
|
||||||
* Array of message handlers attached to
|
|
||||||
* this server.
|
|
||||||
*/
|
|
||||||
public MessageHandler[] handlers;
|
|
||||||
|
|
||||||
/**
|
|
||||||
* The server's socket.
|
|
||||||
*/
|
|
||||||
private Socket serverSocket;
|
|
||||||
/* TODO: The above to be replaced */
|
|
||||||
|
|
||||||
/**
|
|
||||||
* Socket listeners for incoming connections.
|
|
||||||
*/
|
|
||||||
public BesterListener[] listeners;
|
|
||||||
|
|
||||||
/**
|
|
||||||
* Connected clients.
|
|
||||||
*/
|
|
||||||
public BesterConnection[] clients;
|
|
||||||
private Mutex clientsMutex;
|
|
||||||
|
|
||||||
/**
|
|
||||||
* The informer server.
|
|
||||||
*/
|
|
||||||
private BesterInformer informer;
|
|
||||||
|
|
||||||
/**
|
|
||||||
* Admin information regarding this server.
|
|
||||||
*/
|
|
||||||
private JSONValue adminInfo;
|
|
||||||
|
|
||||||
/**
|
|
||||||
* The datastore for the account information.
|
|
||||||
*/
|
|
||||||
private BesterDataStore dataStore;
|
|
||||||
|
|
||||||
/**
|
|
||||||
* Returns a list of BesterConnection objects that
|
|
||||||
* match the usernames provided.
|
|
||||||
*
|
|
||||||
* @param usernames Array of strings of usernames to match to
|
|
||||||
*/
|
|
||||||
public BesterConnection[] getClients(string[] usernames)
|
|
||||||
{
|
|
||||||
/* List of authenticated users matching `usernames` */
|
|
||||||
BesterConnection[] matchedUsers;
|
|
||||||
|
|
||||||
//debugPrint("All clients (ever connected): " ~ to!(string)(clients));
|
|
||||||
|
|
||||||
/* Search through the provided usernames */
|
|
||||||
for(ulong i = 0; i < usernames.length; i++)
|
|
||||||
{
|
|
||||||
for(ulong k = 0; k < clients.length; k++)
|
|
||||||
{
|
|
||||||
/* The potentially-matched user */
|
|
||||||
BesterConnection potentialMatch = clients[k];
|
|
||||||
|
|
||||||
/* Check if the user is authenticated */
|
|
||||||
if(potentialMatch.getType() == BesterConnection.Scope.CLIENT && cmp(potentialMatch.getCredentials()[0], usernames[i]) == 0)
|
|
||||||
{
|
|
||||||
matchedUsers ~= potentialMatch;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
return matchedUsers;
|
|
||||||
}
|
|
||||||
|
|
||||||
/**
|
|
||||||
* Adds a new Connection, `connection`, to the server.
|
|
||||||
*/
|
|
||||||
public void addConnection(BesterConnection connection)
|
|
||||||
{
|
|
||||||
/**
|
|
||||||
* Lock the mutex so that only one listener thread
|
|
||||||
* may access the array at a time.
|
|
||||||
*/
|
|
||||||
clientsMutex.lock();
|
|
||||||
|
|
||||||
/**
|
|
||||||
* Append the connection to the array
|
|
||||||
*/
|
|
||||||
clients ~= connection;
|
|
||||||
|
|
||||||
/**
|
|
||||||
* Release the mutex so other listeners can now append
|
|
||||||
* to the array.
|
|
||||||
*/
|
|
||||||
clientsMutex.unlock();
|
|
||||||
}
|
|
||||||
|
|
||||||
/* TODO: Add more thread sfaety here and abroad */
|
|
||||||
|
|
||||||
/**
|
|
||||||
* Adds a listener, `listener`, to this server's
|
|
||||||
* listener set.
|
|
||||||
*/
|
|
||||||
public void addListener(BesterListener listener)
|
|
||||||
{
|
|
||||||
this.listeners ~= listener;
|
|
||||||
}
|
|
||||||
|
|
||||||
/**
|
|
||||||
* Constructs a new BesterServer with the given
|
|
||||||
* JSON configuration.
|
|
||||||
*/
|
|
||||||
this(JSONValue config)
|
|
||||||
{
|
|
||||||
/* TODO: Bounds check and JSON type check */
|
|
||||||
//debugPrint("Setting up socket...");
|
|
||||||
//setupServerSocket(config["network"]);
|
|
||||||
|
|
||||||
/* TODO: Bounds check and JSON type check */
|
|
||||||
debugPrint("Setting up message handlers...");
|
|
||||||
setupHandlers(config["handlers"]);
|
|
||||||
setupDatabase(config["database"]);
|
|
||||||
|
|
||||||
/* Initialize the `clients` array mutex */
|
|
||||||
clientsMutex = new Mutex();
|
|
||||||
}
|
|
||||||
|
|
||||||
/* TODO: Add comment, implement me */
|
|
||||||
private void setupDatabase(JSONValue databaseBlock)
|
|
||||||
{
|
|
||||||
/* Get the type */
|
|
||||||
string dbType = databaseBlock["type"].str();
|
|
||||||
|
|
||||||
if(cmp(dbType, "redis") == 0)
|
|
||||||
{
|
|
||||||
/* get the redis block */
|
|
||||||
JSONValue redisBlock = databaseBlock["redis"];
|
|
||||||
|
|
||||||
/* Get information */
|
|
||||||
string address = redisBlock["address"].str();
|
|
||||||
ushort port = to!(ushort)(redisBlock["port"].str());
|
|
||||||
|
|
||||||
/* Create the redis datastore */
|
|
||||||
// dataStore = new RedisDataStore(address, port);
|
|
||||||
// dataStore.createAccount("bruh","fdgdg");
|
|
||||||
// writeln("brdfsfdhjk: ", dataStore.userExists("bruh"));
|
|
||||||
// writeln("brfdddhjk: ", dataStore.userExists("brsdfuh"));
|
|
||||||
// writeln("brfddhjk: ", dataStore.userExists("bradsfuh"));
|
|
||||||
// writeln("brfddhjk: ", dataStore.userExists("brasuh"));
|
|
||||||
// writeln("brfdhdgfgsfdsgfdgfdsjk: ", dataStore.userExists("brdfsauh"));
|
|
||||||
// writeln("brfdhjk: ", dataStore.userExists("brfdsasuh"));
|
|
||||||
// writeln("brfdhjk: ", dataStore.userExists("brasuh"));
|
|
||||||
// writeln("brfdhjk: ", dataStore.userExists("brsauh"));
|
|
||||||
// writeln("brfdhjk: ", dataStore.userExists("brsaasuh"));
|
|
||||||
// writeln("brfdhjk: ", dataStore.userExists("brusaasfh"));
|
|
||||||
// writeln("fhdjfhjdf");
|
|
||||||
// dataStore.authenticate("dd","dd");
|
|
||||||
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
/**
|
|
||||||
* Given JSON, `handlerBlock`, this will setup the
|
|
||||||
* relevant message handlers.
|
|
||||||
*/
|
|
||||||
private void setupHandlers(JSONValue handlerBlock)
|
|
||||||
{
|
|
||||||
/* TODO: Implement me */
|
|
||||||
debugPrint("Constructing message handlers...");
|
|
||||||
handlers = MessageHandler.constructHandlers(this, handlerBlock);
|
|
||||||
}
|
|
||||||
|
|
||||||
/**
|
|
||||||
* Setup the server socket.
|
|
||||||
*/
|
|
||||||
private void setupServerSocket(JSONValue networkBlock)
|
|
||||||
{
|
|
||||||
string bindAddress;
|
|
||||||
ushort listenPort;
|
|
||||||
|
|
||||||
JSONValue jsonAddress, jsonPort;
|
|
||||||
|
|
||||||
debugPrint(networkBlock);
|
|
||||||
|
|
||||||
/* TODO: Bounds check */
|
|
||||||
jsonAddress = networkBlock["address"];
|
|
||||||
jsonPort = networkBlock["port"];
|
|
||||||
|
|
||||||
bindAddress = jsonAddress.str;
|
|
||||||
listenPort = cast(ushort)jsonPort.integer;
|
|
||||||
|
|
||||||
debugPrint("Binding to address: " ~ bindAddress ~ " and port " ~ to!(string)(listenPort));
|
|
||||||
|
|
||||||
/* Create a socket */
|
|
||||||
serverSocket = new Socket(AddressFamily.INET, SocketType.STREAM, ProtocolType.TCP);
|
|
||||||
serverSocket.bind(parseAddress(bindAddress, listenPort));
|
|
||||||
}
|
|
||||||
|
|
||||||
/**
|
|
||||||
* Starts all the listeners.
|
|
||||||
*/
|
|
||||||
private void startListeners()
|
|
||||||
{
|
|
||||||
for(ulong i = 0; i < listeners.length; i++)
|
|
||||||
{
|
|
||||||
debugPrint("Starting listener \"" ~ listeners[i].toString() ~ "\"...");
|
|
||||||
listeners[i].start();
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
/**
|
|
||||||
* Starts the BesterInformer.
|
|
||||||
*/
|
|
||||||
private void startInformer()
|
|
||||||
{
|
|
||||||
informer = new BesterInformer(this);
|
|
||||||
informer.start();
|
|
||||||
}
|
|
||||||
|
|
||||||
/**
|
|
||||||
* Start listen loop.
|
|
||||||
*/
|
|
||||||
public void run()
|
|
||||||
{
|
|
||||||
/* Start the listeners */
|
|
||||||
startListeners();
|
|
||||||
|
|
||||||
/* Start the informer */
|
|
||||||
startInformer();
|
|
||||||
}
|
|
||||||
|
|
||||||
/**
|
|
||||||
* Authenticate the user.
|
|
||||||
*/
|
|
||||||
public bool authenticate(string username, string password)
|
|
||||||
{
|
|
||||||
/* TODO: Implement me */
|
|
||||||
debugPrint("Attempting to authenticate:\n\nUsername: " ~ username ~ "\nPassword: " ~ password);
|
|
||||||
|
|
||||||
/* If the authentication went through */
|
|
||||||
bool authed = true;
|
|
||||||
|
|
||||||
/* Strip the username of whitespace (TODO: Should we?) */
|
|
||||||
username = strip(username);
|
|
||||||
|
|
||||||
/* Make sure username and password are not empty */
|
|
||||||
if(cmp(username, "") != 0 && cmp(password, "") != 0)
|
|
||||||
{
|
|
||||||
/* TODO: Fix me */
|
|
||||||
//authed = dataStore.authenticate(username, password);
|
|
||||||
}
|
|
||||||
else
|
|
||||||
{
|
|
||||||
authed = false;
|
|
||||||
}
|
|
||||||
|
|
||||||
debugPrint("Auth" ~ to!(string)(authed));
|
|
||||||
|
|
||||||
return authed;
|
|
||||||
}
|
|
||||||
|
|
||||||
/**
|
|
||||||
* Returns the MessageHandler object of the requested type.
|
|
||||||
*/
|
|
||||||
public MessageHandler findHandler(string payloadType)
|
|
||||||
{
|
|
||||||
/* The found MessageHandler */
|
|
||||||
MessageHandler foundHandler;
|
|
||||||
|
|
||||||
for(uint i = 0; i < handlers.length; i++)
|
|
||||||
{
|
|
||||||
if(cmp(handlers[i].getPluginName(), payloadType) == 0)
|
|
||||||
{
|
|
||||||
foundHandler = handlers[i];
|
|
||||||
break;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
return foundHandler;
|
|
||||||
}
|
|
||||||
|
|
||||||
public static bool isBuiltInCommand(string command)
|
|
||||||
{
|
|
||||||
/* Whether or not `payloadType` is a built-in command */
|
|
||||||
bool isBuiltIn = true;
|
|
||||||
|
|
||||||
|
|
||||||
return isBuiltIn;
|
|
||||||
}
|
|
||||||
|
|
||||||
/**
|
|
||||||
* Stops the server.
|
|
||||||
*/
|
|
||||||
private void shutdown()
|
|
||||||
{
|
|
||||||
/* Stop the informer service */
|
|
||||||
informer.shutdown();
|
|
||||||
|
|
||||||
/* Shutdown all the listeners */
|
|
||||||
shutdownListeners();
|
|
||||||
|
|
||||||
/* Shutdown all the clients */
|
|
||||||
shutdownClients();
|
|
||||||
|
|
||||||
/* Shutdown the datastore */
|
|
||||||
dataStore.shutdown();
|
|
||||||
}
|
|
||||||
|
|
||||||
/**
|
|
||||||
* Loops through the list of `BesterListener`s and
|
|
||||||
* shuts each of them down.
|
|
||||||
*/
|
|
||||||
private void shutdownListeners()
|
|
||||||
{
|
|
||||||
/* Shutdown all the listeners */
|
|
||||||
for(ulong i = 0; i < listeners.length; i++)
|
|
||||||
{
|
|
||||||
listeners[i].shutdown();
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
/**
|
|
||||||
* Loops through the list of `BesterConnection`s and
|
|
||||||
* shuts each of them down.
|
|
||||||
*/
|
|
||||||
private void shutdownClients()
|
|
||||||
{
|
|
||||||
/* Shutdown all the clients */
|
|
||||||
for(ulong i = 0; i < clients.length; i++)
|
|
||||||
{
|
|
||||||
clients[i].shutdown();
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
public JSONValue getAdminInfo()
|
|
||||||
{
|
|
||||||
return adminInfo;
|
|
||||||
}
|
|
||||||
}
|
|
|
@ -1,16 +0,0 @@
|
||||||
module utils.debugging;
|
|
||||||
|
|
||||||
import std.stdio : writeln;
|
|
||||||
import std.process : environment;
|
|
||||||
import std.conv : to;
|
|
||||||
|
|
||||||
alias dprint = debugPrint;
|
|
||||||
|
|
||||||
void debugPrint(MessageType)(MessageType message)
|
|
||||||
{
|
|
||||||
/* Check if the environment variable for `B_DEBUG` exists */
|
|
||||||
if(!(environment.get("B_DEBUG") is null))
|
|
||||||
{
|
|
||||||
writeln("[DEBUG] " ~ to!(string)(message));
|
|
||||||
}
|
|
||||||
}
|
|
|
@ -1,46 +0,0 @@
|
||||||
module utils.message;
|
|
||||||
|
|
||||||
import std.socket : Socket, SocketFlags;
|
|
||||||
import std.json : JSONValue, parseJSON, toJSON;
|
|
||||||
import utils.debugging : debugPrint;
|
|
||||||
import std.stdio : writeln;
|
|
||||||
import base.net : NetworkException;
|
|
||||||
import bmessage : bformatreceiveMessage = receiveMessage, bformatsendMessage = sendMessage;
|
|
||||||
|
|
||||||
/**
|
|
||||||
* Generalized socket receive function which will read into the
|
|
||||||
* variable pointed to by `receiveMessage` by reading from the
|
|
||||||
* socket `originator`.
|
|
||||||
*/
|
|
||||||
public void receiveMessage(Socket originator, ref JSONValue receiveMessage)
|
|
||||||
{
|
|
||||||
/* The received bytes */
|
|
||||||
byte[] receivedBytes;
|
|
||||||
|
|
||||||
if(!bformatreceiveMessage(originator, receivedBytes))
|
|
||||||
{
|
|
||||||
throw new NetworkException(originator);
|
|
||||||
}
|
|
||||||
|
|
||||||
receiveMessage = parseJSON(cast(string)receivedBytes);
|
|
||||||
}
|
|
||||||
|
|
||||||
/**
|
|
||||||
* Generalized socket send function which will send the JSON
|
|
||||||
* encoded message, `jsonMessage`, over to the client at the
|
|
||||||
* other end of the socket, `recipient`.
|
|
||||||
*
|
|
||||||
* It gets the length of `jsonMessage` and encodes a 4 byte
|
|
||||||
* message header in little-endian containing the message's
|
|
||||||
* length.
|
|
||||||
*/
|
|
||||||
public void sendMessage(Socket recipient, JSONValue jsonMessage)
|
|
||||||
{
|
|
||||||
if(!bformatsendMessage(recipient, cast(byte[])toJSON(jsonMessage)))
|
|
||||||
{
|
|
||||||
throw new NetworkException(recipient);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
|
|
||||||
/* TODO: Hash message: Next-gen implementation */
|
|
Loading…
Reference in New Issue