Initial commit
This commit is contained in:
commit
b7d90b9afe
16245 files changed
+3396666
No files matched your search
Generated
Vendored
Executable
+88
@@ -0,0 +1,88 @@
|
||||
WebSocketServer
|
||||
===============
|
||||
A WebSocket server implementation for Node.js, various features to still implement such as support for wss:// and performance analytics centre.
|
||||
|
||||
Installation
|
||||
============
|
||||
Install using npm - https://npmjs.org/package/websocketserver
|
||||
|
||||
<pre>npm install websocketserver</pre>
|
||||
|
||||
Basic Usage
|
||||
===========
|
||||
In a project require the websocketserver module and create an instance of it:
|
||||
|
||||
<pre>var WebSocketServer = require("websocketserver");
|
||||
var server = new WebSocketServer("all", 9000);</pre>
|
||||
|
||||
This will create a WebSocket server that runs on 9000 (default 8080) and uses a sendMethod that sends recieved connections to all server connections. Four sendMethods can be used:
|
||||
|
||||
1) `all` - will send received messages to all users
|
||||
|
||||
2) `others` - will send messages to all users apart from the sender
|
||||
|
||||
3) `echo` - will echo the message back to the sender
|
||||
|
||||
4) `none` - will not automatically send any messages (useful for when more control is needed over message sending, use API to send messages)
|
||||
|
||||
Events
|
||||
======
|
||||
The server emits events on a new connection, message and a closed connection:
|
||||
|
||||
1) `connection` event - use to built a list of connections
|
||||
|
||||
<pre>var connectionList = [];
|
||||
server.on("connection", function(id) {
|
||||
connectionList.push(id);
|
||||
});</pre>
|
||||
|
||||
2) `message` event - get hold of the message
|
||||
<pre>server.on("message", function(data, id) {
|
||||
var mes = server.unmaskMessage(data);
|
||||
var str = server.convertToString(mes.message);
|
||||
console.log(str);
|
||||
});</pre>
|
||||
|
||||
3) `connectionclosed` - log a connection has left (can also use id to remove from connectionList)
|
||||
<pre>server.on("closedconnection", function(id) {
|
||||
console.log("Connection " + id + " has left the server");
|
||||
});</pre>
|
||||
|
||||
Functions
|
||||
==========
|
||||
|
||||
1) `WebSocketServer.sendMessage(people, data, id);` - send a message to specified `people` (`all`, `others` or `one`), `data` supports strings and packaged WebSocket messages (use `packageMessage` function for packaging), `id` is relates to the connection that you would like (or not to if `others` has been selected for the `people` argument, `id` not required if all has been selected).
|
||||
|
||||
This example shows how new connections to the server can be welcomed with a message:
|
||||
|
||||
<pre>server.on('connection', function(id) {
|
||||
server.sendMessage("one", "Welcome to the server!", id);
|
||||
});</pre>
|
||||
|
||||
2) `WebSocketServer.unmaskMessage(frame);` - unmasks a WebSocket frame recieved by the server - returns a JavaScript objects with two properties: `opcode` and `message` (`message` is an array of bytes holding character codes - use `convertToString(message)` function to get the string representation)
|
||||
|
||||
Example shows how a message could be logged to the console:
|
||||
|
||||
<pre>server.on('message', function(data, id) {
|
||||
var mesObj = server.unmaskMessage(data);
|
||||
console.log(server.convertToString(mesObj.message));
|
||||
});</pre>
|
||||
|
||||
3) `WebSocketServer.convertToString(bytes);` - converts an array of bytes containing character codes to a string.
|
||||
|
||||
4) `WebSocketServer.packageMessage(opcode, message);` - packages a recieved WebSocket message into a sendable WebSocket message - this function is only really necessary after use of `unmaskMessage()` as it is only really necessary for data that needs to pass through the server (primarily binary messages)
|
||||
|
||||
Example shows how we may filter binary data to send straight through the server as most of the time we have no reason to log it.
|
||||
|
||||
<pre>server.on('message', function(data, id) {
|
||||
var mes = server.unmaskMessage(data);
|
||||
if (mes.opcode == 130) {
|
||||
var packagedMessage = server.packageMessage(mes.opcode, mes.message);
|
||||
server.sendMessage('all', packagedMessage);
|
||||
}
|
||||
});</pre>
|
||||
|
||||
5) `WebSocketServer.closeConnection(id);` close a specific connection
|
||||
|
||||
|
||||
|
||||
Generated
Vendored
Executable
+4
@@ -0,0 +1,4 @@
|
||||
// James Taylor - 466598
|
||||
// This file is used simply for installation purposes
|
||||
|
||||
module.exports = require("./modules/server.js");
|
||||
Generated
Vendored
Executable
+283
@@ -0,0 +1,283 @@
|
||||
// James Taylor - 466598
|
||||
// Connections Module - Used to log information of a connection
|
||||
|
||||
function socketConnection(socket, connectionNumber) {
|
||||
this.connectionNumber = connectionNumber;
|
||||
this.socket = socket;
|
||||
this.waitingForFrames = false;
|
||||
this.fullWaitLength = 0;
|
||||
this.waitArray = [];
|
||||
this.arrayIndex;
|
||||
this.fragmentOpcode;
|
||||
this.fragmentArray = [];
|
||||
}
|
||||
|
||||
socketConnection.prototype = {
|
||||
sendData: function(data) {
|
||||
this.socket.write(data);
|
||||
},
|
||||
returnID: function() {
|
||||
return this.connectionNumber;
|
||||
},
|
||||
closeSocket: function(closeFrame) {
|
||||
this.socket.write(closeFrame);
|
||||
this.socket.end();
|
||||
this.socket.destroy();
|
||||
},
|
||||
returnWaitingFrames: function() {
|
||||
return this.waitingForFrames;
|
||||
},
|
||||
changeWaitingFrames: function(state) {
|
||||
this.waitingForFrames = state;
|
||||
},
|
||||
setWaitArray: function(data) {
|
||||
this.waitArray.push(data);
|
||||
},
|
||||
resetWaitArray: function() {
|
||||
this.waitArray = [];
|
||||
},
|
||||
giveWaitArray: function() {
|
||||
var buf = Buffer.concat(this.waitArray)
|
||||
return buf;
|
||||
},
|
||||
setWaitLength: function(length) {
|
||||
this.fullWaitLength = length;
|
||||
},
|
||||
getWaitLength: function() {
|
||||
return this.fullWaitLength;
|
||||
},
|
||||
resetWaitLength: function() {
|
||||
this.fullWaitLength = 0;
|
||||
},
|
||||
setOpcode: function(opcode) {
|
||||
this.fragmentOpcode = opcode;
|
||||
},
|
||||
returnOpcode: function() {
|
||||
return this.fragmentOpcode;
|
||||
},
|
||||
setFragmentArray: function(data) {
|
||||
this.fragmentArray.push(data);
|
||||
},
|
||||
returnFragmentArray: function() {
|
||||
var buf = Buffer.concat(this.fragmentArray);
|
||||
return buf;
|
||||
},
|
||||
resetFragmentArray: function() {
|
||||
this.fragmentArray = [];
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
function serverConnections() {
|
||||
|
||||
var connectionNumber = 0;
|
||||
var connectionList = [];
|
||||
var sendTo = "";
|
||||
var self = this;
|
||||
|
||||
// Sends data to all users
|
||||
this.sendToAll = function(data) {
|
||||
var i = 0;
|
||||
while (i < connectionList.length) {
|
||||
connectionList[i].sendData(data);
|
||||
i++;
|
||||
}
|
||||
}
|
||||
|
||||
// Sends data to all users apart from the sender
|
||||
this.sendToOthers = function(data, id) {
|
||||
var i = 0;
|
||||
while (i < connectionList.length) {
|
||||
if (id != connectionList[i].returnID()) {
|
||||
connectionList[i].sendData(data);
|
||||
}
|
||||
i++;
|
||||
}
|
||||
}
|
||||
|
||||
// Echo test
|
||||
this.echoBack = function(data, id) {
|
||||
var i = 0;
|
||||
while (i < connectionList.length) {
|
||||
if (id == connectionList[i].returnID()) {
|
||||
connectionList[i].sendData(data);
|
||||
}
|
||||
i++;
|
||||
}
|
||||
}
|
||||
|
||||
this.addToConnectionList = function(connection) {
|
||||
connectionList.push(connection);
|
||||
}
|
||||
|
||||
this.closeConnection = function(connectionID, status) {
|
||||
connectionID = findConnection(connectionID);
|
||||
if (connectionID != -1) {
|
||||
var closeFrameArray = [];
|
||||
var statusBytes = statusCode(status);
|
||||
closeFrameArray[0] = 136;
|
||||
closeFrameArray[1] = 2;
|
||||
closeFrameArray[2] = statusBytes[0];
|
||||
closeFrameArray[3] = statusBytes[1];
|
||||
closeFrame = new Buffer(closeFrameArray);
|
||||
connectionList[connectionID].closeSocket(closeFrame);
|
||||
//connectionList.splice(connectionID, 1);
|
||||
}
|
||||
else {
|
||||
console.log("Error shutting connection.");
|
||||
}
|
||||
}
|
||||
|
||||
// Changes the state to true if the connection is waiting for more frames
|
||||
this.changeWaitState = function(connectionID, state) {
|
||||
connectionID = findConnection(connectionID);
|
||||
if (connectionID != -1) {
|
||||
connectionList[connectionID].changeWaitingFrames(state);
|
||||
}
|
||||
else {
|
||||
console.log("Error changing Connection state");
|
||||
}
|
||||
}
|
||||
|
||||
this.returnWaitState = function(connectionID) {
|
||||
var state;
|
||||
connectionID = findConnection(connectionID);
|
||||
if (connectionID != -1) {
|
||||
state = connectionList[connectionID].returnWaitingFrames();
|
||||
}
|
||||
else {
|
||||
state = -1;
|
||||
}
|
||||
return state;
|
||||
}
|
||||
|
||||
this.addWaitArray = function(data, connectionID) {
|
||||
connectionID = findConnection(connectionID);
|
||||
if (connectionID != -1) {
|
||||
connectionList[connectionID].setWaitArray(data);
|
||||
}
|
||||
else {
|
||||
console.log("Error setting waitArray");
|
||||
}
|
||||
}
|
||||
|
||||
this.clearWaitArray = function(connectionID) {
|
||||
connectionID = findConnection(connectionID);
|
||||
if (connectionID != -1) {
|
||||
connectionList[connectionID].resetWaitArray();
|
||||
}
|
||||
else {
|
||||
console.log("Error resetting waitArray");
|
||||
}
|
||||
}
|
||||
|
||||
this.returnWaitArray = function(connectionID) {
|
||||
var data = [];
|
||||
connectionID = findConnection(connectionID);
|
||||
if (connectionID != -1) {
|
||||
data = connectionList[connectionID].giveWaitArray();
|
||||
}
|
||||
return data;
|
||||
}
|
||||
|
||||
function findConnection(connectionID) {
|
||||
var i = 0;
|
||||
while (i < connectionList.length) {
|
||||
var lookup = connectionList[i].returnID();
|
||||
if (lookup == connectionID) {
|
||||
connectionID = i;
|
||||
}
|
||||
i++;
|
||||
}
|
||||
return connectionID;
|
||||
}
|
||||
|
||||
this.addFullWaitLength = function(connectionID, length) {
|
||||
connectionID = findConnection(connectionID);
|
||||
if (connectionID != -1) {
|
||||
connectionList[connectionID].setWaitLength(length);
|
||||
}
|
||||
}
|
||||
|
||||
this.returnFullWaitLength = function(connectionID) {
|
||||
connectionID = findConnection(connectionID);
|
||||
if (connectionID != -1) {
|
||||
return connectionList[connectionID].getWaitLength();
|
||||
}
|
||||
}
|
||||
|
||||
this.clearWaitLength = function(connectionID) {
|
||||
connectionID = findConnection(connectionID);
|
||||
if (connectionID != -1) {
|
||||
connectionList[connectionID].resetWaitLength();
|
||||
}
|
||||
}
|
||||
|
||||
// used to remember the fragment opcode
|
||||
this.fragmentOpcode = function(state, opcode, connectionID) {
|
||||
connectionID = findConnection(connectionID);
|
||||
if (connectionID != -1) {
|
||||
if (state == "set") {
|
||||
connectionList[connectionID].setOpcode(opcode);
|
||||
}
|
||||
else if (state == "get") {
|
||||
return connectionList[connectionID].returnOpcode();
|
||||
}
|
||||
else {
|
||||
console.log("INVALID STATE GIVEN!" + state);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
// Used to hold recived fragments
|
||||
this.fragmentArray = function(state, data, connectionID) {
|
||||
connectionID = findConnection(connectionID);
|
||||
if (connectionID != -1) {
|
||||
if (state == "set") {
|
||||
connectionList[connectionID].setFragmentArray(data);
|
||||
}
|
||||
else if (state == "get") {
|
||||
return connectionList[connectionID].returnFragmentArray();
|
||||
}
|
||||
else if (state == "reset") {
|
||||
connectionList[connectionID].resetFragmentArray();
|
||||
}
|
||||
else {
|
||||
console.log("INVALID STATE GIVEN: " + state);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
// For setting the send method
|
||||
this.setSend = function(sendMethod) {
|
||||
console.log("Setting server send method to: " + sendMethod);
|
||||
sendTo = sendMethod;
|
||||
}
|
||||
|
||||
// Handles which method to use to send data
|
||||
this.sendFrame = function(data, connectionID) {
|
||||
switch(sendTo) {
|
||||
case "all": self.sendToAll(data); break;
|
||||
case "others": self.sendToOthers(data, connectionID); break;
|
||||
case "echo": self.echoBack(data, connectionID); break;
|
||||
case "none": break;
|
||||
}
|
||||
}
|
||||
|
||||
// Returns a new id for a new connection
|
||||
this.getNewID = function() {
|
||||
connectionNumber++;
|
||||
return connectionNumber;
|
||||
}
|
||||
|
||||
// Returns the correct byte values for the status code
|
||||
function statusCode(status) {
|
||||
var first = status >> 8;
|
||||
var second = status & 255;
|
||||
|
||||
return [first,second];
|
||||
}
|
||||
}
|
||||
|
||||
exports.socketConnection = socketConnection;
|
||||
exports.serverConnections = serverConnections;
|
||||
Generated
Vendored
Executable
+549
@@ -0,0 +1,549 @@
|
||||
// James Taylor - 466598
|
||||
// Framing module - Decodes and packages data
|
||||
|
||||
function framer() {
|
||||
|
||||
var connections = require("./connections");
|
||||
// used for the server to emit events
|
||||
var emitter = "";
|
||||
var connection;
|
||||
|
||||
|
||||
// Ensures the full length of the frame has been recieved
|
||||
this.checkRecievedData = function(data, connectionID) {
|
||||
|
||||
// checks to see if more frames are expected
|
||||
if (connection.returnWaitState(connectionID) == true) {
|
||||
connection.addWaitArray(data, connectionID);
|
||||
var concatenatedArray = connection.returnWaitArray(connectionID);
|
||||
|
||||
// Set length of expected data
|
||||
if ((concatenatedArray[1] & 127) <= 125) {
|
||||
var setLength = (concatenatedArray[1] & 127) + 6;
|
||||
connection.addFullWaitLength(connectionID, setLength);
|
||||
}
|
||||
if ((concatenatedArray[1] & 127) == 126) {
|
||||
if (concatenatedArray.length > 4) {
|
||||
var setLength = returnLength(concatenatedArray);
|
||||
connection.addFullWaitLength(connectionID, setLength);
|
||||
}
|
||||
}
|
||||
if ((concatenatedArray[1] & 127) == 127) {
|
||||
if (concatenatedArray.length > 10) {
|
||||
var setLength = returnLength(concatenatedArray);
|
||||
connection.addFullWaitLength(connectionID, setLength);
|
||||
}
|
||||
|
||||
}
|
||||
if (concatenatedArray.length == connection.returnFullWaitLength(connectionID)) {
|
||||
// Reset wait variables
|
||||
connection.changeWaitState(connectionID, false);
|
||||
connection.clearWaitArray(connectionID);
|
||||
connection.clearWaitLength(connectionID);
|
||||
|
||||
// Pass on message for decoding
|
||||
decode(concatenatedArray, connectionID);
|
||||
}
|
||||
}
|
||||
else {
|
||||
if (data[0] == 129 || data[0] == 130 || data[0] == 136 || data[0] == 137 || data[0] == 138) {
|
||||
if (data[0] == 138) {
|
||||
function splitFrames(data) {
|
||||
var checkLength = returnLength(data);
|
||||
if (data.length > checkLength) {
|
||||
var newFragment = data.slice(0, checkLength);
|
||||
decode(newFragment, connectionID);
|
||||
var unhandledFragment = data.slice(checkLength);
|
||||
splitFrames(unhandledFragment);
|
||||
}
|
||||
else {
|
||||
checkFullFrameRecieved(data, connectionID);
|
||||
decode(data, connectionID);
|
||||
}
|
||||
}
|
||||
splitFrames(data);
|
||||
}
|
||||
else if (data[0] == 136) {
|
||||
decode(data, connectionID);
|
||||
return;
|
||||
}
|
||||
else {
|
||||
checkFullFrameRecieved(data,connectionID);
|
||||
}
|
||||
if (connection.returnWaitState(connectionID) == false) {
|
||||
decode(data, connectionID);
|
||||
}
|
||||
else {
|
||||
connection.addWaitArray(data, connectionID);
|
||||
}
|
||||
}
|
||||
else if (data[0] == 1 || data[0] == 2 || data[0] == 128 || data[0] == 0) { // fragmentation
|
||||
// if continuation frame is sent with no message to continue
|
||||
if (data[0] == 0 && connection.fragmentArray("get", data, connectionID).length == 0) {
|
||||
connection.closeConnection(connectionID, 1002);
|
||||
}
|
||||
// if continuation frame is sent with its FIN set but it has no message to continue
|
||||
else if (data[0] == 128 && connection.fragmentArray("get", data, connectionID).length == 0) {
|
||||
connection.closeConnection(connectionID, 1002);
|
||||
}
|
||||
else if (data.length < 6) {
|
||||
connection.changeWaitState(connectionID, true);
|
||||
connection.addWaitArray(data, connectionID);
|
||||
}
|
||||
else {
|
||||
function checkFragments(data) {
|
||||
//check frame is fragmented, if not divide it
|
||||
var checkLength = returnLength(data);
|
||||
if (data.length > checkLength) {
|
||||
var newFragment = data.slice(0, checkLength);
|
||||
handleFragmentation(newFragment, connectionID);
|
||||
var unhandledFragment = data.slice(checkLength);
|
||||
checkFragments(unhandledFragment);
|
||||
}
|
||||
else {
|
||||
handleFragmentation(data, connectionID);
|
||||
}
|
||||
}
|
||||
checkFragments(data);
|
||||
}
|
||||
}
|
||||
else {
|
||||
connection.closeConnection(connectionID, 1002);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
// Handles fragmentation cases
|
||||
function handleFragmentation(data, connectionID) {
|
||||
var opcode = data[0];
|
||||
|
||||
// Pings may be sent in the middle of fragments
|
||||
if (opcode == 137) {
|
||||
decode(data, connectionID);
|
||||
return;
|
||||
}
|
||||
else if (opcode == 136) {
|
||||
decode(data, connectionID);
|
||||
return;
|
||||
}
|
||||
if (opcode == 0 && connection.fragmentArray("get", data, connectionID).length == 0) {
|
||||
connection.closeConnection(connectionID, 1002);
|
||||
}
|
||||
else if ((opcode == 1 || opcode == 2 || opcode == 129) && (connection.fragmentArray("get", data, connectionID).length != 0)) {
|
||||
connection.closeConnection(connectionID, 1002);
|
||||
}
|
||||
|
||||
// remember the opcode for sending later
|
||||
if (opcode == 1) {
|
||||
connection.fragmentOpcode("set", 129, connectionID);
|
||||
}
|
||||
else if (opcode == 2) {
|
||||
connection.fragmentOpcode("set", 130, connectionID);
|
||||
}
|
||||
// unmask the message
|
||||
var maskLocation = 2;
|
||||
var dataLength = data[1] & 127;
|
||||
if (dataLength == 126) {
|
||||
maskLocation = 4;
|
||||
}
|
||||
else if (dataLength == 127) {
|
||||
maskLocation = 10;
|
||||
}
|
||||
var unmaskedData = [];
|
||||
var maskingKey = data.slice(maskLocation, maskLocation + 4);
|
||||
var dataLocation = maskLocation + 4;
|
||||
for (var i = 0; dataLocation < data.length; i++) {
|
||||
var newData = data[dataLocation] ^ maskingKey[i % 4];
|
||||
unmaskedData.push(newData);
|
||||
dataLocation++;
|
||||
}
|
||||
|
||||
|
||||
var buf = new Buffer(unmaskedData);
|
||||
connection.fragmentArray("set", buf, connectionID);
|
||||
|
||||
// Message with FIN set to 1, package fragments for sending
|
||||
if (opcode == 128) {
|
||||
var dataBuffer = connection.fragmentArray("get", buf, connectionID);
|
||||
connection.fragmentArray("reset", buf, connectionID);
|
||||
opcode = connection.fragmentOpcode("get", 0, connectionID);
|
||||
|
||||
// Construct the first bytes of the message
|
||||
var newData = [];
|
||||
newData[0] = opcode;
|
||||
setPayloadLength(newData, dataBuffer.length);
|
||||
|
||||
var buf = new Buffer(newData);
|
||||
var bufList = [];
|
||||
bufList[0] = buf;
|
||||
bufList[1] = dataBuffer;
|
||||
var packagedMessage = Buffer.concat(bufList);
|
||||
connection.sendFrame(packagedMessage, connectionID);
|
||||
}
|
||||
}
|
||||
|
||||
// Used to correctly handle the data recieved from the client
|
||||
function decode(data, connectionID) {
|
||||
|
||||
// The type of data (Opcode)
|
||||
var opcode = data[0];
|
||||
// The length of the data must be greater than 5 to be a valid frame
|
||||
if (data.length <= 5) {
|
||||
connection.closeConnection(connectionID, 1008);
|
||||
console.log("Frame Length too short!");
|
||||
return;
|
||||
}
|
||||
|
||||
// make the initial check on the RSV numbers
|
||||
if (invalidRSV(opcode)) {
|
||||
connection.closeConnection(connectionID, 1002);
|
||||
}
|
||||
|
||||
if (opcode == 129) { // text message
|
||||
emitter.emit("message", data, connectionID);
|
||||
var decodedData = unmaskMessage(data);
|
||||
var packagedData = packageData(decodedData);
|
||||
var encodedBuffer = new Buffer(packagedData);
|
||||
connection.sendFrame(encodedBuffer, connectionID);
|
||||
}
|
||||
else if (opcode == 1 || opcode == 2 || opcode == 0 || opcode == 128) { // fragmented messages
|
||||
handleFragmentation(data, connectionID);
|
||||
}
|
||||
else if (opcode == 130) { // binary frame
|
||||
emitter.emit("message", data, connectionID);
|
||||
var decodedData = unmaskMessage(data);
|
||||
var packagedData = packageData(decodedData);
|
||||
var encodedBuffer = new Buffer(packagedData);
|
||||
connection.sendFrame(encodedBuffer, connectionID);
|
||||
}
|
||||
else if (opcode == 136) { // close frame
|
||||
// Check for valid close code and close the connection
|
||||
validateCloseFrame(connectionID, data);
|
||||
}
|
||||
else if (opcode == 137) { // ping frame
|
||||
if (data.length <= 131) {
|
||||
var decodedData = unmaskMessage(data);
|
||||
var packagedData = constructPongFrame(decodedData);
|
||||
var encodedBuffer = new Buffer(packagedData);
|
||||
connection.sendFrame(encodedBuffer, connectionID);
|
||||
}
|
||||
else {
|
||||
connection.closeConnection(connectionID, 1002);
|
||||
}
|
||||
}
|
||||
else if (opcode == 138) { // pong frame
|
||||
console.log("Pong recieved");
|
||||
}
|
||||
else {
|
||||
connection.closeConnection(connectionID, 1002);
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
|
||||
// Used to package the data in the correct framing format for WebSockets
|
||||
function packageData(data) {
|
||||
// extract the opcode from the data array then remove it from the array
|
||||
var opcode = data.pop();
|
||||
var newData = [];
|
||||
newData[0] = opcode;
|
||||
|
||||
var payloadLength = data.length;
|
||||
|
||||
if (payloadLength <= 125) {
|
||||
var dataLocation = 2;
|
||||
newData[1] = payloadLength;
|
||||
}
|
||||
else if (payloadLength >= 126 && payloadLength <= 65535) {
|
||||
var dataLocation = 4;
|
||||
newData[1] = 126;
|
||||
newData[2] = payloadLength >>> 8;
|
||||
newData[3] = payloadLength & 255;
|
||||
}
|
||||
else if (payloadLength >= 65536) {
|
||||
var dataLocation = 10;
|
||||
newData[1] = 127;
|
||||
|
||||
// The first 32 bits
|
||||
newData[9] = (payloadLength) & 255;
|
||||
newData[8] = (payloadLength >> 8) & 255;
|
||||
newData[7] = (payloadLength >> 16) & 255;
|
||||
newData[6] = (payloadLength >> 24) & 255;
|
||||
|
||||
// if the number is greater than 32bit
|
||||
if (payloadLength >= 4294967296) {
|
||||
// Get the higher 64 bit
|
||||
var sixtyFourBit = payloadLength.toString(2);
|
||||
var thirtyTwoBit = sixtyFourBit.slice(0, (sixtyFourBit.length - 32));
|
||||
var secondThirtyTwoBits = parseInt(thirtyTwoBit, 2);
|
||||
|
||||
newData[5] = (secondThirtyTwoBits) & 255;
|
||||
newData[4] = (secondThirtyTwoBits >> 8) & 255;
|
||||
newData[3] = (secondThirtyTwoBits >> 16) & 255;
|
||||
newData[2] = (secondThirtyTwoBits >> 24) & 255;
|
||||
}
|
||||
else { // if number is less than 32bit
|
||||
newData[5] = 0;
|
||||
newData[4] = 0;
|
||||
newData[3] = 0;
|
||||
newData[2] = 0;
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
for (var i = 0; i < payloadLength; i++) {
|
||||
newData[dataLocation] = data[i];
|
||||
dataLocation++;
|
||||
}
|
||||
return newData;
|
||||
|
||||
}
|
||||
|
||||
// unmasks a message
|
||||
function unmaskMessage(data) {
|
||||
// The payload length
|
||||
var payloadLength = data[1];
|
||||
|
||||
// The location of the masking key
|
||||
// Defualt 2
|
||||
var locationOfMask = 2;
|
||||
|
||||
// Find the length of the message being sent, remove the masking key using & 127
|
||||
var payloadLength = payloadLength & 127;
|
||||
if (payloadLength == 126) {
|
||||
// If the length of the payload is 126, 2 extra bytes represent the length
|
||||
locationOfMask = 4;
|
||||
}
|
||||
else if (payloadLength == 127) {
|
||||
// If length of the payload is 127, 8 extra bytes represent the length
|
||||
locationOfMask = 10;
|
||||
}
|
||||
|
||||
// Mask is 4 bytes long
|
||||
var maskingKey = data.slice(locationOfMask, locationOfMask + 4);
|
||||
|
||||
var dataLocation = locationOfMask + 4;
|
||||
|
||||
// new array to hold decodedData
|
||||
var decodedData = [];
|
||||
|
||||
var logString = "";
|
||||
for (var i = 0; dataLocation < data.length; i++) {
|
||||
var newData = data[dataLocation] ^ maskingKey[i % 4];
|
||||
decodedData.push(newData);
|
||||
dataLocation++;
|
||||
}
|
||||
|
||||
decodedData.push(data[0]);
|
||||
|
||||
return decodedData;
|
||||
|
||||
}
|
||||
|
||||
// Used to set the correct payload length for packaging data
|
||||
function setPayloadLength(newData, payloadLength) {
|
||||
if (payloadLength <= 125) {
|
||||
var dataLocation = 2;
|
||||
newData[1] = payloadLength;
|
||||
}
|
||||
else if (payloadLength >= 126 && payloadLength <= 65535) {
|
||||
var dataLocation = 4;
|
||||
newData[1] = 126;
|
||||
newData[2] = payloadLength >>> 8;
|
||||
newData[3] = payloadLength & 255;
|
||||
}
|
||||
else if (payloadLength >= 65536) {
|
||||
var dataLocation = 10;
|
||||
newData[1] = 127;
|
||||
|
||||
// The first 32 bits
|
||||
newData[9] = (payloadLength) & 255;
|
||||
newData[8] = (payloadLength >> 8) & 255;
|
||||
newData[7] = (payloadLength >> 16) & 255;
|
||||
newData[6] = (payloadLength >> 24) & 255;
|
||||
|
||||
// if the number is greater than 32bit
|
||||
if (payloadLength >= 4294967296) {
|
||||
var sixtyFourBit = payloadLength.toString(2);
|
||||
var thirtyTwoBit = sixtyFourBit.slice(0, (sixtyFourBit.length - 32));
|
||||
var secondThirtyTwoBits = parseInt(thirtyTwoBit, 2);
|
||||
newData[5] = (secondThirtyTwoBits) & 255;
|
||||
newData[4] = (secondThirtyTwoBits >> 8) & 255;
|
||||
newData[3] = (secondThirtyTwoBits >> 16) & 255;
|
||||
newData[2] = (secondThirtyTwoBits >> 24) & 255;
|
||||
}
|
||||
else { // if number is less than 32bit
|
||||
newData[5] = 0;
|
||||
newData[4] = 0;
|
||||
newData[3] = 0;
|
||||
newData[2] = 0;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
// Creates a Pong frame
|
||||
function constructPongFrame(data) {
|
||||
var pingOpcode = data.pop();
|
||||
var newData = [];
|
||||
newData[0] = 138;
|
||||
newData[1] = data.length;
|
||||
var dataLocation = 2;
|
||||
|
||||
var i = 0;
|
||||
while (i < data.length) {
|
||||
newData[dataLocation] = data[i];
|
||||
dataLocation++;
|
||||
i++;
|
||||
}
|
||||
return newData;
|
||||
}
|
||||
|
||||
// Used to check the full frame has been recieved
|
||||
function checkFullFrameRecieved(data, connectionID) {
|
||||
var payloadLength = data[1] & 127; //remove mask bit
|
||||
if (payloadLength <= 125) {
|
||||
if ((data.length - 6) == payloadLength) {
|
||||
//console.log("Correct Frame Size");
|
||||
}
|
||||
else {
|
||||
connection.changeWaitState(connectionID, true);
|
||||
}
|
||||
}
|
||||
else if (payloadLength == 126) {
|
||||
var dataRecieved = data.slice(8, (data.length));
|
||||
var payOne = data[2] << 8;
|
||||
var payTwo = data[3];
|
||||
var dataLength = payOne + payTwo;
|
||||
if (dataLength == dataRecieved.length) {
|
||||
//console.log("Correct Frame Size");
|
||||
}
|
||||
else {
|
||||
connection.changeWaitState(connectionID, true);
|
||||
}
|
||||
}
|
||||
else if (payloadLength == 127) {
|
||||
var dataRecieved = data.slice(14, (data.length));
|
||||
var dataLength = "";
|
||||
for (var i = 2; i < 10; i++) {
|
||||
if (data[i] == 0) {
|
||||
var str ="00000000";
|
||||
}
|
||||
else {
|
||||
var newByte = data[i];
|
||||
var str = newByte.toString(2);
|
||||
}
|
||||
dataLength += str;
|
||||
}
|
||||
dataLength = parseInt(dataLength, 2);
|
||||
if (dataLength == dataRecieved.length) {
|
||||
//console.log("Frame Recieved");
|
||||
}
|
||||
else {
|
||||
connection.changeWaitState(connectionID, true);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
// Returns the expected length of the data as specified by the Payload Length byte (2nd byte);
|
||||
function returnLength(data) {
|
||||
if ((data[1] & 127) <= 125) {
|
||||
return (data[1] & 127) + 6;
|
||||
}
|
||||
else if ((data[1] & 127) == 126) {
|
||||
var dataLength = (data[2] << 8) + data[3];
|
||||
return dataLength + 8;
|
||||
}
|
||||
else if ((data[1] & 127) == 127) {
|
||||
var dataLength = "";
|
||||
for (var i = 2; i < 10; i++) {
|
||||
if (data[i] == 0) {
|
||||
var str ="00000000";
|
||||
}
|
||||
else {
|
||||
var newByte = data[i];
|
||||
var str = newByte.toString(2);
|
||||
}
|
||||
dataLength += str;
|
||||
}
|
||||
dataLength = parseInt(dataLength, 2);
|
||||
return dataLength + 14;
|
||||
}
|
||||
}
|
||||
|
||||
// Checks that RSV1, RSV2 and RSV3 are all set to 0
|
||||
function invalidRSV(firstByte) {
|
||||
var check = firstByte;
|
||||
// remove opcode and FIN;
|
||||
check = check >> 4;
|
||||
check = check & 7;
|
||||
|
||||
if (check == 0) {
|
||||
return false;
|
||||
}
|
||||
else {
|
||||
return true;
|
||||
}
|
||||
}
|
||||
|
||||
// Validates a close frame
|
||||
function validateCloseFrame(connectionID, data) {
|
||||
if (data.length == 6) { // No close code
|
||||
connection.closeConnection(connectionID, 1000);
|
||||
return;
|
||||
}
|
||||
else if (data.length == 7) { // Only half close code
|
||||
connection.closeConnection(connectionID, 1002);
|
||||
return;
|
||||
}
|
||||
// CLOSE CODE CHECKING
|
||||
var mask = data.slice(2, 6);
|
||||
var maskedCode = data.slice(6, 8);
|
||||
var unmaskedCode = [];
|
||||
for (var i = 0; i < maskedCode.length; i++) {
|
||||
unmaskedCode[i] = maskedCode[i] ^ mask[i % 4];
|
||||
}
|
||||
unmaskedCode[0] = unmaskedCode[0] << 8;
|
||||
var statusCode = unmaskedCode[0] + unmaskedCode[1];
|
||||
|
||||
if (statusCode <= 999 || statusCode >= 5000) {
|
||||
connection.closeConnection(connectionID, 1002);
|
||||
}
|
||||
else if (statusCode >= 3000 && statusCode <= 4999) {
|
||||
connection.closeConnection(connectionID, 1000);
|
||||
}
|
||||
else {
|
||||
switch(statusCode) {
|
||||
case 1000: connection.closeConnection(connectionID, 1000); break;
|
||||
case 1001: connection.closeConnection(connectionID, 1000); break;
|
||||
case 1002: connection.closeConnection(connectionID, 1000); break;
|
||||
case 1003: connection.closeConnection(connectionID, 1000); break;
|
||||
case 1007: connection.closeConnection(connectionID, 1000); break;
|
||||
case 1008: connection.closeConnection(connectionID, 1000); break;
|
||||
case 1009: connection.closeConnection(connectionID, 1000); break;
|
||||
case 1010: connection.closeConnection(connectionID, 1000); break;
|
||||
case 1011: connection.closeConnection(connectionID, 1000); break;
|
||||
default: connection.closeConnection(connectionID, 1002);
|
||||
}
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
this.setEmitter = function(object) {
|
||||
emitter = object;
|
||||
}
|
||||
|
||||
this.setConnectionList = function(list) {
|
||||
connection = list;
|
||||
}
|
||||
|
||||
this.unmaskMessageCaller = function(message) {
|
||||
return unmaskMessage(message);
|
||||
}
|
||||
|
||||
this.packageDataCaller = function(data) {
|
||||
return packageData(data);
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
module.exports = framer;
|
||||
Generated
Vendored
Executable
+210
@@ -0,0 +1,210 @@
|
||||
// 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;
|
||||
|
||||
|
||||
|
||||
Generated
Vendored
Executable
+47
@@ -0,0 +1,47 @@
|
||||
{
|
||||
"_from": "websocketserver",
|
||||
"_id": "websocketserver@1.0.0",
|
||||
"_inBundle": false,
|
||||
"_integrity": "sha1-ssuE4ndFlwqstCsXYM+LmuUdzoE=",
|
||||
"_location": "/websocketserver",
|
||||
"_phantomChildren": {},
|
||||
"_requested": {
|
||||
"type": "tag",
|
||||
"registry": true,
|
||||
"raw": "websocketserver",
|
||||
"name": "websocketserver",
|
||||
"escapedName": "websocketserver",
|
||||
"rawSpec": "",
|
||||
"saveSpec": null,
|
||||
"fetchSpec": "latest"
|
||||
},
|
||||
"_requiredBy": [
|
||||
"#USER",
|
||||
"/"
|
||||
],
|
||||
"_resolved": "https://registry.npmjs.org/websocketserver/-/websocketserver-1.0.0.tgz",
|
||||
"_shasum": "b2cb84e27745970aacb42b1760cf8b9ae51dce81",
|
||||
"_spec": "websocketserver",
|
||||
"_where": "D:\\Abteilungskram\\NLY\\WSS\\SocketMap",
|
||||
"author": {
|
||||
"name": "James Taylor",
|
||||
"email": "james.taylor5@myport.ac.uk"
|
||||
},
|
||||
"bin": {
|
||||
"WebSocketServer": "./index.js"
|
||||
},
|
||||
"bugs": {
|
||||
"url": "https://github.com/jamest222/WebSocketServer/issues"
|
||||
},
|
||||
"bundleDependencies": false,
|
||||
"deprecated": false,
|
||||
"description": "A WebSocket Server implementation for Node.js",
|
||||
"homepage": "https://github.com/jamest222/WebSocketServer#readme",
|
||||
"main": "./index.js",
|
||||
"name": "websocketserver",
|
||||
"repository": {
|
||||
"type": "git",
|
||||
"url": "git+https://github.com/jamest222/WebSocketServer.git"
|
||||
},
|
||||
"version": "1.0.0"
|
||||
}
|
||||
Reference in new issue
Block a user