0 Favourites

NodeJs Server

  • I'm currently developing this server as I learned JS a few weeks ago, it works with websockets in C2.

    There is probably a lot I can do better or have done wrong but I thought I would post this here so that other newbies like myself may give it a try.

    /* 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]
  • Construct 3

    Buy Construct 3

    Develop games in your browser. Powerful, performant & highly capable.

    Buy Now Construct 3 users don't see these ads
Jump to:
Active Users
There are 1 visitors browsing this topic (0 users and 1 guests)