mirror of
https://github.com/socketio/socket.io.git
synced 2026-01-11 16:08:24 -05:00
Compare commits
46 Commits
| Author | SHA1 | Date | |
|---|---|---|---|
|
|
c173141f8b | ||
|
|
91cdddb32c | ||
|
|
4acf98db9d | ||
|
|
73cc5e2c66 | ||
|
|
58ac323423 | ||
|
|
cd2c5140f1 | ||
|
|
ae159b5f7d | ||
|
|
e738e7c26e | ||
|
|
4e7db51c8c | ||
|
|
f87d883d79 | ||
|
|
0a2d007179 | ||
|
|
223c58b248 | ||
|
|
4ea73fc889 | ||
|
|
830fe231d5 | ||
|
|
79faa40dbf | ||
|
|
303b485775 | ||
|
|
4322dd23de | ||
|
|
9686771535 | ||
|
|
7da19a2c38 | ||
|
|
15613de7d4 | ||
|
|
3d11b176fe | ||
|
|
910de83942 | ||
|
|
b2c0b78130 | ||
|
|
22ef7918b3 | ||
|
|
7b1dba192c | ||
|
|
18d78f5448 | ||
|
|
0edd16d575 | ||
|
|
41100e5a51 | ||
|
|
7b3781c2c8 | ||
|
|
deb59f6725 | ||
|
|
f95988ff59 | ||
|
|
fc40156a8c | ||
|
|
ce6fa8611b | ||
|
|
5ba6035260 | ||
|
|
18068b56cd | ||
|
|
ca4589d929 | ||
|
|
dc08ca5fb6 | ||
|
|
fa90d2c520 | ||
|
|
dc960f9bc6 | ||
|
|
1f75828eb7 | ||
|
|
0a9743d56f | ||
|
|
0985193ae1 | ||
|
|
1aa7845c62 | ||
|
|
8bf30afda2 | ||
|
|
b971a18e5b | ||
|
|
57c8d37d47 |
13
.gitmodules
vendored
13
.gitmodules
vendored
@@ -1,6 +1,9 @@
|
||||
[submodule "lib/vendor/js-oo"]
|
||||
path = lib/vendor/js-oo
|
||||
url = git://github.com/visionmedia/js-oo.git
|
||||
[submodule "test/client"]
|
||||
path = test/client
|
||||
[submodule "example/client"]
|
||||
path = example/client
|
||||
url = git://github.com/LearnBoost/Socket.IO.git
|
||||
[submodule "tests/support/expresso"]
|
||||
path = tests/support/expresso
|
||||
url = git://github.com/visionmedia/expresso.git
|
||||
[submodule "tests/support/node-websocket-client"]
|
||||
path = tests/support/node-websocket-client
|
||||
url = git://github.com/pgriess/node-websocket-client.git
|
||||
|
||||
2
Makefile
Normal file
2
Makefile
Normal file
@@ -0,0 +1,2 @@
|
||||
test:
|
||||
./tests/support/expresso/bin/expresso -I lib $(TESTFLAGS) tests/*.test.js
|
||||
22
README.md
22
README.md
@@ -8,21 +8,19 @@ The `Socket.IO` server provides seamless supports for a variety of transports in
|
||||
- XHR Multipart Streaming
|
||||
- Forever Iframe
|
||||
|
||||
Requirements
|
||||
------------
|
||||
## Requirements
|
||||
|
||||
- Node v0.1.94+
|
||||
- Node v0.1.102+
|
||||
- [Socket.IO client](http://github.com/LearnBoost/Socket.IO) to connect from the browser
|
||||
|
||||
How to use
|
||||
----------
|
||||
## How to use
|
||||
|
||||
`Socket.IO` is designed not to take over an entire port or Node `http.Server` instance. This means that if you choose your HTTP server to listen on the port 80, `socket.io` can intercept requests directed to it and the normal requests will still be served.
|
||||
|
||||
By default, the server will intercept requests that contain `socket.io` in the path / resource part of the URI. You can change this (look at the available options below).
|
||||
|
||||
var http = require('http'),
|
||||
io = require('./socket.io/socket.io.js'),
|
||||
io = require('./path/to/socket.io'),
|
||||
|
||||
server = http.createServer(function(req, res){
|
||||
// your normal server code
|
||||
@@ -32,7 +30,13 @@ By default, the server will intercept requests that contain `socket.io` in the p
|
||||
});
|
||||
|
||||
// socket.io, I choose you
|
||||
io.listen(server);
|
||||
var socket = io.listen(server);
|
||||
|
||||
socket.on('connection', function(client){
|
||||
// new client is here!
|
||||
client.on('message', function(){ … })
|
||||
client.on('disconnect', function(){ … })
|
||||
});
|
||||
|
||||
On the client side, you should use the [Socket.IO client](https://github.com/LearnBoost/Socket.IO) to connect.
|
||||
|
||||
@@ -48,7 +52,7 @@ Another, once cloned
|
||||
|
||||
## Demo
|
||||
|
||||
To run the demo, go to `test` directory and run
|
||||
To run the demo, go to `example` directory and run
|
||||
|
||||
sudo node server.js
|
||||
|
||||
@@ -176,7 +180,7 @@ One of the design goals is that you should be able to implement whatever protoco
|
||||
|
||||
* Message batching
|
||||
|
||||
In order to optimize the resources, messages are batched. In the event of the server trying to send multiple messages while the client is temporarily disconnected (ie: xhr polling), and messages are stacked, or messages being stacked prior to the handshake being successful, a JSON object containing a list (array) of messages is sent to the client.
|
||||
In order to optimize the resources, messages are buffered. In the event of the server trying to send multiple messages while the client is temporarily disconnected (eg: xhr polling), messages are stacked, then encoded in a lightweight way and sent to the client whenever he becomes available.
|
||||
|
||||
Despite this extra layer, your messages are delivered unaltered to the different event listeners. You can JSON.stringify() objects, send XML, or maybe plain text.
|
||||
|
||||
|
||||
@@ -3,6 +3,7 @@
|
||||
<head>
|
||||
<title>socket.io client test</title>
|
||||
|
||||
<script src="/json.js"></script> <!-- for ie -->
|
||||
<script src="/client/socket.io.js"></script>
|
||||
</head>
|
||||
<body>
|
||||
@@ -12,8 +13,8 @@
|
||||
|
||||
function message(obj){
|
||||
var el = document.createElement('p');
|
||||
if ('announcement' in obj) el.innerHTML = '<em>' + obj.announcement;
|
||||
else if ('message' in obj) el.innerHTML = '<b>' + obj.message[0] + ':</b> ' + obj.message[1];
|
||||
if ('announcement' in obj) el.innerHTML = '<em>' + esc(obj.announcement) + '</em>';
|
||||
else if ('message' in obj) el.innerHTML = '<b>' + esc(obj.message[0]) + ':</b> ' + esc(obj.message[1]);
|
||||
document.getElementById('chat').appendChild(el);
|
||||
document.getElementById('chat').scrollTop = 1000000;
|
||||
}
|
||||
@@ -24,6 +25,10 @@
|
||||
message({ message: ['you', val] });
|
||||
document.getElementById('text').value = '';
|
||||
}
|
||||
|
||||
function esc(msg){
|
||||
return msg.replace(/</g, '<').replace(/>/g, '>');
|
||||
};
|
||||
|
||||
var socket = new io.Socket(null, {rememberTransport: false, port: 8080});
|
||||
socket.connect();
|
||||
1
example/client
Submodule
1
example/client
Submodule
Submodule example/client added at a8efecaa56
18
example/json.js
Normal file
18
example/json.js
Normal file
@@ -0,0 +1,18 @@
|
||||
if(!this.JSON){JSON=function(){function f(n){return n<10?'0'+n:n;}
|
||||
Date.prototype.toJSON=function(){return this.getUTCFullYear()+'-'+
|
||||
f(this.getUTCMonth()+1)+'-'+
|
||||
f(this.getUTCDate())+'T'+
|
||||
f(this.getUTCHours())+':'+
|
||||
f(this.getUTCMinutes())+':'+
|
||||
f(this.getUTCSeconds())+'Z';};var m={'\b':'\\b','\t':'\\t','\n':'\\n','\f':'\\f','\r':'\\r','"':'\\"','\\':'\\\\'};function stringify(value,whitelist){var a,i,k,l,r=/["\\\x00-\x1f\x7f-\x9f]/g,v;switch(typeof value){case'string':return r.test(value)?'"'+value.replace(r,function(a){var c=m[a];if(c){return c;}
|
||||
c=a.charCodeAt();return'\\u00'+Math.floor(c/16).toString(16)+
|
||||
(c%16).toString(16);})+'"':'"'+value+'"';case'number':return isFinite(value)?String(value):'null';case'boolean':case'null':return String(value);case'object':if(!value){return'null';}
|
||||
if(typeof value.toJSON==='function'){return stringify(value.toJSON());}
|
||||
a=[];if(typeof value.length==='number'&&!(value.propertyIsEnumerable('length'))){l=value.length;for(i=0;i<l;i+=1){a.push(stringify(value[i],whitelist)||'null');}
|
||||
return'['+a.join(',')+']';}
|
||||
if(whitelist){l=whitelist.length;for(i=0;i<l;i+=1){k=whitelist[i];if(typeof k==='string'){v=stringify(value[k],whitelist);if(v){a.push(stringify(k)+':'+v);}}}}else{for(k in value){if(typeof k==='string'){v=stringify(value[k],whitelist);if(v){a.push(stringify(k)+':'+v);}}}}
|
||||
return'{'+a.join(',')+'}';}}
|
||||
return{stringify:stringify,parse:function(text,filter){var j;function walk(k,v){var i,n;if(v&&typeof v==='object'){for(i in v){if(Object.prototype.hasOwnProperty.apply(v,[i])){n=walk(i,v[i]);if(n!==undefined){v[i]=n;}}}}
|
||||
return filter(k,v);}
|
||||
if(/^[\],:{}\s]*$/.test(text.replace(/\\./g,'@').replace(/"[^"\\\n\r]*"|true|false|null|-?\d+(?:\.\d*)?(?:[eE][+\-]?\d+)?/g,']').replace(/(?:^|:|,)(?:\s*\[)+/g,''))){j=eval('('+text+')');return typeof filter==='function'?walk('',j):j;}
|
||||
throw new SyntaxError('parseJSON');}};}();}
|
||||
@@ -1,7 +1,7 @@
|
||||
var http = require('http'),
|
||||
url = require('url'),
|
||||
fs = require('fs'),
|
||||
io = require('../lib/socket.io'),
|
||||
io = require('../'),
|
||||
sys = require('sys'),
|
||||
|
||||
send404 = function(res){
|
||||
@@ -29,7 +29,7 @@ server = http.createServer(function(req, res){
|
||||
res.end();
|
||||
} catch(e){
|
||||
send404(res);
|
||||
}
|
||||
}
|
||||
break;
|
||||
}
|
||||
|
||||
@@ -42,26 +42,22 @@ server.listen(8080);
|
||||
|
||||
// socket.io, I choose you
|
||||
// simplest chat application evar
|
||||
var buffer = [], json = JSON.stringify;
|
||||
var buffer = [],
|
||||
json = JSON.stringify,
|
||||
io = io.listen(server);
|
||||
|
||||
io.on('connection', function(client){
|
||||
client.send(json({ buffer: buffer }));
|
||||
client.broadcast(json({ announcement: client.sessionId + ' connected' }));
|
||||
|
||||
io.listen(server, {
|
||||
|
||||
onClientConnect: function(client){
|
||||
client.send(json({ buffer: buffer }));
|
||||
client.broadcast(json({ announcement: client.sessionId + ' connected' }));
|
||||
},
|
||||
|
||||
onClientDisconnect: function(client){
|
||||
client.broadcast(json({ announcement: client.sessionId + ' disconnected' }));
|
||||
},
|
||||
|
||||
onClientMessage: function(message, client){
|
||||
client.on('message', function(message){
|
||||
var msg = { message: [client.sessionId, message] };
|
||||
buffer.push(msg);
|
||||
if (buffer.length > 15) {
|
||||
buffer.shift();
|
||||
}
|
||||
if (buffer.length > 15) buffer.shift();
|
||||
client.broadcast(json(msg));
|
||||
}
|
||||
|
||||
});
|
||||
|
||||
client.on('disconnect', function(){
|
||||
client.broadcast(json({ announcement: client.sessionId + ' disconnected' }));
|
||||
});
|
||||
});
|
||||
4
index.js
Normal file
4
index.js
Normal file
@@ -0,0 +1,4 @@
|
||||
exports.Listener = require('./lib/socket.io/listener');
|
||||
exports.listen = function(server, options){
|
||||
return new exports.Listener(server, options);
|
||||
};
|
||||
@@ -1,9 +0,0 @@
|
||||
require.paths.unshift(__dirname + '/vendor/js-oo/lib');
|
||||
require('oo');
|
||||
|
||||
var sys = require('sys'),
|
||||
Listener = require('./socket.io/listener').Listener;
|
||||
|
||||
this.listen = function(server, options){
|
||||
return new Listener(server, options);
|
||||
};
|
||||
@@ -1,131 +1,172 @@
|
||||
var options = require('./util/options').options, urlparse = require('url').parse;
|
||||
var urlparse = require('url').parse,
|
||||
options = require('./utils').options,
|
||||
frame = '~m~',
|
||||
|
||||
exports.Client = Class({
|
||||
Client = module.exports = function(listener, req, res, options, head){
|
||||
process.EventEmitter.call(this);
|
||||
this.listener = listener;
|
||||
this.options({
|
||||
timeout: 8000,
|
||||
heartbeatInterval: 10000,
|
||||
closeTimeout: 0
|
||||
}, options);
|
||||
this.connections = 0;
|
||||
this.connected = false;
|
||||
this._heartbeats = 0;
|
||||
this.upgradeHead = head;
|
||||
this._onConnect(req, res);
|
||||
};
|
||||
|
||||
require('sys').inherits(Client, process.EventEmitter);
|
||||
|
||||
Client.prototype.send = function(message){
|
||||
if (!this.connected || !(this.connection.readyState === 'open' ||
|
||||
this.connection.readyState === 'writeOnly')){
|
||||
return this._queue(message);
|
||||
}
|
||||
this._write(this._encode(message));
|
||||
return this;
|
||||
};
|
||||
|
||||
Client.prototype.broadcast = function(message){
|
||||
if (!('sessionId' in this)) return this;
|
||||
this.listener.broadcast(message, this.sessionId);
|
||||
return this;
|
||||
};
|
||||
|
||||
Client.prototype._onMessage = function(data){
|
||||
var messages = this._decode(data);
|
||||
if (messages === false) return this.listener.options.log('Bad message received from client ' + this.sessionId);
|
||||
for (var i = 0, l = messages.length; i < l; i++){
|
||||
if (messages[i].substr(0, 3) == '~h~'){
|
||||
return this._onHeartbeat(messages[i].substr(3));
|
||||
}
|
||||
this.emit('message', messages[i]);
|
||||
this.listener._onClientMessage(messages[i], this);
|
||||
}
|
||||
};
|
||||
|
||||
Client.prototype._onConnect = function(req, res){
|
||||
var self = this;
|
||||
this.request = req;
|
||||
this.response = res;
|
||||
this.connection = this.request.connection;
|
||||
if (this._disconnectTimeout) clearTimeout(this._disconnectTimeout);
|
||||
};
|
||||
|
||||
include: [options],
|
||||
Client.prototype._encode = function(messages){
|
||||
var ret = '', message,
|
||||
messages = Array.isArray(messages) ? messages : [messages];
|
||||
for (var i = 0, l = messages.length; i < l; i++){
|
||||
message = messages[i] === null || messages[i] === undefined ? '' : String(messages[i]);
|
||||
ret += frame + message.length + frame + message;
|
||||
}
|
||||
return ret;
|
||||
};
|
||||
|
||||
options: {
|
||||
closeTimeout: 0,
|
||||
heartbeatInterval: 5000
|
||||
},
|
||||
|
||||
init: function(listener, req, res, options, head){
|
||||
this.listener = listener;
|
||||
this.setOptions(options);
|
||||
this.connections = 0;
|
||||
this.connected = false;
|
||||
this.upgradeHead = head;
|
||||
this._onConnect(req, res);
|
||||
},
|
||||
|
||||
send: function(message){
|
||||
if (!this.connected || !(this.connection.readyState === 'open' ||
|
||||
this.connection.readyState === 'writeOnly')) {
|
||||
return this._queue(message);
|
||||
Client.prototype._decode = function(data){
|
||||
var messages = [], number, n;
|
||||
do {
|
||||
if (data.substr(0, 3) !== frame) return messages;
|
||||
data = data.substr(3);
|
||||
number = '', n = '';
|
||||
for (var i = 0, l = data.length; i < l; i++){
|
||||
n = Number(data.substr(i, 1));
|
||||
if (data.substr(i, 1) == n){
|
||||
number += n;
|
||||
} else {
|
||||
data = data.substr(number.length + frame.length)
|
||||
number = Number(number);
|
||||
break;
|
||||
}
|
||||
}
|
||||
|
||||
this._write(JSON.stringify({messages: [message]}));
|
||||
return this;
|
||||
},
|
||||
messages.push(data.substr(0, number)); // here
|
||||
data = data.substr(number);
|
||||
} while(data !== '');
|
||||
return messages;
|
||||
};
|
||||
|
||||
Client.prototype._payload = function(){
|
||||
var payload = [];
|
||||
|
||||
this.connections++;
|
||||
this.connected = true;
|
||||
|
||||
if (!this.handshaked){
|
||||
this._generateSessionId();
|
||||
payload.push(this.sessionId);
|
||||
this.handshaked = true;
|
||||
}
|
||||
|
||||
payload = payload.concat(this._writeQueue || []);
|
||||
this._writeQueue = [];
|
||||
|
||||
if (payload.length) this._write(this._encode(payload));
|
||||
if (this.connections === 1) this.listener._onClientConnect(this);
|
||||
|
||||
if (this.options.timeout) this._heartbeat();
|
||||
};
|
||||
|
||||
Client.prototype._heartbeat = function(){
|
||||
var self = this;
|
||||
setTimeout(function(){
|
||||
self.send('~h~' + ++self._heartbeats);
|
||||
self._heartbeatTimeout = setTimeout(function(){
|
||||
self._onClose();
|
||||
}, self.options.timeout);
|
||||
}, self.options.heartbeatInterval);
|
||||
};
|
||||
|
||||
Client.prototype._onHeartbeat = function(h){
|
||||
if (h == this._heartbeats){
|
||||
clearTimeout(this._heartbeatTimeout);
|
||||
this._heartbeat();
|
||||
}
|
||||
};
|
||||
|
||||
broadcast: function(message){
|
||||
if (!('sessionId' in this)) {
|
||||
return this;
|
||||
}
|
||||
this.listener.broadcast(message, this.sessionId);
|
||||
return this;
|
||||
},
|
||||
|
||||
_onMessage: function(data){
|
||||
try {
|
||||
var messages = JSON.parse(data);
|
||||
} catch(e){
|
||||
return this.listener.options.log('Bad message received from client ' + this.sessionId);
|
||||
}
|
||||
for (var i = 0, l = messages.length; i < l; i++){
|
||||
this.listener._onClientMessage(messages[i], this);
|
||||
}
|
||||
},
|
||||
|
||||
_onConnect: function(req, res){
|
||||
Client.prototype._onClose = function(){
|
||||
if (this.connected){
|
||||
var self = this;
|
||||
this.request = req;
|
||||
this.response = res;
|
||||
this.connection = this.request.connection;
|
||||
if (this._disconnectTimeout) {
|
||||
clearTimeout(this._disconnectTimeout);
|
||||
}
|
||||
},
|
||||
|
||||
_payload: function(){
|
||||
var payload = [];
|
||||
|
||||
this.connections++;
|
||||
this.connected = true;
|
||||
|
||||
if (!this.handshaked){
|
||||
this._generateSessionId();
|
||||
payload.push(JSON.stringify({
|
||||
sessionid: this.sessionId
|
||||
}));
|
||||
this.handshaked = true;
|
||||
}
|
||||
|
||||
payload = payload.concat(this._writeQueue || []);
|
||||
this._writeQueue = [];
|
||||
|
||||
if (payload.length) {
|
||||
this._write(JSON.stringify({messages: payload}));
|
||||
}
|
||||
if (this.connections === 1) {
|
||||
this.listener._onClientConnect(this);
|
||||
}
|
||||
},
|
||||
|
||||
_onClose: function(){
|
||||
var self = this;
|
||||
if (this._heartbeatInterval) {
|
||||
clearInterval(this._heartbeatInterval);
|
||||
}
|
||||
if ('_heartbeatTimeout' in this) clearTimeout(this._heartbeatTimeout);
|
||||
this.connected = false;
|
||||
this._disconnectTimeout = setTimeout(function(){
|
||||
self._onDisconnect();
|
||||
}, this.options.closeTimeout);
|
||||
},
|
||||
|
||||
_onDisconnect: function(){
|
||||
if (!this.finalized){
|
||||
this._writeQueue = [];
|
||||
this.connected = false;
|
||||
this.finalized = true;
|
||||
if (this.handshaked) {
|
||||
this.listener._onClientDisconnect(this);
|
||||
}
|
||||
}
|
||||
},
|
||||
|
||||
_queue: function(message){
|
||||
if (!('_writeQueue' in this)) {
|
||||
this._writeQueue = [];
|
||||
}
|
||||
this._writeQueue.push(message);
|
||||
return this;
|
||||
},
|
||||
|
||||
_generateSessionId: function(){
|
||||
if (this.sessionId) {
|
||||
return this.listener.options.log('This client already has a session id');
|
||||
}
|
||||
this.sessionId = Math.random().toString().substr(2);
|
||||
return this;
|
||||
},
|
||||
|
||||
_verifyOrigin: function(origin){
|
||||
var parts = urlparse(origin), origins = this.listener.options.origins;
|
||||
return origins.indexOf('*:*') !== -1 ||
|
||||
origins.indexOf(parts.host + ':' + parts.port) !== -1 ||
|
||||
origins.indexOf(parts.host + ':*') !== -1 ||
|
||||
origins.indexOf('*:' + parts.port) !== -1;
|
||||
}
|
||||
};
|
||||
|
||||
});
|
||||
Client.prototype._onDisconnect = function(){
|
||||
if (!this.finalized){
|
||||
this._writeQueue = [];
|
||||
this.connected = false;
|
||||
this.finalized = true;
|
||||
if (this.handshaked){
|
||||
this.emit('disconnect');
|
||||
this.listener._onClientDisconnect(this);
|
||||
}
|
||||
}
|
||||
};
|
||||
|
||||
Client.prototype._queue = function(message){
|
||||
if (!('_writeQueue' in this)){
|
||||
this._writeQueue = [];
|
||||
}
|
||||
this._writeQueue.push(message);
|
||||
return this;
|
||||
};
|
||||
|
||||
Client.prototype._generateSessionId = function(){
|
||||
if (this.sessionId) return this.listener.options.log('This client already has a session id');
|
||||
this.sessionId = Math.random().toString().substr(2);
|
||||
return this;
|
||||
};
|
||||
|
||||
Client.prototype._verifyOrigin = function(origin){
|
||||
var parts = urlparse(origin), origins = this.listener.options.origins;
|
||||
return origins.indexOf('*:*') !== -1 ||
|
||||
origins.indexOf(parts.host + ':' + parts.port) !== -1 ||
|
||||
origins.indexOf(parts.host + ':*') !== -1 ||
|
||||
origins.indexOf('*:' + parts.port) !== -1;
|
||||
};
|
||||
|
||||
for (var i in options) Client.prototype[i] = options[i];
|
||||
@@ -1,119 +1,124 @@
|
||||
var url = require('url'),
|
||||
options = require('./util/options').options,
|
||||
Client = require('./client').Client,
|
||||
transports = {};
|
||||
sys = require('sys'),
|
||||
options = require('./utils').options,
|
||||
Client = require('./client'),
|
||||
transports = {
|
||||
'flashsocket': require('./transports/flashsocket'),
|
||||
'htmlfile': require('./transports/htmlfile'),
|
||||
'websocket': require('./transports/websocket'),
|
||||
'xhr-multipart': require('./transports/xhr-multipart'),
|
||||
'xhr-polling': require('./transports/xhr-polling')
|
||||
},
|
||||
|
||||
var Listener = exports.Listener = Class({
|
||||
|
||||
include: [process.EventEmitter.prototype, options],
|
||||
|
||||
options: {
|
||||
Listener = module.exports = function(server, options){
|
||||
process.EventEmitter.call(this);
|
||||
var self = this;
|
||||
this.server = server;
|
||||
this.options({
|
||||
origins: '*:*',
|
||||
resource: 'socket.io',
|
||||
transports: ['websocket', 'flashsocket', 'htmlfile', 'xhr-multipart', 'xhr-polling'],
|
||||
transportOptions: {},
|
||||
transportOptions: {
|
||||
'xhr-polling': {
|
||||
timeout: null, // no heartbeats for polling
|
||||
closeTimeout: 8000,
|
||||
duration: 20000
|
||||
}
|
||||
},
|
||||
log: function(message){
|
||||
require('sys').log(message);
|
||||
}
|
||||
},
|
||||
|
||||
init: function(server, options){
|
||||
var self = this;
|
||||
process.EventEmitter.call(this);
|
||||
this.server = server;
|
||||
this.setOptions(options);
|
||||
this.clients = [];
|
||||
this.clientsIndex = {};
|
||||
|
||||
var listeners = this.server.listeners('request');
|
||||
this.server.removeAllListeners('request');
|
||||
|
||||
this.server.addListener('request', function(req, res){
|
||||
if (self.check(req, res)) return;
|
||||
for (var i = 0; i < listeners.length; i++) {
|
||||
listeners[i].call(this, req, res);
|
||||
}
|
||||
});
|
||||
|
||||
this.server.addListener('upgrade', function(req, socket, head){
|
||||
if (!self.check(req, socket, true, head)){
|
||||
socket.destroy();
|
||||
}
|
||||
});
|
||||
|
||||
this.options.transports.forEach(function(t){
|
||||
if (!(t in transports)){
|
||||
transports[t] = require('./transports/' + t)[t];
|
||||
if (transports[t].init) transports[t].init(this);
|
||||
}
|
||||
}, this);
|
||||
|
||||
this.options.log('socket.io ready - accepting connections');
|
||||
},
|
||||
|
||||
broadcast: function(message, except){
|
||||
for (var i = 0, l = this.clients.length; i < l; i++){
|
||||
if (this.clients[i] && (!except || [].concat(except).indexOf(this.clients[i].sessionId) == -1)){
|
||||
this.clients[i].send(message);
|
||||
}
|
||||
}, options);
|
||||
this.clients = [];
|
||||
this.clientsIndex = {};
|
||||
|
||||
var listeners = this.server.listeners('request');
|
||||
this.server.removeAllListeners('request');
|
||||
|
||||
this.server.addListener('request', function(req, res){
|
||||
if (self.check(req, res)) return;
|
||||
for (var i = 0; i < listeners.length; i++){
|
||||
listeners[i].call(this, req, res);
|
||||
}
|
||||
return this;
|
||||
},
|
||||
|
||||
check: function(req, res, httpUpgrade, head){
|
||||
var path = url.parse(req.url).pathname, parts, cn;
|
||||
if (path.indexOf('/' + this.options.resource) === 0){
|
||||
parts = path.substr(1).split('/');
|
||||
if (parts[2]){
|
||||
cn = this._lookupClient(parts[2]);
|
||||
if (cn){
|
||||
cn._onConnect(req, res);
|
||||
} else {
|
||||
req.connection.end();
|
||||
this.options.log('Couldnt find client with session id "' + parts[2] + '"');
|
||||
}
|
||||
} else {
|
||||
this._onConnection(parts[1], req, res, httpUpgrade, head);
|
||||
}
|
||||
return true;
|
||||
});
|
||||
|
||||
this.server.addListener('upgrade', function(req, socket, head){
|
||||
if (!self.check(req, socket, true, head)){
|
||||
socket.destroy();
|
||||
}
|
||||
return false;
|
||||
},
|
||||
});
|
||||
|
||||
_lookupClient: function(sid){
|
||||
return this.clientsIndex[sid];
|
||||
},
|
||||
|
||||
_onClientConnect: function(client){
|
||||
if (!(client instanceof Client) || !client.sessionId){
|
||||
return this.options.log('Invalid client');
|
||||
}
|
||||
client.i = this.clients.length;
|
||||
this.clients.push(client);
|
||||
this.clientsIndex[client.sessionId] = client;
|
||||
this.options.log('Client '+ client.sessionId +' connected');
|
||||
this.emit('clientConnect', client);
|
||||
},
|
||||
|
||||
_onClientMessage: function(data, client){
|
||||
this.emit('clientMessage', data, client);
|
||||
},
|
||||
|
||||
_onClientDisconnect: function(client){
|
||||
this.clientsIndex[client.sessionId] = null;
|
||||
this.clients[client.i] = null;
|
||||
this.options.log('Client '+ client.sessionId +' disconnected');
|
||||
this.emit('clientDisconnect', client);
|
||||
},
|
||||
|
||||
// new connections (no session id)
|
||||
_onConnection: function(transport, req, res, httpUpgrade, head){
|
||||
if (this.options.transports.indexOf(transport) === -1 || (httpUpgrade && !transports[transport].httpUpgrade)){
|
||||
httpUpgrade ? res.destroy() : req.connection.destroy();
|
||||
return this.options.log('Illegal transport "'+ transport +'"');
|
||||
}
|
||||
this.options.log('Initializing client with transport "'+ transport +'"');
|
||||
new transports[transport](this, req, res, this.options.transportOptions[transport], head);
|
||||
for (var i in transports){
|
||||
if ('init' in transports[i]) transports[i].init(this);
|
||||
}
|
||||
|
||||
});
|
||||
|
||||
this.options.log('socket.io ready - accepting connections');
|
||||
};
|
||||
|
||||
sys.inherits(Listener, process.EventEmitter);
|
||||
for (var i in options) Listener.prototype[i] = options[i];
|
||||
|
||||
Listener.prototype.broadcast = function(message, except){
|
||||
for (var i = 0, l = this.clients.length; i < l; i++){
|
||||
if (this.clients[i] && (!except || [].concat(except).indexOf(this.clients[i].sessionId) == -1)){
|
||||
this.clients[i].send(message);
|
||||
}
|
||||
}
|
||||
return this;
|
||||
};
|
||||
|
||||
Listener.prototype.check = function(req, res, httpUpgrade, head){
|
||||
var path = url.parse(req.url).pathname, parts, cn;
|
||||
if (path.indexOf('/' + this.options.resource) === 0){
|
||||
parts = path.substr(1).split('/');
|
||||
if (parts[2]){
|
||||
cn = this._lookupClient(parts[2]);
|
||||
if (cn){
|
||||
cn._onConnect(req, res);
|
||||
} else {
|
||||
req.connection.end();
|
||||
this.options.log('Couldnt find client with session id "' + parts[2] + '"');
|
||||
}
|
||||
} else {
|
||||
this._onConnection(parts[1], req, res, httpUpgrade, head);
|
||||
}
|
||||
return true;
|
||||
}
|
||||
return false;
|
||||
};
|
||||
|
||||
Listener.prototype._lookupClient = function(sid){
|
||||
return this.clientsIndex[sid];
|
||||
};
|
||||
|
||||
Listener.prototype._onClientConnect = function(client){
|
||||
if (!(client instanceof Client) || !client.sessionId){
|
||||
return this.options.log('Invalid client');
|
||||
}
|
||||
client.i = this.clients.length;
|
||||
this.clients.push(client);
|
||||
this.clientsIndex[client.sessionId] = client;
|
||||
this.options.log('Client '+ client.sessionId +' connected');
|
||||
this.emit('clientConnect', client);
|
||||
this.emit('connection', client);
|
||||
};
|
||||
|
||||
Listener.prototype._onClientMessage = function(data, client){
|
||||
this.emit('clientMessage', data, client);
|
||||
};
|
||||
|
||||
Listener.prototype._onClientDisconnect = function(client){
|
||||
this.clientsIndex[client.sessionId] = null;
|
||||
this.clients[client.i] = null;
|
||||
this.options.log('Client '+ client.sessionId +' disconnected');
|
||||
this.emit('clientDisconnect', client);
|
||||
};
|
||||
|
||||
Listener.prototype._onConnection = function(transport, req, res, httpUpgrade, head){
|
||||
if (this.options.transports.indexOf(transport) === -1 || (httpUpgrade && !transports[transport].httpUpgrade)){
|
||||
httpUpgrade ? res.destroy() : req.connection.destroy();
|
||||
return this.options.log('Illegal transport "'+ transport +'"');
|
||||
}
|
||||
this.options.log('Initializing client with transport "'+ transport +'"');
|
||||
new transports[transport](this, req, res, this.options.transportOptions[transport], head);
|
||||
};
|
||||
@@ -1,25 +1,39 @@
|
||||
var websocket = require('./websocket').websocket,
|
||||
net = require('net'),
|
||||
listeners = [];
|
||||
var net = require('net'),
|
||||
WebSocket = require('./websocket'),
|
||||
listeners = [],
|
||||
netserver,
|
||||
|
||||
exports.flashsocket = websocket.extend({});
|
||||
exports.flashsocket.httpUpgrade = true;
|
||||
exports.flashsocket.init = function(listener){
|
||||
listeners.push(listener);
|
||||
Flashsocket = module.exports = function(){
|
||||
WebSocket.apply(this, arguments);
|
||||
};
|
||||
|
||||
net.createServer(function(socket){
|
||||
socket.write('<?xml version="1.0"?>\n');
|
||||
socket.write('<!DOCTYPE cross-domain-policy SYSTEM "http://www.macromedia.com/xml/dtds/cross-domain-policy.dtd">\n');
|
||||
socket.write('<cross-domain-policy>\n');
|
||||
require('sys').inherits(Flashsocket, WebSocket);
|
||||
|
||||
listeners.forEach(function(l){
|
||||
[].concat(l.options.origins).forEach(function(origin){
|
||||
var parts = origin.split(':');
|
||||
socket.write('<allow-access-from domain="' + parts[0] + '" to-ports="'+ parts[1] +'"/>\n');
|
||||
});
|
||||
Flashsocket.httpUpgrade = true;
|
||||
|
||||
Flashsocket.init = function(listener){
|
||||
listeners.push(listener);
|
||||
listener.server.on('close', function(){
|
||||
try {
|
||||
netserver.close();
|
||||
} catch(e){}
|
||||
});
|
||||
};
|
||||
|
||||
socket.write('</cross-domain-policy>\n');
|
||||
socket.end();
|
||||
}).listen(843);
|
||||
try {
|
||||
netserver = net.createServer(function(socket){
|
||||
socket.write('<?xml version="1.0"?>\n');
|
||||
socket.write('<!DOCTYPE cross-domain-policy SYSTEM "http://www.macromedia.com/xml/dtds/cross-domain-policy.dtd">\n');
|
||||
socket.write('<cross-domain-policy>\n');
|
||||
|
||||
listeners.forEach(function(l){
|
||||
[].concat(l.options.origins).forEach(function(origin){
|
||||
var parts = origin.split(':');
|
||||
socket.write('<allow-access-from domain="' + parts[0] + '" to-ports="'+ parts[1] +'"/>\n');
|
||||
});
|
||||
});
|
||||
|
||||
socket.write('</cross-domain-policy>\n');
|
||||
socket.end();
|
||||
}).listen(843);
|
||||
} catch(e){}
|
||||
@@ -1,50 +1,48 @@
|
||||
var Client = require('../client').Client,
|
||||
qs = require('querystring');
|
||||
var Client = require('../client'),
|
||||
qs = require('querystring'),
|
||||
|
||||
exports.htmlfile = Client.extend({
|
||||
HTMLFile = module.exports = function(){
|
||||
Client.apply(this, arguments);
|
||||
};
|
||||
|
||||
require('sys').inherits(HTMLFile, Client);
|
||||
|
||||
_onConnect: function(req, res){
|
||||
var self = this, body = '';
|
||||
switch (req.method){
|
||||
case 'GET':
|
||||
this.__super__(req, res);
|
||||
this.request.connection.addListener('close', function(){ self._onClose(); });
|
||||
this.response.useChunkedEncodingByDefault = true;
|
||||
this.response.shouldKeepAlive = true;
|
||||
this.response.writeHead(200, {
|
||||
'Content-Type': 'text/html',
|
||||
'Connection': 'keep-alive',
|
||||
'Transfer-Encoding': 'chunked'
|
||||
});
|
||||
this.response.write('<html><body>' + new Array(244).join(' '));
|
||||
if ('flush' in this.response) this.response.flush();
|
||||
this._payload();
|
||||
this._heartbeatInterval = setInterval(function(){
|
||||
self.response.write('<!-- heartbeat -->');
|
||||
if ('flush' in self.response) self.response.flush();
|
||||
}, this.options.heartbeatInterval);
|
||||
break;
|
||||
|
||||
case 'POST':
|
||||
req.addListener('data', function(message){
|
||||
body += message;
|
||||
});
|
||||
req.addListener('end', function(){
|
||||
try {
|
||||
var msg = qs.parse(body);
|
||||
self._onMessage(msg.data);
|
||||
} catch(e){}
|
||||
res.writeHead(200);
|
||||
res.write('ok');
|
||||
res.end();
|
||||
});
|
||||
break;
|
||||
}
|
||||
},
|
||||
|
||||
_write: function(message){
|
||||
this.response.write('<script>parent.s._('+ message +', document);</script>');
|
||||
if ('flush' in this.response) this.response.flush();
|
||||
HTMLFile.prototype._onConnect = function(req, res){
|
||||
var self = this, body = '';
|
||||
switch (req.method){
|
||||
case 'GET':
|
||||
Client.prototype._onConnect.apply(this, [req, res]);
|
||||
this.request.connection.addListener('close', function(){ self._onClose(); });
|
||||
this.response.useChunkedEncodingByDefault = true;
|
||||
this.response.shouldKeepAlive = true;
|
||||
this.response.writeHead(200, {
|
||||
'Content-Type': 'text/html',
|
||||
'Connection': 'keep-alive',
|
||||
'Transfer-Encoding': 'chunked'
|
||||
});
|
||||
this.response.write('<html><body>' + new Array(244).join(' '));
|
||||
if ('flush' in this.response) this.response.flush();
|
||||
this._payload();
|
||||
break;
|
||||
|
||||
case 'POST':
|
||||
req.addListener('data', function(message){
|
||||
body += message;
|
||||
});
|
||||
req.addListener('end', function(){
|
||||
try {
|
||||
var msg = qs.parse(body);
|
||||
self._onMessage(msg.data);
|
||||
} catch(e){}
|
||||
res.writeHead(200);
|
||||
res.write('ok');
|
||||
res.end();
|
||||
});
|
||||
break;
|
||||
}
|
||||
};
|
||||
|
||||
});
|
||||
HTMLFile.prototype._write = function(message){
|
||||
this.response.write('<script>parent.s._('+ JSON.stringify(message) +', document);</script>'); //json for escaping
|
||||
if ('flush' in this.response) this.response.flush();
|
||||
};
|
||||
@@ -1,106 +1,139 @@
|
||||
var Client = require('../client').Client,
|
||||
var Client = require('../client'),
|
||||
url = require('url'),
|
||||
Buffer = require('buffer').Buffer,
|
||||
crypto = require('crypto');
|
||||
crypto = require('crypto'),
|
||||
|
||||
exports.websocket = Client.extend({
|
||||
|
||||
_onConnect: function(req, socket){
|
||||
var self = this, headers = [];
|
||||
this.request = req;
|
||||
this.connection = socket;
|
||||
this.data = '';
|
||||
|
||||
if (this.request.headers.upgrade !== 'WebSocket' || !this._verifyOrigin(this.request.headers.origin)){
|
||||
this.listener.options.log('WebSocket connection invalid');
|
||||
this.connection.end();
|
||||
}
|
||||
|
||||
this.connection.setTimeout(0);
|
||||
this.connection.setEncoding('utf8');
|
||||
this.connection.setNoDelay(true);
|
||||
WebSocket = module.exports = function(){
|
||||
Client.apply(this, arguments);
|
||||
};
|
||||
|
||||
require('sys').inherits(WebSocket, Client);
|
||||
|
||||
WebSocket.prototype._onConnect = function(req, socket){
|
||||
var self = this, headers = [];
|
||||
this.request = req;
|
||||
this.connection = socket;
|
||||
this.data = '';
|
||||
|
||||
if (this.request.headers.upgrade !== 'WebSocket' || !this._verifyOrigin(this.request.headers.origin)){
|
||||
this.listener.options.log('WebSocket connection invalid');
|
||||
this.connection.end();
|
||||
}
|
||||
|
||||
this.connection.setTimeout(0);
|
||||
this.connection.setEncoding('utf8');
|
||||
this.connection.setNoDelay(true);
|
||||
|
||||
if ('sec-websocket-key1' in this.request.headers){
|
||||
this.draft = 76;
|
||||
}
|
||||
|
||||
if (this.draft == 76){
|
||||
var origin = this.request.headers.origin;
|
||||
|
||||
headers = [
|
||||
'HTTP/1.1 101 Web Socket Protocol Handshake',
|
||||
'Upgrade: WebSocket',
|
||||
'HTTP/1.1 101 WebSocket Protocol Handshake',
|
||||
'Upgrade: WebSocket',
|
||||
'Connection: Upgrade',
|
||||
'Sec-WebSocket-Origin: ' + (origin || 'null'),
|
||||
'Sec-WebSocket-Location: ws://' + this.request.headers.host + this.request.url
|
||||
];
|
||||
|
||||
if ('sec-websocket-protocol' in this.request.headers){
|
||||
headers.push('Sec-WebSocket-Protocol: ' + this.request.headers['sec-websocket-protocol']);
|
||||
}
|
||||
} else {
|
||||
|
||||
headers = [
|
||||
'HTTP/1.1 101 Web Socket Protocol Handshake',
|
||||
'Upgrade: WebSocket',
|
||||
'Connection: Upgrade',
|
||||
'WebSocket-Origin: ' + this.request.headers.origin,
|
||||
'WebSocket-Location: ws://' + this.request.headers.host + this.request.url
|
||||
];
|
||||
|
||||
if ('sec-websocket-key1' in this.request.headers){
|
||||
headers.push(
|
||||
'Sec-WebSocket-Origin: ' + this.request.headers.origin,
|
||||
'Sec-WebSocket-Location: ws://' + this.request.headers.host + this.request.url);
|
||||
}
|
||||
|
||||
this.connection.write(headers.concat('', '').join('\r\n'));
|
||||
this.connection.addListener('end', function(){ self._onClose(); });
|
||||
this.connection.addListener('data', function(data){ self._handle(data); });
|
||||
if (this._proveReception()) {
|
||||
this._payload();
|
||||
}
|
||||
},
|
||||
|
||||
_handle: function(data){
|
||||
var chunk, chunks, chunk_count;
|
||||
this.data += data;
|
||||
chunks = this.data.split('\ufffd');
|
||||
chunk_count = chunks.length - 1;
|
||||
for (var i = 0; i < chunk_count; i++) {
|
||||
chunk = chunks[i];
|
||||
if (chunk[0] !== '\u0000') {
|
||||
this.listener.options.log('Data incorrectly framed by UA. Dropping connection');
|
||||
this.connection.destroy();
|
||||
return false;
|
||||
}
|
||||
this._onMessage(chunk.slice(1));
|
||||
}
|
||||
this.data = chunks[chunks.length - 1];
|
||||
},
|
||||
|
||||
// http://www.whatwg.org/specs/web-apps/current-work/complete/network.html#opening-handshake
|
||||
_proveReception: function(){
|
||||
var k1 = this.request.headers['sec-websocket-key1'],
|
||||
k2 = this.request.headers['sec-websocket-key2'];
|
||||
if (k1 && k2) {
|
||||
var md5 = crypto.createHash('md5');
|
||||
|
||||
[k1, k2].forEach(function(k) {
|
||||
var n = k.replace(/[^\d]/g, ''),
|
||||
spaces = k.replace(/[^ ]/g, '').length,
|
||||
buf = new Buffer(4);
|
||||
if (spaces === 0) {
|
||||
this.listener.options.log('Invalid WebSocket key: "' + k + '". Dropping connection');
|
||||
this.connection.destroy();
|
||||
return false;
|
||||
}
|
||||
|
||||
n /= spaces;
|
||||
buf[3] = n & 0xff;
|
||||
buf[2] = (n >>= 8) & 0xff;
|
||||
buf[1] = (n >>= 8) & 0xff;
|
||||
buf[0] = (n >>= 8) & 0xff;
|
||||
|
||||
md5.update(buf.toString('binary'));
|
||||
});
|
||||
|
||||
md5.update(this.upgradeHead.toString('binary'));
|
||||
this.connection.write(md5.digest('binary'), 'binary');
|
||||
}
|
||||
return true;
|
||||
},
|
||||
|
||||
_write: function(message){
|
||||
try {
|
||||
this.connection.write('\u0000', 'binary');
|
||||
this.connection.write(message, 'utf8');
|
||||
this.connection.write('\uffff', 'binary');
|
||||
this.connection.write(headers.concat('', '').join('\r\n'));
|
||||
} catch(e){
|
||||
this._onClose();
|
||||
}
|
||||
}
|
||||
|
||||
});
|
||||
|
||||
this.connection.addListener('end', function(){
|
||||
self._onClose();
|
||||
});
|
||||
|
||||
this.connection.addListener('data', function(data){
|
||||
self._handle(data);
|
||||
});
|
||||
|
||||
exports.websocket.httpUpgrade = true;
|
||||
if (this._proveReception(headers)) this._payload();
|
||||
};
|
||||
|
||||
WebSocket.prototype._handle = function(data){
|
||||
var chunk, chunks, chunk_count;
|
||||
this.data += data;
|
||||
chunks = this.data.split('\ufffd');
|
||||
chunk_count = chunks.length - 1;
|
||||
for (var i = 0; i < chunk_count; i++){
|
||||
chunk = chunks[i];
|
||||
if (chunk[0] !== '\u0000'){
|
||||
this.listener.options.log('Data incorrectly framed by UA. Dropping connection');
|
||||
this.connection.destroy();
|
||||
return false;
|
||||
}
|
||||
this._onMessage(chunk.slice(1));
|
||||
}
|
||||
this.data = chunks[chunks.length - 1];
|
||||
};
|
||||
|
||||
// http://www.whatwg.org/specs/web-apps/current-work/complete/network.html#opening-handshake
|
||||
WebSocket.prototype._proveReception = function(headers){
|
||||
var k1 = this.request.headers['sec-websocket-key1'],
|
||||
k2 = this.request.headers['sec-websocket-key2'];
|
||||
|
||||
if (k1 && k2){
|
||||
var md5 = crypto.createHash('md5');
|
||||
|
||||
[k1, k2].forEach(function(k){
|
||||
var n = parseInt(k.replace(/[^\d]/g, '')),
|
||||
spaces = k.replace(/[^ ]/g, '').length;
|
||||
|
||||
if (spaces === 0 || n % spaces !== 0){
|
||||
this.listener.options.log('Invalid WebSocket key: "' + k + '". Dropping connection');
|
||||
this.connection.destroy();
|
||||
return false;
|
||||
}
|
||||
|
||||
n /= spaces;
|
||||
|
||||
md5.update(String.fromCharCode(
|
||||
n >> 24 & 0xFF,
|
||||
n >> 16 & 0xFF,
|
||||
n >> 8 & 0xFF,
|
||||
n & 0xFF));
|
||||
});
|
||||
|
||||
md5.update(this.upgradeHead.toString('binary'));
|
||||
|
||||
try {
|
||||
this.connection.write(headers.concat('', '').join('\r\n') + md5.digest('binary'), 'binary');
|
||||
} catch(e){
|
||||
this._onClose();
|
||||
}
|
||||
}
|
||||
|
||||
return true;
|
||||
};
|
||||
|
||||
WebSocket.prototype._write = function(message){
|
||||
try {
|
||||
this.connection.write('\u0000', 'binary');
|
||||
this.connection.write(message, 'utf8');
|
||||
this.connection.write('\uffff', 'binary');
|
||||
} catch(e){
|
||||
this._onClose();
|
||||
}
|
||||
};
|
||||
|
||||
WebSocket.httpUpgrade = true;
|
||||
@@ -1,69 +1,62 @@
|
||||
var Client = require('../client').Client,
|
||||
qs = require('querystring');
|
||||
var Client = require('../client'),
|
||||
qs = require('querystring'),
|
||||
|
||||
exports['xhr-multipart'] = Client.extend({
|
||||
Multipart = module.exports = function(){
|
||||
Client.apply(this, arguments);
|
||||
};
|
||||
|
||||
options: {
|
||||
pingInterval: 7000
|
||||
},
|
||||
|
||||
_pingInterval: null,
|
||||
require('sys').inherits(Multipart, Client);
|
||||
|
||||
_onConnect: function(req, res){
|
||||
var self = this, body = '', headers = {};
|
||||
// https://developer.mozilla.org/En/HTTP_Access_Control
|
||||
if (req.headers.origin && this._verifyOrigin(req.headers.origin)) {
|
||||
headers['Access-Control-Allow-Origin'] = req.headers.origin;
|
||||
headers['Access-Control-Allow-Credentials'] = 'true';
|
||||
}
|
||||
if (typeof req.headers['access-control-request-method'] !== 'undefined') {
|
||||
// CORS preflight message
|
||||
headers['Access-Control-Allow-Methods'] = req.headers['access-control-request-method'];
|
||||
res.writeHead(200, headers);
|
||||
res.write('ok');
|
||||
res.end();
|
||||
return;
|
||||
}
|
||||
switch (req.method){
|
||||
case 'GET':
|
||||
this.__super__(req, res);
|
||||
headers['Content-Type'] = 'multipart/x-mixed-replace;boundary="socketio"';
|
||||
headers['Connection'] = 'keep-alive';
|
||||
this.request.connection.addListener('end', function(){ self._onClose(); });
|
||||
this.response.useChunkedEncodingByDefault = false;
|
||||
this.response.shouldKeepAlive = true;
|
||||
this.response.writeHead(200, headers);
|
||||
this.response.write("--socketio\n");
|
||||
if ('flush' in this.response) this.response.flush();
|
||||
this._payload();
|
||||
this._heartbeatInterval = setInterval(function(){
|
||||
self._write(String.fromCharCode(6));
|
||||
}, this.options.heartbeatInterval);
|
||||
break;
|
||||
|
||||
case 'POST':
|
||||
req.addListener('data', function(message){
|
||||
body += message.toString();
|
||||
});
|
||||
req.addListener('end', function(){
|
||||
try {
|
||||
var msg = qs.parse(body);
|
||||
self._onMessage(msg.data);
|
||||
} catch(e){}
|
||||
res.writeHead(200, headers);
|
||||
res.write('ok');
|
||||
res.end();
|
||||
body = '';
|
||||
});
|
||||
break;
|
||||
}
|
||||
},
|
||||
|
||||
_write: function(message){
|
||||
this.response.write("Content-Type: text/plain" + (message.length === 1 && message.charCodeAt(0) === 6 ? "; charset=us-ascii" : "") + "\n\n");
|
||||
this.response.write(message + "\n");
|
||||
this.response.write("--socketio\n");
|
||||
if ('flush' in this.response) this.response.flush();
|
||||
Multipart.prototype._onConnect = function(req, res){
|
||||
var self = this, body = '', headers = {};
|
||||
// https://developer.mozilla.org/En/HTTP_Access_Control
|
||||
if (req.headers.origin && this._verifyOrigin(req.headers.origin)){
|
||||
headers['Access-Control-Allow-Origin'] = req.headers.origin;
|
||||
headers['Access-Control-Allow-Credentials'] = 'true';
|
||||
}
|
||||
if (typeof req.headers['access-control-request-method'] !== 'undefined'){
|
||||
// CORS preflight message
|
||||
headers['Access-Control-Allow-Methods'] = req.headers['access-control-request-method'];
|
||||
res.writeHead(200, headers);
|
||||
res.write('ok');
|
||||
res.end();
|
||||
return;
|
||||
}
|
||||
switch (req.method){
|
||||
case 'GET':
|
||||
Client.prototype._onConnect.apply(this, [req, res]);
|
||||
headers['Content-Type'] = 'multipart/x-mixed-replace;boundary="socketio"';
|
||||
headers['Connection'] = 'keep-alive';
|
||||
this.request.connection.addListener('end', function(){ self._onClose(); });
|
||||
this.response.useChunkedEncodingByDefault = false;
|
||||
this.response.shouldKeepAlive = true;
|
||||
this.response.writeHead(200, headers);
|
||||
this.response.write("--socketio\n");
|
||||
if ('flush' in this.response) this.response.flush();
|
||||
this._payload();
|
||||
break;
|
||||
|
||||
case 'POST':
|
||||
req.addListener('data', function(message){
|
||||
body += message.toString();
|
||||
});
|
||||
req.addListener('end', function(){
|
||||
try {
|
||||
var msg = qs.parse(body);
|
||||
self._onMessage(msg.data);
|
||||
} catch(e){}
|
||||
res.writeHead(200, headers);
|
||||
res.write('ok');
|
||||
res.end();
|
||||
body = '';
|
||||
});
|
||||
break;
|
||||
}
|
||||
};
|
||||
|
||||
});
|
||||
Multipart.prototype._write = function(message){
|
||||
this.response.write("Content-Type: text/plain" + (message.length === 1 && message.charCodeAt(0) === 6 ? "; charset=us-ascii" : "") + "\n\n");
|
||||
this.response.write(message + "\n");
|
||||
this.response.write("--socketio\n");
|
||||
if ('flush' in this.response) this.response.flush();
|
||||
};
|
||||
@@ -1,57 +1,52 @@
|
||||
var Client = require('../client').Client,
|
||||
qs = require('querystring');
|
||||
var Client = require('../client'),
|
||||
qs = require('querystring'),
|
||||
|
||||
exports['xhr-polling'] = Client.extend({
|
||||
|
||||
options: {
|
||||
closeTimeout: 5000,
|
||||
duration: 20000
|
||||
},
|
||||
|
||||
_onConnect: function(req, res){
|
||||
var self = this, body = '';
|
||||
switch (req.method){
|
||||
case 'GET':
|
||||
this.__super__(req, res);
|
||||
this._closeTimeout = setTimeout(function(){
|
||||
self._write('');
|
||||
}, this.options.duration);
|
||||
this._payload();
|
||||
break;
|
||||
|
||||
case 'POST':
|
||||
req.addListener('data', function(message){
|
||||
body += message;
|
||||
});
|
||||
req.addListener('end', function(){
|
||||
try {
|
||||
var msg = qs.parse(body);
|
||||
self._onMessage(msg.data);
|
||||
} catch(e){}
|
||||
res.writeHead(200);
|
||||
res.write('ok');
|
||||
res.end();
|
||||
});
|
||||
break;
|
||||
}
|
||||
},
|
||||
|
||||
_write: function(message){
|
||||
if (this._closeTimeout) {
|
||||
clearTimeout(this._closeTimeout);
|
||||
}
|
||||
var headers = {'Content-Type': 'text/plain', 'Content-Length': message.length};
|
||||
// https://developer.mozilla.org/En/HTTP_Access_Control
|
||||
if (this.request.headers.origin && this._verifyOrigin(this.request.headers.origin)) {
|
||||
headers['Access-Control-Allow-Origin'] = this.request.headersorigin;
|
||||
if (this.request.headers.cookie) {
|
||||
headers['Access-Control-Allow-Credentials'] = 'true';
|
||||
}
|
||||
}
|
||||
this.response.writeHead(200, headers);
|
||||
this.response.write(message);
|
||||
this.response.end();
|
||||
this._onClose();
|
||||
Polling = module.exports = function(){
|
||||
Client.apply(this, arguments);
|
||||
};
|
||||
|
||||
require('sys').inherits(Polling, Client);
|
||||
|
||||
Polling.prototype._onConnect = function(req, res){
|
||||
var self = this, body = '';
|
||||
switch (req.method){
|
||||
case 'GET':
|
||||
Client.prototype._onConnect.apply(this, [req, res]);
|
||||
this.request.connection.addListener('end', function(){ self._onClose(); });
|
||||
this._closeTimeout = setTimeout(function(){
|
||||
self._write('');
|
||||
}, this.options.duration);
|
||||
this._payload();
|
||||
break;
|
||||
|
||||
case 'POST':
|
||||
req.addListener('data', function(message){
|
||||
body += message;
|
||||
});
|
||||
req.addListener('end', function(){
|
||||
try {
|
||||
// optimization: just strip first 5 characters here?
|
||||
var msg = qs.parse(body);
|
||||
self._onMessage(msg.data);
|
||||
} catch(e){}
|
||||
res.writeHead(200);
|
||||
res.write('ok');
|
||||
res.end();
|
||||
});
|
||||
break;
|
||||
}
|
||||
};
|
||||
|
||||
});
|
||||
Polling.prototype._write = function(message){
|
||||
if (this._closeTimeout) clearTimeout(this._closeTimeout);
|
||||
var headers = {'Content-Type': 'text/plain', 'Content-Length': message.length};
|
||||
// https://developer.mozilla.org/En/HTTP_Access_Control
|
||||
if (this.request.headers.origin && this._verifyOrigin(this.request.headers.origin)){
|
||||
headers['Access-Control-Allow-Origin'] = this.request.headersorigin;
|
||||
if (this.request.headers.cookie) headers['Access-Control-Allow-Credentials'] = 'true';
|
||||
}
|
||||
this.response.writeHead(200, headers);
|
||||
this.response.write(message);
|
||||
this.response.end();
|
||||
this._onClose();
|
||||
};
|
||||
@@ -1,20 +0,0 @@
|
||||
// Based on Mixin.js from MooTools (MIT)
|
||||
// Copyright (c) 2006-2009 Valerio Proietti, <http://mad4milk.net/>
|
||||
|
||||
exports.flatten = function(arr){
|
||||
var array = [];
|
||||
for (var i = 0, l = arr.length; i < l; i++){
|
||||
var item = arr[i];
|
||||
if (item !== null) {
|
||||
array = array.concat(item instanceof Array ? array.flatten(item) : item);
|
||||
}
|
||||
}
|
||||
return array;
|
||||
};
|
||||
|
||||
exports.include = function(arr, item){
|
||||
if (arr.indexOf(item) === -1) {
|
||||
arr.push(item);
|
||||
}
|
||||
return arr;
|
||||
};
|
||||
@@ -1,51 +0,0 @@
|
||||
// Based on Mixin.js from MooTools (MIT)
|
||||
// Copyright (c) 2006-2009 Valerio Proietti, <http://mad4milk.net/>
|
||||
|
||||
exports.clone = function(item) {
|
||||
var cloned;
|
||||
if (item instanceof Array){
|
||||
cloned = [];
|
||||
for (var i = 0; i < item.length; i++) {
|
||||
cloned[i] = exports.clone(item[i]);
|
||||
}
|
||||
return cloned;
|
||||
}
|
||||
|
||||
if (typeof item === 'object') {
|
||||
cloned = {};
|
||||
for (var key in item) {
|
||||
cloned[key] = exports.clone(item[key]);
|
||||
}
|
||||
return cloned;
|
||||
}
|
||||
|
||||
return item;
|
||||
};
|
||||
|
||||
var mergeOne = function(source, key, current){
|
||||
if (current instanceof Array){
|
||||
source[key] = exports.clone(current);
|
||||
} else if (typeof current === 'object'){
|
||||
if (typeof source[key] === 'object') {
|
||||
exports.merge(source[key], current);
|
||||
} else {
|
||||
source[key] = exports.clone(current);
|
||||
}
|
||||
} else {
|
||||
source[key] = current;
|
||||
}
|
||||
return source;
|
||||
};
|
||||
|
||||
exports.merge = function(source, k, v){
|
||||
if (typeof k === 'string') {
|
||||
return mergeOne(source, k, v);
|
||||
}
|
||||
for (var i = 1, l = arguments.length; i < l; i++){
|
||||
var object = arguments[i];
|
||||
for (var key in object) {
|
||||
mergeOne(source, key, object[key]);
|
||||
}
|
||||
}
|
||||
return source;
|
||||
};
|
||||
@@ -1,35 +0,0 @@
|
||||
// Based on Mixin.js from MooTools (MIT)
|
||||
// Copyright (c) 2006-2009 Valerio Proietti, <http://mad4milk.net/>
|
||||
var object = require('./object'), sys = require('sys');
|
||||
|
||||
exports.options = {
|
||||
|
||||
options: {},
|
||||
|
||||
setOption: function(key, value){
|
||||
object.merge(this.options, key, value);
|
||||
return this;
|
||||
},
|
||||
|
||||
setOptions: function(options){
|
||||
for (var key in options) {
|
||||
this.setOption(key, options[key]);
|
||||
}
|
||||
if (this.addListener){
|
||||
var first_lower = function(full, first){
|
||||
return first.toLowerCase();
|
||||
};
|
||||
|
||||
// Automagically register callbacks if the varname starts with on
|
||||
for (var i in this.options){
|
||||
if (!(/^on[A-Z]/).test(i) || typeof this.options[i] !== 'function') {
|
||||
continue;
|
||||
}
|
||||
this.addListener(i.replace(/^on([A-Z])/, first_lower), this.options[i]);
|
||||
this.options[i] = null;
|
||||
}
|
||||
}
|
||||
return this;
|
||||
}
|
||||
|
||||
};
|
||||
10
lib/socket.io/utils.js
Normal file
10
lib/socket.io/utils.js
Normal file
@@ -0,0 +1,10 @@
|
||||
exports.options = {
|
||||
options: function(options, merge){
|
||||
this.options = exports.merge(options || {}, merge || {});
|
||||
}
|
||||
};
|
||||
|
||||
exports.merge = function(source, merge){
|
||||
for (var i in merge) source[i] = merge[i];
|
||||
return source;
|
||||
};
|
||||
1
lib/vendor/js-oo
vendored
1
lib/vendor/js-oo
vendored
Submodule lib/vendor/js-oo deleted from 1f94bd8979
Submodule test/client deleted from fec3a888e0
28
tests/client.test.js
Normal file
28
tests/client.test.js
Normal file
@@ -0,0 +1,28 @@
|
||||
var listener = require('socket.io/listener'),
|
||||
Client = require('socket.io/client');
|
||||
|
||||
module.exports = {
|
||||
'test decoding': function(assert){
|
||||
var client = new Client(listener, {}, {}),
|
||||
decoded = client._decode('~m~5~m~abcde' + '~m~9~m~123456789');
|
||||
assert.equal(decoded.length, 2);
|
||||
assert.equal(decoded[0], 'abcde');
|
||||
assert.equal(decoded[1], '123456789');
|
||||
},
|
||||
|
||||
'test decoding of bad framed messages': function(assert){
|
||||
var client = new Client(listener, {}, {}),
|
||||
decoded = client._decode('~m~5~m~abcde' + '~m\uffsdaasdfd9~m~1aaa23456789');
|
||||
assert.equal(decoded.length, 1);
|
||||
assert.equal(decoded[0], 'abcde');
|
||||
assert.equal(decoded[1], undefined);
|
||||
},
|
||||
|
||||
'test encoding': function(assert){
|
||||
var client = new Client(listener, {}, {});
|
||||
assert.equal(client._encode(['abcde', '123456789']), '~m~5~m~abcde' + '~m~9~m~123456789');
|
||||
assert.equal(client._encode('asdasdsad'), '~m~9~m~asdasdsad');
|
||||
assert.equal(client._encode(''), '~m~0~m~');
|
||||
assert.equal(client._encode(null), '~m~0~m~');
|
||||
}
|
||||
};
|
||||
52
tests/io.test.js
Normal file
52
tests/io.test.js
Normal file
@@ -0,0 +1,52 @@
|
||||
var io = require('./../'),
|
||||
Listener = io.Listener,
|
||||
Client = require('./../lib/socket.io/client'),
|
||||
WebSocket = require('./support/node-websocket-client/lib/websocket').WebSocket,
|
||||
empty = new Function,
|
||||
port = 8080,
|
||||
|
||||
create = function(fn){
|
||||
var server = require('http').createServer(empty), client;
|
||||
server.listen(port, function(){
|
||||
client = new WebSocket('ws://localhost:'+ port++ +'/socket.io/websocket', 'borf');
|
||||
});
|
||||
return {server: server, client: client, close: function(){
|
||||
client.close();
|
||||
server.close();
|
||||
}};
|
||||
};
|
||||
|
||||
module.exports = {
|
||||
'test server initialization': function(assert){
|
||||
var http = create(),
|
||||
sio = io.listen(http.server);
|
||||
assert.ok(sio instanceof Listener);
|
||||
http.close();
|
||||
},
|
||||
|
||||
'test connection and handshake': function(assert){
|
||||
var server = require('http').createServer(empty),
|
||||
sio = io.listen(server),
|
||||
client,
|
||||
clientCount = 0,
|
||||
|
||||
close = function(){
|
||||
client.close();
|
||||
server.close();
|
||||
assert.ok(clientCount, 1);
|
||||
};
|
||||
|
||||
server.listen(port, function(){
|
||||
client = new WebSocket('ws://localhost:'+ port++ +'/socket.io/websocket', 'borf');
|
||||
client.onmessage = function(){
|
||||
console.log('test');
|
||||
};
|
||||
});
|
||||
|
||||
sio.on('connection', function(client){
|
||||
console.log('test');
|
||||
clientCount++;
|
||||
assert.ok(client instanceof Client);
|
||||
});
|
||||
}
|
||||
};
|
||||
1
tests/support/expresso
Submodule
1
tests/support/expresso
Submodule
Submodule tests/support/expresso added at fe1071999a
1
tests/support/node-websocket-client
Submodule
1
tests/support/node-websocket-client
Submodule
Submodule tests/support/node-websocket-client added at 9d14baa24d
Reference in New Issue
Block a user