Permalink
Browse files

Fixed connection close handling

  • Loading branch information...
1 parent d2ab6fb commit 6b92c52c2d314ac14e6157aacd56d548f1cecf94 @miksago miksago committed Mar 25, 2011
Showing with 58 additions and 22 deletions.
  1. +40 −17 lib/ws/connection.js
  2. +3 −1 samples/handshake-packets
  3. +14 −4 test/manual/chat-server.js
  4. +1 −0 test/manual/client.html
View
57 lib/ws/connection.js
@@ -39,7 +39,7 @@ function Connection(manager, options, req, socket, upgradeHead) {
if (connection._options.debug) {
debug = function() {
util.error('\033[90mWS: ' +
- Array.prototype.join.call(arguments, ', ') +
+ Array.prototype.join.call(arguments, ' ') +
'\033[39m'
);
process.stdout.flush();
@@ -58,10 +58,14 @@ function Connection(manager, options, req, socket, upgradeHead) {
}
});
+ // Close timeout, for browsers that don't send the close packet.
+ connection._closeTimer = undefined;
+
// Set the initial connecting state.
connection.state(1);
// Setup the connection manager's state change listeners:
connection.on('stateChange', function(state, laststate) {
+ if (connection._options.debug) debug(connection.id, 'stateChange: ', laststate, '->', state);
if (state === 4) {
manager.attach(connection);
// Handle first frame breakages.
@@ -96,10 +100,16 @@ function Connection(manager, options, req, socket, upgradeHead) {
debug(connection.id, 'recv: ' + message);
connection.emit('message', message);
});
-
+
parser.on("close", function() {
- debug(connection.id, "requested close");
- connection.close();
+ debug(connection.id, "requested close");
+
+ // Timer to catch clients that don't send close packets.
+ // I'm looking at you safari and chrome.
+ if (connection._closeTimer) {
+ clearTimeout(connection._closeTimer);
+ }
+ connection.state(5);
});
socket.on('data', function(data) {
@@ -169,6 +179,7 @@ util.inherits(Connection, _events.EventEmitter);
Various utility style functions:
-----------------------------------------------*/
function write(connection, data) {
+ debug(connection.id, 'write: ', (new Buffer(data)).inspect());
if (connection._socket.writable) {
return connection._socket.write(data, 'binary');
}
@@ -267,8 +278,6 @@ Connection.prototype.inspect = function() {
Connection.prototype.write = function(data) {
if (this._state === 4) {
- debug(this.id, 'write: ' + data);
-
return write(this, '\u0000' + data + '\uffff');
} else {
debug(this.id, '\033[31mCould not send.');
@@ -287,14 +296,18 @@ Connection.prototype.broadcast = function(data) {
};
Connection.prototype.close = function() {
- if (this._state == 4 && this._socket.writable) {
- write(this, '\xff\x00');
+ var connection = this;
+
+ if (connection._state == 4 && connection._socket.writable) {
+ write(connection, '\xff\x00');
}
- this.state(5);
+ // Add a two second timeout for closing connections.
+ connection._closeTimer = setTimeout(function(){
+ connection.state(5);
+ }, 15 * 1000);
};
-
Connection.prototype.reject = function(reason) {
debug(this.id, 'rejected. Reason: ' + reason);
this.state(5);
@@ -398,35 +411,45 @@ function Parser() {
this.frameData = [];
this.order = 0;
+ this.closing = false;
}
util.inherits(Parser, events.EventEmitter);
Parser.prototype.write = function(data) {
var pkt, msg;
+
+ debug('parse.write', (new Buffer(data)).inspect())
+
for (var i = 0, len = data.length; i < len; i++) {
if (this.order == 0) {
if (data[i] & 0x80 == 0x80) {
this.order = 1;
} else {
this.order = -1;
}
- } else if (this.order == -1) {
+ }
+
+ if (this.order == -1) {
if (data[i] === 0xFF) {
pkt = new Buffer(this.frameData);
this.order = 0;
this.frameData = [];
this.emit('message', pkt.toString('utf8', 0, pkt.length));
- } else {
+ } else if (data[i] !== 0x00) {
this.frameData.push(data[i]);
}
} else if (this.order == 1) {
- debug('High Order packet handling is not yet implemented.');
- this.order = 0;
-
- if(data[i] === 0x00){
- this.emit("close");
+ if (this.closing && data[i] === 0x00) {
+ this.emit('close');
+ this.closing = false;
+ } else if (data[i] === 0xFF && this.frameData.length == 0) {
+ this.closing = true;
+ } else {
+ debug('High Order packet handling is not yet implemented.');
+ this.order = 0;
+ this.closing = false;
}
}
}
View
4 samples/handshake-packets
@@ -45,7 +45,8 @@ GET / HTTP/1.1
Connection: Upgrade
Upgrade: WebSocket
Sec-WebSocket-Key1: 3e6b263 4 17 80
-Origin: http://example.com
+Origin: http://localhost:8000
+Host: http://localhost:8000
Sec-WebSocket-Key2: 17 9 G`ZD9 2 2b 7X 3 /r90
WjN}|M(6
@@ -54,6 +55,7 @@ WjN}|M(6
+
GET / HTTP/1.1
Connection: Upgrade
Host: example.com
View
18 test/manual/chat-server.js
@@ -11,7 +11,7 @@ var httpServer = http.createServer(function(req, res){
res.end("");
} else {
res.writeHead(200, {'Content-Type': 'text/html', 'Connection': 'close'});
- fs.createReadStream( path.normalize(path.join(__dirname, "chat.html")), {
+ fs.createReadStream( path.normalize(path.join(__dirname, "client.html")), {
'flags': 'r',
'encoding': 'binary',
'mode': 0666,
@@ -39,18 +39,28 @@ server.addListener("listening", function(){
// Handle WebSocket Requests
server.addListener("connection", function(conn){
-
+ console.log('[*] open');
conn.send("** Connected as: user_"+conn.id);
conn.send("** Type `/nick USERNAME` to change your username");
conn.broadcast("** "+conn.id+" connected");
conn.addListener("message", function(message){
- server.broadcast(conn.id+": "+message);
+ if (message == 'close') {
+ console.log('[-] close requested')
+ conn.close();
+ } else {
+ console.log('[+] ', (new Buffer(message)).inspect());
+ server.broadcast(conn.id+": "+message);
+ }
});
+
+ conn.addListener("close", function(){
+ console.log('[*] close');
+ })
});
-server.addListener("close", function(conn){
+server.addListener("disconnect", function(conn){
server.broadcast("<"+conn.id+"> disconnected");
});
View
1 test/manual/client.html
@@ -85,6 +85,7 @@
conn.onclose = function() {
log("closed");
+ conn = false;
};
conn.onopen = function() {

0 comments on commit 6b92c52

Please sign in to comment.