revert websocket lib version
- prepare session for profiler
This commit is contained in:
Родитель
23812b3855
Коммит
1e2b66fc70
|
@ -13,6 +13,8 @@ WebInspector.loaded = function() {
|
|||
|
||||
// enable LiveEdit
|
||||
Preferences.canEditScriptSource = true;
|
||||
// enable heap profiler
|
||||
Preferences.heapProfilerPresent = true;
|
||||
|
||||
// patch new watch expression (default crashes node)
|
||||
WebInspector.WatchExpressionsSection.NewWatchExpression = "''";
|
||||
|
|
|
@ -1,7 +1,7 @@
|
|||
var Http = require('http'),
|
||||
EventEmitter = require('events').EventEmitter,
|
||||
path = require('path'),
|
||||
WebSocket = require('../vendor/websockets/ws/server'),
|
||||
WebSocket = require('../vendor/ws'),
|
||||
paperboy = require('../vendor/paperboy'),
|
||||
Session = require('./session');
|
||||
|
||||
|
|
|
@ -15,7 +15,9 @@ exports.create = function (conn, debuggerPort, config) {
|
|||
//milliseconds to wait for a lookup
|
||||
LOOKUP_TIMEOUT = 2500,
|
||||
//node function wrapper
|
||||
FUNC_WRAP = /^\(function \(exports, require, module, __filename, __dirname\) \{ ([\s\S]*)\n\}\);$/;
|
||||
FUNC_WRAP = /^\(function \(exports, require, module, __filename, __dirname\) \{ ([\s\S]*)\n\}\);$/,
|
||||
//store of the last profile taken
|
||||
lastProfile;
|
||||
|
||||
function wrapperObject(type, description, hasChildren, frame, scope, ref) {
|
||||
return {
|
||||
|
@ -109,7 +111,7 @@ exports.create = function (conn, debuggerPort, config) {
|
|||
}
|
||||
|
||||
function evaluate(expr, frame, andThen) {
|
||||
var args = { expression: expr, disable_break: true, global: true };
|
||||
var args = { expression: expr, disable_break: true, global: true, maxStringLength:100000 };
|
||||
if (frame != null) {
|
||||
args.frame = frame;
|
||||
args.global = false;
|
||||
|
@ -461,7 +463,8 @@ exports.create = function (conn, debuggerPort, config) {
|
|||
},
|
||||
enableProfiler: {
|
||||
value: function(always) {
|
||||
|
||||
// TODO: evaluate if profiling is enabled
|
||||
sendEvent('profilerWasEnabled');
|
||||
}
|
||||
},
|
||||
disableProfiler: {
|
||||
|
@ -707,8 +710,17 @@ exports.create = function (conn, debuggerPort, config) {
|
|||
}
|
||||
},
|
||||
getProfile: {
|
||||
value: function(type, uid) {
|
||||
|
||||
value: function(type, uid, seq) {
|
||||
evaluate('process.profiler.findSnapshot(' + uid + ').stringify()', null, function(msg) {
|
||||
sendResponse(seq, true, {
|
||||
profile: {
|
||||
title: "org.webkit.profiles.user-initiated." + lastProfile.uid,
|
||||
uid: parseInt(lastProfile.uid, 10),
|
||||
typeId: "HEAP",
|
||||
head: JSON.parse(msg.body.value)
|
||||
}
|
||||
});
|
||||
})
|
||||
}
|
||||
},
|
||||
removeProfile: {
|
||||
|
@ -723,7 +735,28 @@ exports.create = function (conn, debuggerPort, config) {
|
|||
},
|
||||
takeHeapSnapshot: {
|
||||
value: function() {
|
||||
|
||||
evaluate('process.profiler.takeSnapshot()', null, function(msg){
|
||||
if (msg.success) {
|
||||
var refs = {};
|
||||
lastProfile = {};
|
||||
if (msg.refs && Array.isArray(msg.refs)) {
|
||||
var obj = msg.body;
|
||||
var objProps = obj.properties;
|
||||
msg.refs.forEach(function(r) {
|
||||
refs[r.handle] = r;
|
||||
});
|
||||
objProps.forEach(function(p) {
|
||||
lastProfile[String(p.name)] = refToObject(refs[p.ref]).description;
|
||||
});
|
||||
}
|
||||
sendEvent('addProfileHeader', {
|
||||
header: {
|
||||
title: "org.webkit.profiles.user-initiated." + lastProfile.uid,
|
||||
uid: parseInt(lastProfile.uid, 10),
|
||||
typeId: "HEAP"
|
||||
}});
|
||||
}
|
||||
});
|
||||
}
|
||||
},
|
||||
|
||||
|
|
|
@ -1,30 +0,0 @@
|
|||
var util = require("./_util")
|
||||
, events = require("events")
|
||||
|
||||
EventEmitter = exports.EventEmitter = function(){
|
||||
events.EventEmitter.call(this);
|
||||
};
|
||||
|
||||
util.inherits(EventEmitter, events.EventEmitter);
|
||||
|
||||
EventEmitter.prototype.emit = function(type) {
|
||||
if (type !== "newListener"
|
||||
&& (!this._events || !this._events[type])
|
||||
&& this._bubbleTarget && this._bubbleTarget[type]
|
||||
) {
|
||||
// util.error("\033[31mEvent: "+type+", source: "+this.constructor.name+", target: "+this._bubbleTarget[type].constructor.name+"\033[39m");
|
||||
this._bubbleTarget[type].emit.apply(this._bubbleTarget[type], arguments);
|
||||
} else {
|
||||
// util.error("\033[31mEvent: "+type+", source: "+this.constructor.name+"\033[39m");
|
||||
events.EventEmitter.prototype.emit.apply(this, arguments);
|
||||
}
|
||||
};
|
||||
|
||||
EventEmitter.prototype.bubbleEvent = function(type, target){
|
||||
if(!this._bubbleTarget) this._bubbleTarget = {};
|
||||
this._bubbleTarget[type] = target;
|
||||
};
|
||||
|
||||
EventEmitter.prototype.removeBubbleEvent = function(type) {
|
||||
delete this._bubbleTarget[type];
|
||||
};
|
|
@ -1 +0,0 @@
|
|||
module.exports = require(process.binding("natives").util ? "util" : "sys")
|
|
@ -1,13 +0,0 @@
|
|||
|
||||
module.exports = function Mixin(target, source) {
|
||||
if (source) {
|
||||
for(var key, keys = Object.keys(source), l = keys.length; l--; ) {
|
||||
key = keys[l];
|
||||
|
||||
if(source.hasOwnProperty(key)){
|
||||
target[key] = source[key];
|
||||
}
|
||||
}
|
||||
}
|
||||
return target;
|
||||
};
|
|
@ -1,153 +0,0 @@
|
|||
var debug = function(){};
|
||||
|
||||
var util = require("../_util")
|
||||
, events = require("../_events");
|
||||
|
||||
/*-----------------------------------------------
|
||||
Connection Manager
|
||||
-----------------------------------------------*/
|
||||
module.exports = Manager;
|
||||
|
||||
function Manager(options) {
|
||||
events.EventEmitter.call(this);
|
||||
|
||||
if(options.debug) {
|
||||
var self = this;
|
||||
debug = function(msg, connection) {
|
||||
if(connection && connection.id) {
|
||||
util.error('\033[31mManager: ' +msg+ ": <Connection "+connection.id+"> ("+self._length+")\033[39m");
|
||||
} else {
|
||||
util.error('\033[31mManager: ' +Array.prototype.join.call(arguments, " ")+" ("+self._length+")\033[39m");
|
||||
}
|
||||
};
|
||||
}
|
||||
|
||||
this._head = null;
|
||||
this._tail = null;
|
||||
this._length = 0;
|
||||
this._counter = 0;
|
||||
};
|
||||
|
||||
util.inherits(Manager, events.EventEmitter);
|
||||
|
||||
Object.defineProperty(Manager.prototype, "length", {
|
||||
get: function() {
|
||||
return this._length;
|
||||
}
|
||||
});
|
||||
|
||||
Manager.prototype.createId = function(remotePort) {
|
||||
return process.pid + "" + remotePort + "" + (this._counter++);
|
||||
};
|
||||
|
||||
Manager.prototype.inspect = function() {
|
||||
return "<WS:Manager "+this._length+" (total: "+this._counter+")>";
|
||||
};
|
||||
|
||||
Manager.prototype.attach = function(connection) {
|
||||
var client = {
|
||||
id: connection.id,
|
||||
_next: null,
|
||||
connection: connection
|
||||
};
|
||||
|
||||
if(this._length == 0) {
|
||||
this._head = client;
|
||||
this._tail = client;
|
||||
} else {
|
||||
this._tail._next = client;
|
||||
this._tail = client;
|
||||
}
|
||||
|
||||
++this._length;
|
||||
|
||||
this.emit("attach", connection);
|
||||
debug("Attached", connection);
|
||||
};
|
||||
|
||||
Manager.prototype.detach = function(connection) {
|
||||
var previous = current = this._head
|
||||
, id = connection.id;
|
||||
|
||||
while(current !== null) {
|
||||
if(current.id === id) {
|
||||
previous._next = current._next;
|
||||
|
||||
if(current.id === this._head.id) {
|
||||
this._head = current._next;
|
||||
}
|
||||
|
||||
if(current.id === this._tail.id) {
|
||||
this._tail = previous;
|
||||
}
|
||||
|
||||
this._length--;
|
||||
this.emit("detach", connection);
|
||||
|
||||
debug("Detached", connection);
|
||||
break;
|
||||
} else {
|
||||
previous = current;
|
||||
current = current._next;
|
||||
}
|
||||
}
|
||||
|
||||
if(current === null) {
|
||||
debug("Detach Failed", connection);
|
||||
}
|
||||
|
||||
delete current, previous;
|
||||
};
|
||||
|
||||
Manager.prototype.find = function(id, callback, thisArg) {
|
||||
var current = this._head;
|
||||
|
||||
while(current !== null) {
|
||||
if(current.id === id) {
|
||||
callback.call(thisArg, current.connection);
|
||||
break;
|
||||
} else {
|
||||
current = current._next;
|
||||
}
|
||||
}
|
||||
};
|
||||
|
||||
Manager.prototype.forEach = function(callback, thisArg){
|
||||
var current = this._head;
|
||||
|
||||
while(current !== null) {
|
||||
callback.call(thisArg, current.connection);
|
||||
current = current._next;
|
||||
}
|
||||
};
|
||||
|
||||
Manager.prototype.map = function(callback, thisArg){
|
||||
var current = this._head
|
||||
, result = []
|
||||
, len = 0;
|
||||
|
||||
while(current !== null) {
|
||||
result.push(callback.call(thisArg, current.connection, len, this._head));
|
||||
current = current._next;
|
||||
len++;
|
||||
}
|
||||
|
||||
return result;
|
||||
};
|
||||
|
||||
Manager.prototype.filter = function(callback, thisArg){
|
||||
var current = this._head
|
||||
, result = []
|
||||
, len = 0;
|
||||
|
||||
while(current !== null) {
|
||||
if( Boolean(callback.call(thisArg, current.connection, len, this._head)) ) {
|
||||
result.push(current.connection);
|
||||
}
|
||||
|
||||
current = current._next;
|
||||
len++;
|
||||
}
|
||||
|
||||
return result;
|
||||
};
|
|
@ -1,102 +0,0 @@
|
|||
/*-----------------------------------------------
|
||||
Requirements:
|
||||
-----------------------------------------------*/
|
||||
var http = require("http")
|
||||
, path = require("path");
|
||||
|
||||
var util = require("../_util")
|
||||
, events = require("../_events");
|
||||
|
||||
var Manager = require("./manager")
|
||||
, Connection = require("./connection")
|
||||
, Mixin = require("../lang/mixin");
|
||||
|
||||
/*-----------------------------------------------
|
||||
Reflectors:
|
||||
-----------------------------------------------*/
|
||||
function reflectEvent(sEmitter, sType, tEmitter, tType) {
|
||||
sEmitter.addListener(sType, function(){
|
||||
tEmitter.emit.apply(tEmitter, [tType || sType].concat(Array.prototype.slice.call(arguments)));
|
||||
});
|
||||
};
|
||||
|
||||
function reflectMethod(sObject, sMeth, tObject, tMeth) {
|
||||
tObject[tMeth || sMeth] = function(){
|
||||
return sObject[sMeth].apply(sObject, arguments);
|
||||
};
|
||||
};
|
||||
|
||||
function clientWrite(client, data) {
|
||||
if(client && client._state === 4){
|
||||
client.write(data);
|
||||
}
|
||||
};
|
||||
|
||||
/*-----------------------------------------------
|
||||
WebSocket Server Exports:
|
||||
-----------------------------------------------*/
|
||||
exports.Server = Server;
|
||||
exports.createServer = function(options){
|
||||
return new Server(options);
|
||||
};
|
||||
|
||||
/*-----------------------------------------------
|
||||
WebSocket Server Implementation:
|
||||
-----------------------------------------------*/
|
||||
function Server(o){
|
||||
events.EventEmitter.call(this);
|
||||
|
||||
var options = Mixin({
|
||||
debug: false,
|
||||
server: new http.Server()
|
||||
}, o);
|
||||
|
||||
var manager = new Manager(options)
|
||||
, server = options.server;
|
||||
|
||||
if(options.datastore){
|
||||
throw new Error("Built-in DataStore has been removed, see: http://github.com/miksago/nws-memstore");
|
||||
}
|
||||
|
||||
server.on("upgrade", function(req, socket, upgradeHead){
|
||||
if( req.method == "GET" && ( req.headers["upgrade"] && req.headers["connection"] ) &&
|
||||
req.headers.upgrade.toLowerCase() == "websocket" && req.headers.connection.toLowerCase() == "upgrade"
|
||||
){
|
||||
new Connection(manager, options, req, socket, upgradeHead);
|
||||
}
|
||||
});
|
||||
|
||||
manager.bubbleEvent("error", this);
|
||||
|
||||
reflectEvent( manager, "attach", this, "connection");
|
||||
reflectEvent( manager, "detach", this, "disconnect");
|
||||
|
||||
reflectEvent( server, "listening", this);
|
||||
reflectEvent( server, "request", this);
|
||||
reflectEvent( server, "stream", this);
|
||||
reflectEvent( server, "close", this);
|
||||
reflectEvent( server, "clientError", this);
|
||||
reflectEvent( server, "error", this);
|
||||
|
||||
reflectMethod( server, "listen", this);
|
||||
reflectMethod( server, "close", this);
|
||||
|
||||
this.send = function(id, data){
|
||||
manager.find(id, function(client){
|
||||
clientWrite(client, data);
|
||||
});
|
||||
};
|
||||
|
||||
this.broadcast = function(data){
|
||||
manager.forEach(function(client){
|
||||
clientWrite(client, data);
|
||||
});
|
||||
};
|
||||
|
||||
this.server = server;
|
||||
this.manager = manager;
|
||||
this.options = options;
|
||||
};
|
||||
|
||||
util.inherits(Server, events.EventEmitter);
|
||||
|
|
@ -0,0 +1,136 @@
|
|||
/*-----------------------------------------------
|
||||
Requirements:
|
||||
-----------------------------------------------*/
|
||||
var sys = require("sys")
|
||||
, http = require("http")
|
||||
, events = require("events")
|
||||
, path = require("path");
|
||||
|
||||
var Manager = require("./ws/manager")
|
||||
, Connection = require("./ws/connection");
|
||||
|
||||
|
||||
/*-----------------------------------------------
|
||||
Mixin:
|
||||
-----------------------------------------------*/
|
||||
var mixin = function(target, source) {
|
||||
for(var i = 0, keys = Object.keys(source), l = keys.length; i < l; ++i) {
|
||||
if(source.hasOwnProperty(keys[i])){
|
||||
target[keys[i]] = source[keys[i]];
|
||||
}
|
||||
}
|
||||
return target;
|
||||
};
|
||||
|
||||
/*-----------------------------------------------
|
||||
WebSocket Server Exports:
|
||||
-----------------------------------------------*/
|
||||
exports.Server = Server;
|
||||
exports.createServer = function(options){
|
||||
return new Server(options);
|
||||
};
|
||||
|
||||
exports._Manager = Manager;
|
||||
exports._Connection = Connection;
|
||||
|
||||
/*-----------------------------------------------
|
||||
WebSocket Server Implementation:
|
||||
-----------------------------------------------*/
|
||||
|
||||
function Server(options){
|
||||
var ws = this;
|
||||
|
||||
events.EventEmitter.call(this);
|
||||
|
||||
this.options = mixin({
|
||||
debug: false, // Boolean: Show debug information.
|
||||
version: "auto", // String: Value must be either: draft75, draft76, auto
|
||||
origin: "*", // String, Array: A match for a valid connection origin
|
||||
subprotocol: "*", // String, Array: A match for a valid connection subprotocol.
|
||||
datastore: true, // Object, Function, Boolean: If === true, then it is the default mem-store.
|
||||
server: new http.Server()
|
||||
}, options || {});
|
||||
|
||||
this.manager = new Manager(this.options.debug);
|
||||
this.server = this.options.server
|
||||
this.debug = this.options.debug;
|
||||
|
||||
this.server.addListener("upgrade", function(req, socket, upgradeHead){
|
||||
if( req.method == "GET" && ( "upgrade" in req.headers && "connection" in req.headers) &&
|
||||
req.headers.upgrade.toLowerCase() == "websocket" && req.headers.connection.toLowerCase() == "upgrade"
|
||||
){
|
||||
// create a new connection, it'll handle everything else.
|
||||
new Connection(ws, req, socket, upgradeHead);
|
||||
} else {
|
||||
// Close the socket, it wasn't a valid connection.
|
||||
socket.end();
|
||||
socket.destroy();
|
||||
}
|
||||
});
|
||||
|
||||
this.server.addListener("connection", function(socket){
|
||||
socket.setTimeout(0);
|
||||
socket.setNoDelay(true);
|
||||
socket.setKeepAlive(true, 0);
|
||||
});
|
||||
|
||||
this.server.addListener("listening", function(req, res){
|
||||
ws.emit("listening");
|
||||
});
|
||||
|
||||
this.server.addListener("close", function(errno){
|
||||
ws.emit("shutdown", errno);
|
||||
});
|
||||
|
||||
this.server.addListener("request", function(req, res){
|
||||
ws.emit("request", req, res);
|
||||
});
|
||||
|
||||
this.server.addListener("stream", function(stream){
|
||||
ws.emit("stream", stream);
|
||||
});
|
||||
|
||||
this.server.addListener("clientError", function(e){
|
||||
ws.emit("clientError", e);
|
||||
});
|
||||
};
|
||||
|
||||
sys.inherits(Server, events.EventEmitter);
|
||||
|
||||
/*-----------------------------------------------
|
||||
Public API
|
||||
-----------------------------------------------*/
|
||||
Server.prototype.setSecure = function (credentials) {
|
||||
this.server.setSecure.call(this.server, credentials);
|
||||
}
|
||||
|
||||
Server.prototype.listen = function(){
|
||||
this.server.listen.apply(this.server, arguments);
|
||||
};
|
||||
|
||||
Server.prototype.close = function(){
|
||||
this.server.close();
|
||||
};
|
||||
|
||||
Server.prototype.send = function(id, data){
|
||||
this.manager.find(id, function(client){
|
||||
if(client && client._state === 4){
|
||||
client.write(data);
|
||||
}
|
||||
});
|
||||
};
|
||||
|
||||
Server.prototype.broadcast = function(data){
|
||||
this.manager.forEach(function(client){
|
||||
if(client && client._state === 4){
|
||||
client.write(data);
|
||||
}
|
||||
});
|
||||
};
|
||||
|
||||
|
||||
|
||||
Server.prototype.use = function(module){
|
||||
module.call(this, this.options);
|
||||
};
|
||||
|
|
@ -1,124 +1,149 @@
|
|||
/*-----------------------------------------------
|
||||
Requirements:
|
||||
-----------------------------------------------*/
|
||||
var debug = function(){};
|
||||
|
||||
var util = require("../_util")
|
||||
, events = require("events")
|
||||
var sys = require("sys")
|
||||
, Url = require("url")
|
||||
, Events = require("events")
|
||||
, Buffer = require("buffer").Buffer
|
||||
, Crypto = require("crypto");
|
||||
|
||||
var _events = require("../_events")
|
||||
|
||||
var Mixin = require("../lang/mixin");
|
||||
|
||||
/*-----------------------------------------------
|
||||
The Connection:
|
||||
-----------------------------------------------*/
|
||||
module.exports = Connection;
|
||||
|
||||
// Our connection instance:
|
||||
function Connection(manager, options, req, socket, upgradeHead){
|
||||
var _firstFrame
|
||||
, connection = this;
|
||||
function Connection(server, req, socket, data){
|
||||
this.debug = server.debug;
|
||||
|
||||
_events.EventEmitter.call(this);
|
||||
if (this.debug) {
|
||||
debug = function (id, data) { sys.error('\033[90mWS: ' + Array.prototype.join.call(arguments, ", ") + "\033[39m"); };
|
||||
} else {
|
||||
debug = function (id, data) { };
|
||||
}
|
||||
|
||||
Events.EventEmitter.call(this);
|
||||
|
||||
this._req = req;
|
||||
this._socket = socket;
|
||||
this._manager = manager;
|
||||
this.id = manager.createId(socket.remotePort);
|
||||
this._server = server;
|
||||
this.headers = this._req.headers;
|
||||
this.id = server.manager.createId(this._req.socket.remotePort);
|
||||
|
||||
this._options = Mixin({
|
||||
version: "auto", // String: Value must be either: draft75, draft76, auto
|
||||
origin: "*", // String, Array: A match for a valid connection origin
|
||||
subprotocol: "*", // String, Array: A match for a valid connection subprotocol.
|
||||
debug: true
|
||||
}, options);
|
||||
|
||||
if(connection._options.debug){
|
||||
debug = function () {
|
||||
util.error('\033[90mWS: ' + Array.prototype.join.call(arguments, ", ") + "\033[39m");
|
||||
process.stdout.flush();
|
||||
};
|
||||
}
|
||||
var _firstFrame = false;
|
||||
|
||||
Object.defineProperties(this, {
|
||||
version: {
|
||||
get: function(){
|
||||
return req.headers["sec-websocket-key1"] && req.headers["sec-websocket-key2"]
|
||||
? "draft76"
|
||||
: "draft75";
|
||||
if(this._req.headers["sec-websocket-key1"] && this._req.headers["sec-websocket-key2"]){
|
||||
return "draft76";
|
||||
} else {
|
||||
return "draft75";
|
||||
}
|
||||
}
|
||||
}
|
||||
});
|
||||
|
||||
// Set the initial connecting state.
|
||||
connection.state(1);
|
||||
// Setup the connection manager's state change listeners:
|
||||
connection.on("stateChange", function(state, laststate){
|
||||
if(state === 4) {
|
||||
manager.attach(connection);
|
||||
// Handle first frame breakages.
|
||||
if(_firstFrame){
|
||||
parser.write(_firstFrame);
|
||||
delete _firstFrame;
|
||||
}
|
||||
} else if(state === 5) {
|
||||
close(connection);
|
||||
} else if(state === 6 && laststate === 5) {
|
||||
manager.detach(connection);
|
||||
connection.emit("close");
|
||||
}
|
||||
});
|
||||
|
||||
if(server.options.datastore){
|
||||
var storage;
|
||||
|
||||
if(typeof server.options.datastore === "object" || typeof server.options.datastore === "function"){
|
||||
storage = server.options.datastore;
|
||||
} else if(server.options.datastore === true){
|
||||
storage = require("./mem-store");
|
||||
} else {
|
||||
storage = false;
|
||||
}
|
||||
}
|
||||
|
||||
// Start to process the connection
|
||||
if( !checkVersion(this)) {
|
||||
this.reject("Invalid version.");
|
||||
} else {
|
||||
var connection = this
|
||||
, parser;
|
||||
|
||||
// Let the debug mode know that we have a connection:
|
||||
debug(this.id, this.version+" connection");
|
||||
|
||||
socket.setTimeout(0);
|
||||
socket.setNoDelay(true);
|
||||
socket.setKeepAlive(true, 0);
|
||||
this.debug && debug(this.id, this.version+" connection");
|
||||
|
||||
// Set the initial connecting state.
|
||||
this.state(1);
|
||||
|
||||
// Handle incoming data:
|
||||
var parser = new Parser(this);
|
||||
parser = new Parser(this);
|
||||
|
||||
parser.on("message", function(message){
|
||||
debug(connection.id, "recv: " + message);
|
||||
connection.emit("message", message);
|
||||
});
|
||||
|
||||
socket.on("data", function(data){
|
||||
parser.write(data);
|
||||
socket.addListener("data", function(data){
|
||||
if(data.length == 2 && data[0] == 0xFF && data[1] == 0x00){
|
||||
connection.state(5);
|
||||
} else {
|
||||
parser.write(data);
|
||||
}
|
||||
});
|
||||
|
||||
// Handle the end of the stream, and set the state
|
||||
// appropriately to notify the correct events.
|
||||
socket.on("end", function(){
|
||||
debug(connection.id, "end");
|
||||
socket.addListener("end", function(){
|
||||
connection.state(5);
|
||||
});
|
||||
|
||||
socket.on('timeout', function () {
|
||||
socket.addListener('timeout', function () {
|
||||
debug(connection.id, "timed out");
|
||||
server.emit("timeout", connection);
|
||||
connection.emit("timeout");
|
||||
});
|
||||
|
||||
socket.on("error", function(e){
|
||||
debug(connection.id, "error", e);
|
||||
if(e.errno != 32){
|
||||
socket.addListener("error", function(e){
|
||||
if(e.errno == 32){
|
||||
connection.state(5);
|
||||
closeClient(connection);
|
||||
connection.state(6);
|
||||
} else {
|
||||
manager.emit("error", connection, e);
|
||||
connection.emit("error", e);
|
||||
connection.state(5);
|
||||
}
|
||||
connection.state(5);
|
||||
});
|
||||
|
||||
// Bubble errors up to the manager.
|
||||
connection.bubbleEvent("error", manager);
|
||||
|
||||
// Setup the connection manager's state change listeners:
|
||||
this.addListener("stateChange", function(state, laststate){
|
||||
//debug(connection.id, "Change state: "+laststate+" => "+state);
|
||||
if(state === 4){
|
||||
if(storage && storage.create){
|
||||
connection.storage = storage.create();
|
||||
} else if(storage){
|
||||
connection.storage = storage;
|
||||
}
|
||||
|
||||
server.manager.attach(connection.id, connection);
|
||||
server.emit("connection", connection);
|
||||
|
||||
if(_firstFrame){
|
||||
parser.write(_firstFrame);
|
||||
delete _firstFrame;
|
||||
}
|
||||
|
||||
} else if(state === 5){
|
||||
connection.close();
|
||||
} else if(state === 6 && laststate === 5){
|
||||
|
||||
if(connection.storage && connection.storage.disconnect){
|
||||
connection.storage.disconnect(connection.id);
|
||||
}
|
||||
|
||||
server.manager.detach(connection.id, function(){
|
||||
server.emit("close", connection);
|
||||
connection.emit("close");
|
||||
});
|
||||
}
|
||||
});
|
||||
|
||||
// Let us see the messages when in debug mode.
|
||||
if(this.debug){
|
||||
this.addListener("message", function(msg){
|
||||
debug(connection.id, "recv: " + msg);
|
||||
});
|
||||
}
|
||||
|
||||
// Carry out the handshaking.
|
||||
// - Draft75: There's no upgradeHead, goto Then.
|
||||
|
@ -129,60 +154,54 @@ function Connection(manager, options, req, socket, upgradeHead){
|
|||
// but in the case it does happen, then the state is set to waiting for
|
||||
// the upgradeHead.
|
||||
//
|
||||
// This switch is sorted in order of probably of occurence.
|
||||
switch(this.version) {
|
||||
case "draft76":
|
||||
if(upgradeHead.length >= 8) {
|
||||
if(upgradeHead.length > 8){
|
||||
_firstFrame = upgradeHead.slice(8, upgradeHead.length);
|
||||
}
|
||||
if(this.version == "draft75"){
|
||||
this.handshake();
|
||||
}
|
||||
|
||||
handshakes.draft76(connection, upgradeHead.slice(0, 8));
|
||||
} else {
|
||||
connection.reject("Missing key3");
|
||||
}
|
||||
break;
|
||||
if(this.version == "draft76"){
|
||||
if(data.length >= 8){
|
||||
this._upgradeHead = data.slice(0, 8);
|
||||
|
||||
case "draft75":
|
||||
handshakes.draft75(connection);
|
||||
break;
|
||||
_firstFrame = data.slice(8, data.length);
|
||||
|
||||
default:
|
||||
connection.reject("Unknown version: "+this.version);
|
||||
break;
|
||||
this.handshake();
|
||||
} else {
|
||||
this.reject("Missing key3");
|
||||
}
|
||||
}
|
||||
}
|
||||
};
|
||||
|
||||
util.inherits(Connection, _events.EventEmitter);
|
||||
sys.inherits(Connection, Events.EventEmitter);
|
||||
|
||||
/*-----------------------------------------------
|
||||
Various utility style functions:
|
||||
-----------------------------------------------*/
|
||||
var write = function(connection, data, encoding) {
|
||||
if(connection._socket.writable){
|
||||
var writeSocket = function(socket, data, encoding) {
|
||||
if(socket.writable){
|
||||
try {
|
||||
connection._socket.write(data, encoding);
|
||||
socket.write(data, encoding);
|
||||
return true;
|
||||
} catch(e){
|
||||
debug(null, "Error on write: "+e.toString());
|
||||
return false;
|
||||
}
|
||||
}
|
||||
return false;
|
||||
};
|
||||
|
||||
var close = function(connection) {
|
||||
//connection._socket.flush();
|
||||
connection._socket.end();
|
||||
connection._socket.destroy();
|
||||
debug(connection.id, "socket closed");
|
||||
connection.state(6);
|
||||
var closeClient = function(client){
|
||||
client._req.socket.flush();
|
||||
client._req.socket.end();
|
||||
client._req.socket.destroy();
|
||||
debug(client.id, "socket closed");
|
||||
client.state(6);
|
||||
};
|
||||
|
||||
function checkVersion(connection) {
|
||||
var server_version = connection._options.version.toLowerCase();
|
||||
function checkVersion(client){
|
||||
var server_version = client._server.options.version.toLowerCase();
|
||||
|
||||
return (server_version == "auto" || server_version == connection.version);
|
||||
return (server_version == "auto" || server_version == client.version);
|
||||
};
|
||||
|
||||
|
||||
|
@ -199,27 +218,23 @@ function pack(num) {
|
|||
/*-----------------------------------------------
|
||||
Formatters for the urls
|
||||
-----------------------------------------------*/
|
||||
|
||||
// TODO: Properly handle origin headers.
|
||||
function websocket_origin(connection) {
|
||||
var origin = connection._options.origin || "*";
|
||||
|
||||
function websocket_origin(){
|
||||
var origin = this._server.options.origin || "*";
|
||||
if(origin == "*" || Array.isArray(origin)){
|
||||
origin = connection._req.headers.origin;
|
||||
origin = this._req.headers.origin;
|
||||
}
|
||||
|
||||
return origin;
|
||||
};
|
||||
|
||||
function websocket_location(connection){
|
||||
if(connection._req.headers["host"] === undefined){
|
||||
connection.reject("Missing host header");
|
||||
function websocket_location(){
|
||||
if(this._req.headers["host"] === undefined){
|
||||
this.reject("Missing host header");
|
||||
return;
|
||||
}
|
||||
|
||||
var location = ""
|
||||
, secure = connection._socket.secure
|
||||
, host = connection._req.headers.host.split(":")
|
||||
, secure = this._req.socket.secure
|
||||
, host = this._req.headers.host.split(":")
|
||||
, port = host[1] !== undefined ? host[1] : (secure ? 443 : 80);
|
||||
|
||||
location += secure ? "wss://" : "ws://";
|
||||
|
@ -229,7 +244,7 @@ function websocket_location(connection){
|
|||
location += ":"+port;
|
||||
}
|
||||
|
||||
location += connection._req.url;
|
||||
location += this._req.url;
|
||||
|
||||
return location;
|
||||
};
|
||||
|
@ -258,19 +273,17 @@ Connection.prototype.state = function(state){
|
|||
}
|
||||
};
|
||||
|
||||
Connection.prototype.inspect = function(){
|
||||
return "<WS:Connection "+this.id+">";
|
||||
};
|
||||
Connection.prototype.write = function(data){
|
||||
var socket = this._req.socket;
|
||||
|
||||
Connection.prototype.write = function(data) {
|
||||
if(this._state === 4) {
|
||||
if(this._state == 4){
|
||||
debug(this.id, "write: "+data);
|
||||
|
||||
if(
|
||||
write(this, "\x00", "binary") &&
|
||||
write(this, data, "utf8") &&
|
||||
write(this, "\xff", "binary")
|
||||
) {
|
||||
writeSocket(socket, "\x00", "binary") &&
|
||||
writeSocket(socket, data, "utf8") &&
|
||||
writeSocket(socket, "\xff", "binary")
|
||||
){
|
||||
return true;
|
||||
} else {
|
||||
debug(this.id, "\033[31mERROR: write: "+data);
|
||||
|
@ -284,88 +297,86 @@ Connection.prototype.write = function(data) {
|
|||
Connection.prototype.send = Connection.prototype.write;
|
||||
|
||||
Connection.prototype.broadcast = function(data){
|
||||
this._manager.forEach(function(client){
|
||||
if(client && client._state === 4 && client.id != this.id){
|
||||
var conn = this;
|
||||
|
||||
this._server.manager.forEach(function(client){
|
||||
if(client && client._state === 4 && client.id != conn.id){
|
||||
client.write(data);
|
||||
}
|
||||
}, this);
|
||||
});
|
||||
};
|
||||
|
||||
Connection.prototype.close = function(){
|
||||
if(this._state == 4 && this._socket.writable){
|
||||
write(this, "\xff", "binary");
|
||||
write(this, "\x00", "binary");
|
||||
}
|
||||
var socket = this._req.socket;
|
||||
|
||||
this.state(5);
|
||||
if(this._state == 4 && socket.writable){
|
||||
writeSocket(socket, "\xff", "binary");
|
||||
writeSocket(socket, "\x00", "binary");
|
||||
}
|
||||
closeClient(this);
|
||||
};
|
||||
|
||||
|
||||
Connection.prototype.reject = function(reason){
|
||||
debug(this.id, "rejected. Reason: "+reason);
|
||||
this.state(5);
|
||||
this.debug && debug(this.id, "rejected. Reason: "+reason);
|
||||
|
||||
this.emit("rejected");
|
||||
closeClient(this);
|
||||
};
|
||||
|
||||
Connection.prototype.handshake = function(){
|
||||
if(this._state < 3){
|
||||
debug(this.id, this.version+" handshake");
|
||||
this.debug && debug(this.id, this.version+" handshake");
|
||||
|
||||
this.state(3);
|
||||
|
||||
doHandshake[this.version].call(this);
|
||||
} else {
|
||||
debug(this.id, "Already handshaked.");
|
||||
this.debug && debug(this.id, "Already handshaked.");
|
||||
}
|
||||
};
|
||||
|
||||
/*-----------------------------------------------
|
||||
Do the handshake.
|
||||
-----------------------------------------------*/
|
||||
var handshakes = {
|
||||
var doHandshake = {
|
||||
// Using draft75, work out and send the handshake.
|
||||
draft75: function(connection){
|
||||
connection.state(3);
|
||||
|
||||
var location = websocket_location(connection)
|
||||
, res;
|
||||
draft75: function(){
|
||||
var location = websocket_location.call(this), res;
|
||||
|
||||
if(location){
|
||||
res = "HTTP/1.1 101 Web Socket Protocol Handshake\r\n"
|
||||
+ "Upgrade: WebSocket\r\n"
|
||||
+ "Connection: Upgrade\r\n"
|
||||
+ "WebSocket-Origin: "+websocket_origin(connection)+"\r\n"
|
||||
+ "WebSocket-Origin: "+websocket_origin.call(this)+"\r\n"
|
||||
+ "WebSocket-Location: "+location;
|
||||
|
||||
if(connection._options.subprotocol && typeof connection._options.subprotocol == "string") {
|
||||
res += "\r\nWebSocket-Protocol: "+connection._options.subprotocol;
|
||||
if(this._server.options.subprotocol && typeof this._server.options.subprotocol == "string") {
|
||||
res += "\r\nWebSocket-Protocol: "+this._server.options.subprotocol;
|
||||
}
|
||||
|
||||
write(connection, res+"\r\n\r\n", "ascii");
|
||||
|
||||
connection.state(4);
|
||||
writeSocket(this._req.socket, res+"\r\n\r\n", "ascii");
|
||||
this.state(4);
|
||||
}
|
||||
},
|
||||
|
||||
// Using draft76 (security model), work out and send the handshake.
|
||||
draft76: function(connection, upgradeHead){
|
||||
connection.state(3);
|
||||
|
||||
var location = websocket_location(connection)
|
||||
, res;
|
||||
draft76: function(){
|
||||
var location = websocket_location.call(this), res;
|
||||
|
||||
if(location){
|
||||
res = "HTTP/1.1 101 WebSocket Protocol Handshake\r\n"
|
||||
+ "Upgrade: WebSocket\r\n"
|
||||
+ "Connection: Upgrade\r\n"
|
||||
+ "Sec-WebSocket-Origin: "+websocket_origin(connection)+"\r\n"
|
||||
+ "Sec-WebSocket-Origin: "+websocket_origin.call(this)+"\r\n"
|
||||
+ "Sec-WebSocket-Location: "+location;
|
||||
|
||||
if(connection._options.subprotocol && typeof connection._options.subprotocol == "string") {
|
||||
res += "\r\nSec-WebSocket-Protocol: "+connection._options.subprotocol;
|
||||
if(this._server.options.subprotocol && typeof this._server.options.subprotocol == "string") {
|
||||
res += "\r\nSec-WebSocket-Protocol: "+this._server.options.subprotocol;
|
||||
}
|
||||
|
||||
var strkey1 = connection._req.headers['sec-websocket-key1']
|
||||
, strkey2 = connection._req.headers['sec-websocket-key2']
|
||||
var strkey1 = this._req.headers['sec-websocket-key1']
|
||||
, strkey2 = this._req.headers['sec-websocket-key2']
|
||||
|
||||
, numkey1 = parseInt(strkey1.replace(/[^\d]/g, ""), 10)
|
||||
, numkey2 = parseInt(strkey2.replace(/[^\d]/g, ""), 10)
|
||||
|
@ -375,7 +386,7 @@ var handshakes = {
|
|||
|
||||
|
||||
if (spaces1 == 0 || spaces2 == 0 || numkey1 % spaces1 != 0 || numkey2 % spaces2 != 0) {
|
||||
connection.reject("WebSocket contained an invalid key -- closing connection.");
|
||||
this.reject("WebSocket contained an invalid key -- closing connection.");
|
||||
} else {
|
||||
var hash = Crypto.createHash("md5")
|
||||
, key1 = pack(parseInt(numkey1/spaces1))
|
||||
|
@ -383,14 +394,13 @@ var handshakes = {
|
|||
|
||||
hash.update(key1);
|
||||
hash.update(key2);
|
||||
hash.update(upgradeHead.toString("binary"));
|
||||
hash.update(this._upgradeHead.toString("binary"));
|
||||
|
||||
res += "\r\n\r\n";
|
||||
res += hash.digest("binary");
|
||||
|
||||
write(connection, res, "binary");
|
||||
|
||||
connection.state(4);
|
||||
writeSocket(this._req.socket, res, "binary");
|
||||
this.state(4);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
@ -400,15 +410,12 @@ var handshakes = {
|
|||
The new onData callback for
|
||||
http.Server IncomingMessage
|
||||
-----------------------------------------------*/
|
||||
function Parser(){
|
||||
events.EventEmitter.call(this);
|
||||
|
||||
var Parser = function(client){
|
||||
this.frameData = [];
|
||||
this.order = 0;
|
||||
this.client = client;
|
||||
};
|
||||
|
||||
util.inherits(Parser, events.EventEmitter);
|
||||
|
||||
Parser.prototype.write = function(data){
|
||||
var pkt, msg;
|
||||
for(var i = 0, len = data.length; i<len; i++){
|
||||
|
@ -424,12 +431,12 @@ Parser.prototype.write = function(data){
|
|||
this.order = 0;
|
||||
this.frameData = [];
|
||||
|
||||
this.emit("message", pkt.toString("utf8", 0, pkt.length));
|
||||
this.client.emit("message", pkt.toString("utf8", 0, pkt.length));
|
||||
} else {
|
||||
this.frameData.push(data[i]);
|
||||
}
|
||||
} else if(this.order == 1){
|
||||
debug("High Order packet handling is not yet implemented.");
|
||||
debug(this.client.id, "High Order packet handling is not yet implemented.");
|
||||
this.order = 0;
|
||||
}
|
||||
}
|
|
@ -0,0 +1,131 @@
|
|||
var debug, sys;
|
||||
|
||||
/*-----------------------------------------------
|
||||
Connection Manager
|
||||
-----------------------------------------------*/
|
||||
module.exports = Manager;
|
||||
|
||||
function Manager(showDebug){
|
||||
if(showDebug) {
|
||||
sys = require("sys");
|
||||
debug = function(){sys.error('\033[31mManager: ' + Array.prototype.join.call(arguments, ", ") + "\033[39m"); };
|
||||
} else {
|
||||
debug = function(){};
|
||||
}
|
||||
|
||||
this._head = null;
|
||||
this._tail = null;
|
||||
this._length = 0;
|
||||
this._counter = 0;
|
||||
};
|
||||
|
||||
Object.defineProperty(Manager.prototype, "length", {
|
||||
get: function(){
|
||||
return this._length;
|
||||
}
|
||||
});
|
||||
|
||||
Manager.prototype.createId = function(remotePort) {
|
||||
return process.pid + "" + remotePort + "" + (this._counter++);
|
||||
};
|
||||
|
||||
Manager.prototype.attach = function(id, client){
|
||||
var connection = {
|
||||
id: id,
|
||||
_next: null,
|
||||
client: client
|
||||
};
|
||||
|
||||
if(this._length == 0) {
|
||||
this._head = connection;
|
||||
this._tail = connection;
|
||||
} else {
|
||||
this._tail._next = connection;
|
||||
this._tail = connection;
|
||||
}
|
||||
|
||||
++this._length;
|
||||
|
||||
debug("Attached: "+id, this._length);
|
||||
};
|
||||
|
||||
Manager.prototype.detach = function(id, callback){
|
||||
var previous = current = this._head;
|
||||
|
||||
while(current !== null){
|
||||
if(current.id === id){
|
||||
previous._next = current._next;
|
||||
this._length--;
|
||||
|
||||
if(current.id === this._head.id){
|
||||
this._head = current._next;
|
||||
}
|
||||
if(current.id === this._tail.id){
|
||||
this._tail = previous;
|
||||
}
|
||||
|
||||
break;
|
||||
} else {
|
||||
previous = current;
|
||||
current = current._next;
|
||||
}
|
||||
}
|
||||
|
||||
delete current, previous;
|
||||
|
||||
debug("Detached: "+id, this._length);
|
||||
callback();
|
||||
};
|
||||
|
||||
Manager.prototype.find = function(id, callback, thisArg){
|
||||
var current = this._head;
|
||||
|
||||
while(current !== null){
|
||||
if(current.id === id){
|
||||
callback.call(thisArg, current.client);
|
||||
break;
|
||||
} else {
|
||||
current = current._next;
|
||||
}
|
||||
}
|
||||
};
|
||||
|
||||
Manager.prototype.forEach = function(callback, thisArg){
|
||||
var current = this._head;
|
||||
|
||||
while(current !== null){
|
||||
callback.call(thisArg, current.client);
|
||||
current = current._next;
|
||||
}
|
||||
};
|
||||
|
||||
Manager.prototype.map = function(callback, thisArg){
|
||||
var current = this._head
|
||||
, len = 0
|
||||
, result = new Array(this._length);
|
||||
|
||||
while(current !== null){
|
||||
result[len] = callback.call(thisArg, current.client, len, this._head);
|
||||
current = current._next;
|
||||
++len;
|
||||
}
|
||||
|
||||
return result;
|
||||
};
|
||||
|
||||
Manager.prototype.filter = function(callback, thisArg){
|
||||
var current = this._head
|
||||
, len = 0
|
||||
, result = new Array(this._length);
|
||||
|
||||
while(current !== null){
|
||||
if( Boolean(callback.call(thisArg, current.client, len, this._head)) ){
|
||||
result[len] = current.client;
|
||||
++len;
|
||||
}
|
||||
|
||||
current = current._next;
|
||||
}
|
||||
|
||||
return result;
|
||||
};
|
|
@ -0,0 +1,72 @@
|
|||
/*-----------------------------------------------
|
||||
Storage is a simple in memory storage object.
|
||||
In otherwords, don't run your website off it.
|
||||
-----------------------------------------------*/
|
||||
|
||||
module.exports = {
|
||||
create: function(){
|
||||
return new memStore();
|
||||
}
|
||||
};
|
||||
|
||||
function memStore(){
|
||||
var data = {};
|
||||
|
||||
return {
|
||||
set: function(key, value){
|
||||
return data[key] = value;
|
||||
},
|
||||
|
||||
get: function(key, def){
|
||||
if(data[key] !== undefined){
|
||||
return data[key];
|
||||
} else {
|
||||
return def;
|
||||
}
|
||||
},
|
||||
|
||||
exists: function(key){
|
||||
return data[key] !== undefined;
|
||||
},
|
||||
|
||||
incr: function(key){
|
||||
if(data[key] === undefined){
|
||||
data[key] = 0;
|
||||
}
|
||||
|
||||
if(typeof(data[key]) === "number"){
|
||||
return data[key]++;
|
||||
}
|
||||
},
|
||||
|
||||
decr: function(key){
|
||||
if(typeof(data[key]) === "number"){
|
||||
return data[key]--;
|
||||
}
|
||||
},
|
||||
|
||||
push: function(key, value){
|
||||
if(data[key] === undefined){
|
||||
data[key] = [];
|
||||
}
|
||||
|
||||
if(Array.isArray(data[key])){
|
||||
data[key].push(value);
|
||||
}
|
||||
},
|
||||
|
||||
pop: function(key){
|
||||
if(data[key] !== undefined && Array.isArray(data[key])){
|
||||
return data[key].pop();
|
||||
}
|
||||
},
|
||||
|
||||
del: function(key){
|
||||
data[key] = undefined;
|
||||
},
|
||||
|
||||
to_json: function(){
|
||||
return JSON.stringify(data);
|
||||
}
|
||||
};
|
||||
};
|
Загрузка…
Ссылка в новой задаче