Skip to content

Instantly share code, notes, and snippets.

Created November 2, 2010 01:48
Show Gist options
  • Star 1 You must be signed in to star a gist
  • Fork 1 You must be signed in to fork a gist
  • Save anonymous/659158 to your computer and use it in GitHub Desktop.
Save anonymous/659158 to your computer and use it in GitHub Desktop.
for nodejs. need do a lot refactoring though.
var net = require('net'),
sys = require('sys');
function WSServer(port, ip) {
if(!port) throw('You should assign a port to listen.');
this.port = port;
this.ip = ip||'127.0.0.1';
this.server = null;
var clients = {};
var state = {
handshaked: false
},
requestHeaders = [
/^GET (.*) HTTP\/1\.1$/,
/^Upgrade: WebSocket$/,
/^Connection: Upgrade$/,
/^Host: (.*)$/,
/^Origin: (.*)$/,
/^Sec-WebSocket-Protocol: (.*)$/,
/^Sec-WebSocket-Key1: (.*)$/,
/^Sec-WebSocket-Key2: (.*)$/
],
responseHeaders = [
'HTTP/1.1 101 WebSocket Protocol Handshake',
'Upgrade: WebSocket',
'Connection: Upgrade',
'Sec-WebSocket-Origin: {ORIGIN}',
'Sec-WebSocket-Location: {LOCATION}',
'Sec-WebSocket-Protocol: {PROTOCOL}'
];
function _abort(stream){
stream.end();
}
function pack(num) {
return new Buffer([
num >> 24 & 0xFF,
num >> 16 & 0xFF,
num >> 8 & 0xFF,
num & 0xFF
]);
};
function _getParams(_data, _requestHeaders){
var _responseParam = {
resource:null,
upgrade:null,
connection:null,
host:null,
port:null,
origin:null,
protocol:null,
chalenge:null,
key1:null,
key2:null,
key3:null,
location:null,
k1s:null,
k2s:null
};
var data1 = _data.toString('utf8',0,_data.length);
var fields = data1.split('\r\n');
fields.map(function(item){
_requestHeaders.map(function(rule,j){
var matches = [],t;
matches = item.match(rule);
if(matches && matches.length > 0){
switch(j) {
case 0:
if(matches.length==2)
_responseParam.resource = matches[1];
break;
case 1:
if(matches.length==1)
_responseParam.upgrade = matches[0];
break;
case 2:
if(matches.length==1)
_responseParam.connection = matches[0];
break;
case 3:
if(matches.length==2)
t = matches[1];
if(t.indexOf(':') > -1) {
t = t.split(':');
_responseParam.host = t[0];
_responseParam.port = t[1];
} else {
_responseParam.host = t;
}
break;
case 4:
if(matches.length==2)
_responseParam.origin = matches[1];
break;
case 5:
if(matches.length==2)
_responseParam.protocol = matches[1];
break;
case 6:
if(matches.length==2)
t = parseInt(matches[1].replace(/[^\d]/g,''),10);
_responseParam.k1s = matches[1].replace(/[^ ]/g,'').length;
if(0 === (t%_responseParam.k1s) && _responseParam.k1s !== 0 && t < 4294967295) {
_responseParam.key1 = t/_responseParam.k1s;
}
break;
case 7:
if(matches.length==2)
t = parseInt(matches[1].replace(/[^\d]/g,''));
_responseParam.k2s = matches[1].replace(/[^ ]/g,'').length;
if(0 === (t%_responseParam.k2s) && _responseParam.k2s !== 0 && t < 4294967295) {
_responseParam.key2 = t/_responseParam.k2s;
}
break;
}
}
});
});
for(var m=0;m<_data.length;m++) {
if(_data[m]==0x0D && _data[m+1]==0x0A && _data[m+2]==0x0D && _data[m+3]==0x0A) {
_responseParam.key3 = _data.slice(m+4, m+12);
break;
}
}
return _responseParam;
}
function _checkRequestValid(_responseParam) {
var required = [
'resource',
'upgrade',
'connection',
'host',
'origin',
'key1',
'key2',
'key3'
];
var flag = true;
required.map(function(item){
if(!_responseParam[item]) {
sys.log('client handshake invalid: ' + item);
flag = false;
}
});
return flag;
}
function _handshake(data, requestHeaders, stream){
responseParam = _getParams(data, requestHeaders);
if(!_checkRequestValid(responseParam)) {
_abort(stream);
}
sys.log('client handshake is valid.');
responseParam.location = 'ws://' + responseParam.host + (responseParam.port? ':'+responseParam.port:'') + responseParam.resource;
var responseHeader = responseHeaders.concat('','').join('\r\n');
var md5 = require('crypto').createHash('md5');
md5.update(pack(parseInt(responseParam.key1)).toString('binary'));
md5.update(pack(parseInt(responseParam.key2)).toString('binary'));
md5.update(responseParam.key3.toString('binary',0,responseParam.key3.length));
responseHeader = responseHeader.replace(/\{ORIGIN\}/,responseParam.origin)
.replace(/\{LOCATION\}/,responseParam.location)
.replace(/\{PROTOCOL\}/,responseParam.protocol);
stream.write(responseHeader+md5.digest('binary'),'binary');
sys.log('server handshake sent ok.');
state.handshaked = true;
return;
}
function _receiveData(data, stream) {
if(data.length==2) {
if(data[0] == 0xFF && data[1] == 0x00) {
_abort(stream);
return;
}
}
if(data[0] != 0x00) {
return;
}
function writePacket(s) {
stream.write(new Buffer([0]));
stream.write(s,'utf8');
stream.write(new Buffer([255]));
}
var content = data.toString('utf8', 1, data.length-1);
sys.log('frame data: ' + content);
writePacket(content);
}
this.start = function() {
this.server = net.createServer(function(stream){
stream.on('connect', function(){
this.sid = this.remoteAddress + ((new Date().getTime()) + Math.random()*100);
clients[this.sid] = this;
sys.log('client connected. sid: ['+this.sid+']');
});
stream.on('data',function(data){
if(!state.handshaked) {
_handshake(data, requestHeaders, stream);
} else {
_receiveData(data, stream);
}
});
stream.on('end',function(){
stream.end();
sys.log('client end');
});
stream.on('close',function(){
stream.destroy();
delete clients[this.sid];
sys.log('client: ['+this.sid+'] closed');
});
});
this.server.on('close',function(){
sys.log('server closed');
});
this.server.listen(this.port, this.ip);
};
this.close = function() {
for (i in clients) {
clients[i].end();
clients[i].destroy();
delete clients[i];
}
this.server.close();
};
}
var Server = new WSServer(8080, '0.0.0.0');
var Admin = net.createServer(function(stream){
var cmd = '';
stream.setEncoding('ascii');
stream.on('data', function(data){
cmd += data;
if(cmd.indexOf('quit')>-1) {
sys.log('server stopping');
stream.end();
stream.destroy();
Server.close();
Admin.close();
}
});
});
Server.start();
Admin.listen(8081, '127.0.0.1');
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment