/* Node.JS Server */
//INCLUDED MODULES
var WebSocketServer = require('websocket').server; //Websockets
var Http = require('http'); //HTTP Connection
var Cluster = require('cluster'); //Cluster module
var NumCpus = require('os').cpus().length; //Number of CPU cores
var Sqlite3 = require("sqlite3").verbose(); //SQLite3 module
//SETTING VARIABLES
var Port = 8080; //Port the server will use
var ClientAliveTimer = 20000; //Amount of time the server will ping the client to ensure it is connected
var ClientAliveGraceTimer = 10000; //Amount of grace timer after ClientAliveTimer has timed out
var MaxFrameSize = 0x100000; //Max received frame size in bytes
var MaxMsgSize = 0x100000; //Max fragmented message size in bytes
//GLOBAL VARS
var UniqueID = 0; //Incrementing on every connection
var ConnArray = []; //Holds all connections
var NumOfQueries = 0; //Number of SQL queries
var ClustersExiting = 10; //Keep track of clusters exiting
//FILE SYSTEM
var FileSystem = require("fs"); //File System to be used by SQLite3 and the logger
var Dir = './DataFiles'; //Directory of backup folder
//SQLITE3 DATABASE VARIABLES
var dbFile = "./DataFiles/UserDB.db"; //Database filename
var DBExists = FileSystem.existsSync(dbFile); //Check if the Database exists
var DB = new Sqlite3.Database(dbFile); //DB file
//CREATE DATAFILE DIRECTORY
if (!FileSystem.existsSync(Dir)){ FileSystem.mkdirSync(Dir);}
//DB CHECK AND TABLE CREATION
if(!DBExists) {
//USERDATA table
DB.run( "CREATE TABLE USERDATA(" +
"EMAIL CHAR(60) PRIMARY KEY NOT NULL," +
"PASS CHAR(15) NOT NULL," +
"LEVEL TINYINT NOT NULL," +
"EXP INT NOT NULL," +
"ISMEMBER BOOLEAN NOT NULL," +
"CHARCREATED BOOLEAN NOT NULL" +
")");
}
//WRITE TO LOG FILE AND CONSOLE
function EventLogging(LogText) {
console.log(LogText);
FileSystem.appendFile("./DataFiles/ServerLog.txt", LogText + "\r\n", function(err) {
if(err) {
return console.log(err);
}
});
}
//RETURN THE FORMATTED DATE
function LogDate(){
var DateGet = new Date();
var LogDate = " DATE: " + DateGet.getDate() + "/" + DateGet.getMonth() + "/" + DateGet.getFullYear() + " (" + DateGet.getUTCHours() + ":" + DateGet.getUTCMinutes() + ")";
return LogDate;
}
//ENCODE AND DECODE UTF8 (MESSAGES ARE RECIEVED IN UTF8)
function Encode_Utf8(string) { return unescape(encodeURIComponent(string)); }
function Decode_Utf8(string) { return decodeURIComponent(escape(string)); }
//HTTP SERVER
var server = Http.createServer(function(request, response) {});
//WEBSOCKET SERVER
wsServer = new WebSocketServer({
httpServer: server,
maxReceivedFrameSize: MaxFrameSize,
maxReceivedMessageSize: MaxMsgSize,
autoAcceptConnections: false,
keepalive: true,
keepaliveInterval: ClientAliveTimer,
dropConnectionOnKeepaliveTimeout: true,
keepaliveGracePeriod: ClientAliveGraceTimer
});
//SERVER HANDLING [CLOSE]
server.on('close', function() {
//Close all open connections
for(i=0; i<ConnArray.length; i++){ ConnArray[i].close(); }
var KeyLoop = true;
while(KeyLoop){
if (NumOfQueries == 0){
if (DB.open) { DB.close(); }
EventLogging("[SERVER CLOSING] ID: " + process.pid + LogDate());
KeyLoop = false;
}
}
});
//SERVER HANDLING [ERROR]
server.on('error', function(err) { EventLogging("[HTTP SERVER ERROR]: ID: " + process.pid + LogDate() + " ERROR: " + err.code); });
//SERVER HANDLING [REQUEST]
server.on('request', function(request, response) { });
//SERVER HANDLING [CONNECTION]
server.on('connection', function(socket) { });
//SERVER HANDLING [CONNECT]
server.on('connect', function(request, socket, head) { });
//CLUSTER HANDLING [EXIT]
Cluster.on('exit', function(worker, code, signal) {
//Close all open connections
for(i=0; i<ConnArray.length; i++){ ConnArray[i].close(); } //UNSURE IF THIS WORKS
var KeepLoop = true;
if (typeof (worker.suicide) !== "undefined"){ //If it committed suicide, restart it
if (worker.suicide === true) {
//Make sure we aren't loosing too many clusters
ClustersExiting --;
if (ClustersExiting <= 0){ process.exit(1); };
while(KeyLoop){
if (NumOfQueries == 0){
if (DB.open) { DB.close(); } //Close the DB connection
EventLogging("[CLUSTER RESTARTING] ID: " + worker.id + LogDate());
Cluster.fork(); //Create a new cluster
KeyLoop = false;
}
}
}
} else { //Else just clear up
while(KeyLoop){
if (NumOfQueries == 0){
if (DB.open) { DB.close(); } //Close the DB connection
EventLogging("[CLUSTER CLOSING] ID: " + worker.id + LogDate());
KeyLoop = false;
}
}
}
});
//CLUSTER HANDLING [FORK]
Cluster.on('fork', function(worker) { EventLogging("[CLUSTER FORKED] ID: " + worker.id + LogDate()); });
//CLUSTER HANDLING [DISCONNECT]
Cluster.on('disconnect', function(worker) { EventLogging("[CLUSTER DISCONNECTED] ID: " + worker.id + LogDate()); });
//CLUSTER HANDLING [ONLINE]
Cluster.on('online', function(worker) { EventLogging("[CLUSTER ONLINE] ID: " + worker.id + LogDate()); });
//CLUSTER HANDLING [ul]
Cluster.on('listening', function(worker, address) { });
//CLUSTER HANDLING [SETUP]
Cluster.on('setup', function(settings) { });
//PROCESS HANDLING [SIGTERM]
process.on('SIGTERM', function() {
server.close(function(){
process.exit(0);
});
});
//PROCESS HANDLING [SIGINT]
process.on('SIGINT', function() {
server.close(function(){
process.exit(0);
});
});
//PROCESS HANDLING [EXIT]
process.on('exit', function(code) {
if (Cluster.isMaster) { EventLogging("[MASTER PROCESS EXIT CODE] ID: " + process.pid + LogDate() + " EXIT CODE: " + code); }
else { EventLogging("[PROCESS EXIT CODE] ID: " + process.pid + LogDate() + " EXIT CODE: " + code); }
});
//PROCESS HANDLING [UNCAUGHT EXCEPTION]
process.on('uncaughtException', function(err) { EventLogging("[UNCAUGHT EXCEPTION] ID: " + process.pid + LogDate() + " ERROR: " + err.code); });
//LOG THE USER IN
function LogUserIn(UsrEmail, UsrPass, connection){
NumOfQueries ++; //Add to the number of queries
//SQL query to check if the email already exists
DB.get("SELECT CHARCREATED, EMAIL, PASS from USERDATA where EMAIL=\"" + UsrEmail +"\"", function(err, row){
NumOfQueries --; //Reduce the number of queries
//Check row for a value
if (typeof row != 'undefined') {
if (row.EMAIL) {
if (row.EMAIL === UsrEmail && row.PASS === UsrPass){ //User and pass match
if(row.CHARCREATED) { //Character already created
connection.email = UsrEmail; //Add the user email to the connection object
connection.send("C2"); //return C2 so to login
EventLogging("[LOGIN]:" + LogDate() + " EMAIL: " + UsrEmail);
} else if(!row.CHARCREATED) { //Character needs creating
connection.email = UsrEmail; //Add the user email to the connection object
connection.send("C2N"); //return C2N so to login and create character
EventLogging("[LOGIN]:" + LogDate() + " EMAIL: " + UsrEmail);
}
} else if (row.EMAIL === UsrEmail && row.PASS !== UsrPass) { //Email is right but pass is not right
connection.send("LF2"); //Return LF2 as failed password
EventLogging("[LOGIN FAILED [2]]:" + LogDate() + " EMAIL: " + UsrEmail);
}
}
} else {
connection.send("LF1"); //Return LF1 as failed email login
EventLogging("[LOGIN FAILED [1]]:" + LogDate() + " EMAIL: " + UsrEmail);
}
//Error handling
if (err !== null) {
EventLogging("[SQL ERROR]" + LogDate() + " ERROR: " + err);
connection.send("SF"); //Report SF for SQL Fault
}
});
}
//CREATE USER ACCOUNT
function CreateUsrAcc(UsrEmail, UsrPass, connection){
NumOfQueries ++; //Add to the number of queries
//Serialize the database calls
DB.serialize( function() {
//SQL query to check if the email already exists
DB.all("SELECT Email from USERDATA where Email=\"" + UsrEmail +"\"", function(err, rows){
NumOfQueries --; //Reduce the number of queries
//Check rows for a value
if (rows.length > 0) {
if (rows[0].EMAIL) {
EventLogging("[CREATION ERROR]" + LogDate() + rows[0].EMAIL + " already exists");
connection.send("CD"); //Return CD so the client knows that the email already exists
}
} else {
NumOfQueries ++; //Add to the number of queries
//SQL query to create an account
var SqlQuery = DB.run("INSERT into USERDATA(EMAIL, PASS, LEVEL, EXP, ISMEMBER, CHARCREATED) VALUES (\"" + UsrEmail + "\",\"" + UsrPass + "\",\"1\",\"0\",\"0\",\"0\")", function(err) {
NumOfQueries --; //Reduce the number of queries
//SQL error checking
if (err !== null) {
EventLogging("[SQL ERROR] " + LogDate() + " ERROR: " + err);
connection.send("SF"); //Report SF for SQL Fault
} else {
connection.send("C1"); //Return C1 so client knows the account is created
connection.email = UsrEmail; //Add the user email to the connection object
EventLogging("[ACCOUNT CREATED]:" + LogDate() + " EMAIL: " + UsrEmail);
}
});
}
//Error handling
if (err !== null) {
EventLogging("[SQL ERROR]" + LogDate() + " ERROR: " + err);
connection.send("SF"); //Report SF for SQL Fault
}
});
});
}
//CHECK IF ORIGIN IS ALLOWED
function originIsAllowed(origin) {
//Code to check of origin is allowed//TODO
return true;
}
//CLUSTERS/CLUSTER MASTER
if (Cluster.isMaster) {
//CREATE OUR CLUSTERS
for (var i = 0; i < NumCpus; i++) { Cluster.fork(); }
} else {
//SERVER HANDLING [ul]
server.listen(Port, function() {
EventLogging("[HTTP SERVER STARTED] ID: " + process.pid + LogDate() + " is listening on port " + Port);
//Post all command line arguments
if (process.argv.length > 2) {
//Get all of the arguments into a string
var TmpString = "";
for(i=2; i!=process.argv.length; i++){
TmpString = TmpString + process.argv[i] + " ";
}
EventLogging("[COMMAND LINE ARGUMENTS] " + TmpString);
}
});
//WEBSOCKET HANDLING
wsServer.on('request', function(request) {
//Is the origin allowed
if (!originIsAllowed(request.origin)) {
EventLogging("[LOGIN REJECTED]:" + LogDate() + " ORIGIN: " + request.origin);
request.reject(); //Reject the connection
return;
}
var connection = request.accept(null, request.origin); //Accept the connection
ConnArray.push(connection); //Add the client to the array
UniqueID ++; //Increment the UID
EventLogging("[CONNETION ACCEPTED]:" + LogDate() + " ORIGIN: " + request.origin);
//MESSAGE HANDLING
connection.on('message', function(message) {
if (message.type === 'utf8') { //Ensure the message is in UTF8
EventLogging("[MESSAGE]: " + LogDate() + message.utf8Data);
var TmpMsg = (Decode_Utf8(message.utf8Data)); //Decode the message data
var MsgData = TmpMsg.split(","); //Split the message for the data
//Check what the client has requested
switch(MsgData[0]) {
case "C1": //Create account
CreateUsrAcc(MsgData[1],MsgData[2], connection);
break;
case "C2": //Login
LogUserIn(MsgData[1],MsgData[2], connection)
break;
}
}
});
//CONNECTION HANDLING [ERROR]
connection.on('error', function(err) {
EventLogging("[CLIENT ERROR]:" + LogDate() + " " + err);
});
//CONNECTION HANDLING [CLOSE]
connection.on('close', function(connection) {
EventLogging("[CLIENT CLOSED]:" + LogDate());
});
});
}[/code:3jvxiztl]