211 lines
5.3 KiB
JavaScript
Executable File
211 lines
5.3 KiB
JavaScript
Executable File
// WebSocket Server
|
|
// James Taylor - 466598
|
|
|
|
// Emit events
|
|
var events = require("events");
|
|
var util = require("util");
|
|
|
|
|
|
function WebSocketServer(sendMethod, port) {
|
|
|
|
// Used to create the http server that will be upgraded
|
|
var http = require("http");
|
|
// Used to create the "sec-websocket-accept" key
|
|
var crypto = require("crypto");
|
|
// Used to retrive a message sent by the client
|
|
var recievedFrame = require("./framing");
|
|
// Used to create a connection instance
|
|
var connections = require("./connections");
|
|
|
|
var connectionList = new connections.serverConnections();
|
|
var frameOperation = new recievedFrame();
|
|
frameOperation.setConnectionList(connectionList);
|
|
this.framer = frameOperation;
|
|
this.connect = connectionList;
|
|
var port = port || 8080;
|
|
|
|
events.EventEmitter.call(this);
|
|
var self = this;
|
|
frameOperation.setEmitter(this);
|
|
|
|
console.log("Creating Server...");
|
|
|
|
switch(sendMethod) {
|
|
case "all":
|
|
connectionList.setSend("all");
|
|
break;
|
|
case "others":
|
|
connectionList.setSend("others");
|
|
break;
|
|
case "echo":
|
|
connectionList.setSend("echo");
|
|
break;
|
|
case "none":
|
|
connectionList.setSend("none");
|
|
break;
|
|
default:
|
|
connectionList.setSend("all");
|
|
}
|
|
|
|
// The http server that acts as the underlying server
|
|
var newServer = http.createServer(function (request, response) {
|
|
request.on("end", function() {
|
|
response.writeHead(200, {
|
|
'Content-Type': 'text/html'
|
|
});
|
|
response.end("Connect using the ws:// prefix");
|
|
});
|
|
}).listen(port);
|
|
|
|
console.log("Server started and listening for connections on port " + port);
|
|
|
|
//listens for an upgrade
|
|
newServer.on("upgrade", function (res, socket, head) {
|
|
var key = acceptKey(res.headers["sec-websocket-key"]);
|
|
socket.write("HTTP/1.1 101 Switching Protocols\r\n" +
|
|
"Upgrade: websocket\r\n" +
|
|
"Connection: Upgrade\r\n" +
|
|
"Sec-WebSocket-Accept: " + key + "\r\n" +
|
|
"\r\n");
|
|
socket.setKeepAlive(true);
|
|
socket.removeAllListeners("timeout");
|
|
var newConnection = new connections.socketConnection(socket, connectionList.getNewID());
|
|
connectionList.addToConnectionList(newConnection);
|
|
var connectionID = newConnection.returnID();
|
|
self.emit("connection", connectionID);
|
|
socket.on("data", function(data) {
|
|
frameOperation.checkRecievedData(data, connectionID);
|
|
});
|
|
socket.on("close", function(e) {
|
|
self.emit("closedconnection", connectionID);
|
|
});
|
|
});
|
|
|
|
// Used to return the accept key
|
|
function acceptKey(clientKey) {
|
|
// The Globally Unique Identifier
|
|
var GUID = "258EAFA5-E914-47DA-95CA-C5AB0DC85B11";
|
|
var key = clientKey + GUID;
|
|
var sha = crypto.createHash('sha1');
|
|
sha.update(key);
|
|
key = sha.digest(encoding="base64");
|
|
return key;
|
|
}
|
|
|
|
|
|
exports.sendFrame = connections.sendFrame;
|
|
|
|
}
|
|
|
|
util.inherits(WebSocketServer, events.EventEmitter);
|
|
|
|
// Send a message to a client
|
|
WebSocketServer.prototype.sendMessage = function(people, data, id) {
|
|
// Data type checks
|
|
if (typeof data == "string") {
|
|
var newData = [];
|
|
opcode = 129;
|
|
for (var i = 0; i < data.length; i++) {
|
|
newData.push(data.charCodeAt(i));
|
|
}
|
|
newData.push(opcode);
|
|
data = new Buffer(this.framer.packageDataCaller(newData));
|
|
}
|
|
else if (!Buffer.isBuffer(data)) {
|
|
console.log("You need to use the ws.packageMessage() first");
|
|
return;
|
|
}
|
|
|
|
// Send the data
|
|
if (people == "all") {
|
|
this.connect.sendToAll(data);
|
|
}
|
|
else if (people == "others") {
|
|
if (!id) {
|
|
console.log("The others parameter cannot be used if an ID is not specified");
|
|
}
|
|
this.connect.sendToOthers(data, id)
|
|
}
|
|
else if (people == "one") {
|
|
if (!id) {
|
|
console.log("The others parameter cannot be used if an ID is not specified");
|
|
}
|
|
this.connect.echoBack(data, id)
|
|
}
|
|
else {
|
|
console.log("Unknown Send Command: " + people);
|
|
}
|
|
}
|
|
|
|
// Unmask a message
|
|
WebSocketServer.prototype.unmaskMessage = function(message) {
|
|
if (!Buffer.isBuffer(message)) {
|
|
console.log("You must supply a valid WebSocket frame to unmask.");
|
|
return;
|
|
}
|
|
var unmasked = this.framer.unmaskMessageCaller(message);
|
|
var obj = { };
|
|
obj.opcode = unmasked.pop();
|
|
obj.message = unmasked;
|
|
return obj;
|
|
}
|
|
|
|
// Package a message
|
|
WebSocketServer.prototype.packageMessage = function(opcode, message) {
|
|
if (Array.isArray(message)) {
|
|
message.push(opcode);
|
|
return new Buffer(this.framer.packageDataCaller(message));
|
|
}
|
|
else {
|
|
console("Wrong data type fed to packageMessage");
|
|
}
|
|
}
|
|
|
|
|
|
// Close a connection
|
|
WebSocketServer.prototype.closeConnection = function(id) {
|
|
if (typeof id != "number") {
|
|
console.log("closeConnection needs a valid integer id");
|
|
return;
|
|
}
|
|
this.connect.closeConnection(id, 1000);
|
|
}
|
|
|
|
// Convert bytes to a string
|
|
WebSocketServer.prototype.convertToString = function(message) {
|
|
if (!Array.isArray(message)) {
|
|
console.log("You must enter an array of bytes to convert to a string");
|
|
return;
|
|
}
|
|
var str = "";
|
|
for (var i = 0; i < message.length; i++) {
|
|
str += String.fromCharCode(message[i]);
|
|
}
|
|
return str;
|
|
}
|
|
|
|
// Return the type of received message
|
|
WebSocketServer.prototype.messageType = function(opcode) {
|
|
// Check that the opcode is a valid whole number
|
|
if ((typeof opcode != 'number') || (opcode % 1 != 0)) {
|
|
console.log("An opcode must be an integer");
|
|
return "invalid";
|
|
}
|
|
if (opcode === 129) {
|
|
return "text";
|
|
}
|
|
else if (opcode === 130) {
|
|
return "binary";
|
|
}
|
|
else {
|
|
console.log("Cannot determine type of message");
|
|
return "invalid";
|
|
}
|
|
|
|
}
|
|
|
|
module.exports = WebSocketServer;
|
|
|
|
|
|
|