Compare commits

...

44 Commits
0.1 ... 0.4

Author SHA1 Message Date
Guillermo Rauch
57c8d37d47 Implemented heartbeats 2010-06-26 15:04:55 -07:00
Guillermo Rauch
5e329229b4 Fix for flush in htmlfile 2010-06-25 14:57:57 -07:00
Guillermo Rauch
ea36d24d9e Updated Socket.IO client 2010-06-18 03:02:51 -03:00
Guillermo Rauch
d79de5aa56 Multipart fix for Node 0.1.98 2010-06-18 02:38:08 -03:00
Guillermo Rauch
06769c4ca7 Object fixes 2010-06-01 16:19:29 -03:00
Christiaan
df876e37df New changes passing JSlint as much as possible again 2010-06-01 21:05:20 +02:00
Christiaan
31d119740c Merge branch 'master' of http://github.com/LearnBoost/Socket.IO-node
Conflicts:
	lib/socket.io/client.js
	lib/socket.io/listener.js
	lib/socket.io/transports/websocket.js
	lib/socket.io/transports/xhr-multipart.js
	lib/socket.io/transports/xhr-polling.js

Conflicts Resolved
2010-06-01 20:54:13 +02:00
Guillermo Rauch
f3c28e298a Cross domain support:
XHR-Polling
 XHR-Multipart
WebSocket support for Webkit nightly / Chromium nightly
Added heartbeats to multipart
2010-06-01 08:01:11 -03:00
Brian
71d80181fa Fixed multipart streaming so that it works properly again (especially for cross-domain requests). Tested on Firefox 3.6.4. 2010-05-30 22:52:28 -07:00
Brian
0e483a05a5 Moved the cross-site origin checking to the client class, added cross-domain support to xhr-* transports, added ping functionality for the xhr-multipart transport, and fixed the listener class where the log function that can be overridden was not being referenced everywhere. 2010-05-30 22:52:16 -07:00
Christiaan
c1fca0c520 Almost fully passing JSlint 2010-05-29 15:21:46 +02:00
Visnu Pitiyanuvath
1918b75360 implement the sec-websocket-key handshake for latest chrome and webkit builds
http://www.whatwg.org/specs/web-apps/current-work/complete/network.html#opening-handshake
2010-05-28 11:15:55 -07:00
Christiaan
5e255ccdf9 Use exports instead of this as exports is the official way according to the commonjs spec 2010-05-28 19:37:46 +02:00
Christiaan
9a306fddab variables which are no constructor shouldn't start with a uppercase letter 2010-05-28 19:24:38 +02:00
Christiaan
dc593e9972 Use EventEmitter public API instead of the private _events array 2010-05-28 19:22:54 +02:00
Guillermo Rauch
7f5228abc3 Fixed syntax error -.- 2010-05-28 06:12:31 -03:00
Guillermo Rauch
0ad237ddf5 Fix for stream not writable problems 2010-05-28 05:28:25 -03:00
Guillermo Rauch
3134940ed6 Updated io.Socket initialization to work on hosts other than localhost
Updated client
2010-05-27 21:45:01 -03:00
Guillermo Rauch
0f783bc06a Fixed htmlfile
- end > close for detecting closed connections
 - Added hearbeats
 - added flush() in write
2010-05-27 21:33:07 -03:00
Virtuo
dd3d829173 Correct bug due to variable name clash.
IMO: this kind of code should be put in an external lib and 100% tested.
2010-05-21 13:09:14 +02:00
Guillermo Rauch
fbce6379a5 Note about submodules 2010-05-13 22:05:31 -03:00
Guillermo Rauch
0459c95848 Added support for Node 0.1.94
Close timeout for transports with keep-alive / websocket set to 0
Close timeout for xhr-polling reduced from 8 seconds to 5 seconds
Now you can pass options to specific transports from io.listen()
Some cleanup
2010-05-12 12:16:44 -03:00
Guillermo Rauch
67815ec15d http > git in gitmodules 2010-05-11 19:34:25 -03:00
Guillermo Rauch
87f62373c5 Client updated 2010-04-30 09:02:11 -03:00
Guillermo Rauch
a557bd6acb Updated client to 0.2.0 2010-04-30 08:55:09 -03:00
Guillermo Rauch
ae9e550c26 More support for disconnections 2010-04-30 08:48:17 -03:00
Guillermo Rauch
b19bac2d33 Updated Readme 2010-04-30 08:36:15 -03:00
Guillermo Rauch
a977ecd65b Fixes for htmlfile, multipart, polling
Removed patches
Removed server events
2010-04-30 08:35:34 -03:00
Guillermo Rauch
c4cf1b28f2 Updated JSON decoding 2010-04-30 08:12:08 -03:00
Guillermo Rauch
f5796c630c OCD-related work 2010-04-30 08:10:25 -03:00
Guillermo Rauch
6ce8c52c4d Updated README
Updated copyright
Updated project locations
2010-04-30 08:09:23 -03:00
Guillermo Rauch
408b2c73f8 Fixed encoding issues for WebSocket transport 2010-04-30 08:07:57 -03:00
Guillermo Rauch
16b5de7b4f All instances of 'close' now 'end' 2010-04-30 07:04:15 -03:00
Guillermo Rauch
b65e6fa611 'tcp' is now 'net' 2010-04-30 07:01:08 -03:00
Tobias Schneider
ddfc39826f Repository for submodule RosePad/Socket.IO doesn't exist anymore, use LearnBoost/Socket.IO instead. 2010-04-27 20:13:47 +02:00
Guillermo Rauch
1745e89936 Client update 2010-04-03 18:50:50 -07:00
Guillermo Rauch
10e6b66a97 Updating client 2010-04-03 18:31:34 -07:00
Guillermo Rauch
bf9ba8c08b Fixed encoding for WebSocket (set to utf-8 per spec)
Fixed handling of chunks sent by UA
2010-04-03 18:16:19 -07:00
Guillermo Rauch
4e58204b83 Updated client 2010-04-01 19:46:02 -07:00
Guillermo Rauch
4308ba8f14 Test client updated 2010-04-01 17:45:47 -07:00
Guillermo Rauch
0e5fb040a9 JSON try/catch (fixes #3) 2010-04-01 17:45:11 -07:00
Guillermo Rauch
e62370161d Clarified what versions of Node it's been tested on 2010-03-24 17:31:57 -07:00
Dan Loewenherz
5c5b7ee0ba update Socket.IO URL to be universally accessible 2010-03-24 17:15:22 -07:00
Guillermo Rauch
4db40d9ecc 0.1.33 patch
Removed server check to keep compatibility with 0.1.32 (process.http is now accessed through process.binding('http'))
2010-03-23 11:03:20 -07:00
17 changed files with 384 additions and 376 deletions

2
.gitmodules vendored
View File

@@ -3,4 +3,4 @@
url = git://github.com/visionmedia/js-oo.git
[submodule "test/client"]
path = test/client
url = git@github.com:RosePad/Socket.IO.git
url = git://github.com/LearnBoost/Socket.IO.git

View File

@@ -4,7 +4,6 @@ Socket.IO Server: Sockets for the rest of us
The `Socket.IO` server provides seamless supports for a variety of transports intended for realtime communication
- WebSocket (with Flash policy support)
- Server-Sent Events
- XHR Polling
- XHR Multipart Streaming
- Forever Iframe
@@ -12,8 +11,8 @@ The `Socket.IO` server provides seamless supports for a variety of transports in
Requirements
------------
- Node v0.1.32+
- [Socket.IO client](http://github.com/RosePad/Socket.IO) to connect from the browser
- Node v0.1.94+
- [Socket.IO client](http://github.com/LearnBoost/Socket.IO) to connect from the browser
How to use
----------
@@ -35,23 +34,17 @@ By default, the server will intercept requests that contain `socket.io` in the p
// socket.io, I choose you
io.listen(server);
Due to a lack of flexibility in the current Node HTTP server implementation, you'll have to patch Node before using `socket.io`.
In the node directory run:
patch -p1 < {../directory/to/socket.io-node}/patch/{node version}.patch
./configure
make
make test
sudo make install
On the client side, you should use the [Socket.IO client](https://github.com/RosePad/Socket.IO) to connect.
On the client side, you should use the [Socket.IO client](https://github.com/LearnBoost/Socket.IO) to connect.
## Checking out
## Notes
After cloning the repository, remember to run
IMPORTANT! When checking out the git repo, make sure to include the submodules. One way to do it is:
git submodule init
git submodule update
git clone [repo] --recursive
Another, once cloned
git submodule update --init --recursive
## Demo
@@ -115,11 +108,9 @@ Options:
A list of the accepted transports.
- *timeout*
8000
Time it has to pass without a reconnection to consider a client disconnected. Applies to all transports.
- *transportOptions*
An object of options to pass to each transport. For example `{ websocket: { closeTimeout: 8000 }}`
- *log*
@@ -181,7 +172,7 @@ One of the design goals is that you should be able to implement whatever protoco
The concept of session also benefits naturally full-duplex WebSocket, in the event of an accidental disconnection and a quick reconnection. Messages that the server intends to deliver to the client are cached temporarily until the reconnection.
The implementation of reconnection logic (potentially with retries) is left for the user.
The implementation of reconnection logic (potentially with retries) is left for the user. By default, transports that are keep-alive or open all the time (like WebSocket) have a timeout of 0 if a disconnection is detected.
* Message batching
@@ -191,15 +182,13 @@ Despite this extra layer, your messages are delivered unaltered to the different
## Credits
Guillermo Rauch [guillermo@rosepad.com]
Special thanks to [Jonas Pfenniger](http://github.com/zimbatm) for his workaround patch to keep the HTTPConnection open after the request is successful.
Guillermo Rauch &lt;guillermo@learnboost.com&gt;
## License
(The MIT License)
Copyright (c) 2009 RosePad &lt;dev@rosepad.com&gt;
Copyright (c) 2010 LearnBoost &lt;dev@learnboost.com&gt;
Permission is hereby granted, free of charge, to any person obtaining
a copy of this software and associated documentation files (the

View File

@@ -1,39 +1,60 @@
var sys = require('sys');
var options = require('./util/options').options, urlparse = require('url').parse;
this.Client = Class({
exports.Client = Class({
include: [options],
options: {
closeTimeout: 0,
heartbeatInterval: 5000
},
init: function(listener, req, res){
this.listener = listener;
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);
if (!this.connected || !(this.connection.readyState === 'open' ||
this.connection.readyState === 'writeOnly')) {
return this._queue(message);
}
this._write(JSON.stringify({messages: [message]}));
return this;
},
broadcast: function(message){
if (!('sessionId' in this)) return this;
if (!('sessionId' in this)) {
return this;
}
this.listener.broadcast(message, this.sessionId);
return this;
},
_onMessage: function(data){
var messages = JSON.parse(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){
var self = this;
var self = this;
this.request = req;
this.response = res;
this.connection = this.request.connection;
if (this._disconnectTimeout) clearTimeout(this._disconnectTimeout);
this.connection = this.request.connection;
if (this._disconnectTimeout) {
clearTimeout(this._disconnectTimeout);
}
},
_payload: function(){
@@ -53,16 +74,23 @@ this.Client = Class({
payload = payload.concat(this._writeQueue || []);
this._writeQueue = [];
if (payload.length) this._write(JSON.stringify({messages: payload}));
if (this.connections == 1) this.listener._onClientConnect(this);
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);
}
this.connected = false;
this._disconnectTimeout = setTimeout(function(){
self._onDisconnect();
}, this.listener.options.timeout);
}, this.options.closeTimeout);
},
_onDisconnect: function(){
@@ -70,20 +98,34 @@ this.Client = Class({
this._writeQueue = [];
this.connected = false;
this.finalized = true;
if (this.handshaked) this.listener._onClientDisconnect(this);
if (this.handshaked) {
this.listener._onClientDisconnect(this);
}
}
},
_queue: function(message){
if (!('_writeQueue' in this)) this._writeQueue = [];
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');
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;
}
});

View File

@@ -1,52 +1,50 @@
var url = require('url'),
sys = require('sys'),
Options = require('./util/options').Options,
Client = require('./client').Client,
Transports = {},
Listener = this.Listener = Class({
options = require('./util/options').options,
Client = require('./client').Client,
transports = {};
var Listener = exports.Listener = Class({
include: [process.EventEmitter.prototype, Options],
include: [process.EventEmitter.prototype, options],
options: {
origins: '*:*',
resource: 'socket.io',
transports: ['websocket', 'server-events', 'flashsocket', 'htmlfile', 'xhr-multipart', 'xhr-polling'],
timeout: 8000,
transports: ['websocket', 'flashsocket', 'htmlfile', 'xhr-multipart', 'xhr-polling'],
transportOptions: {},
log: function(message){
sys.log(message);
require('sys').log(message);
}
},
init: function(server, options){
var self = this;
process.EventEmitter.call(this);
this.server = server;
this.setOptions(options);
this.server = server;
this.setOptions(options);
this.clients = [];
this.clientsIndex = {};
if (!(this.server instanceof process.http.Server)){
throw new Error('Please pass the result of http.createServer() to the listener');
}
var listeners = this.server.listeners('request');
this.server.removeAllListeners('request');
var listener = (this.server._events['request'] instanceof Array)
? this.server._events['request'][0]
: this.server._events['request'];
if (listener){
var self = this;
this.server._events['request'] = function(req, res){
if (self.check(req, res)) return;
listener(req, res);
};
} else {
throw new Error('Couldn\'t find the `request` event in the HTTP server.');
}
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);
if (!(t in transports)){
transports[t] = require('./transports/' + t)[t];
if (transports[t].init) transports[t].init(this);
}
}, this);
@@ -57,13 +55,13 @@ Listener = this.Listener = Class({
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;
},
check: function(req, res){
var path = url.parse(req.url).pathname, parts, cn;
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]){
@@ -71,14 +69,14 @@ Listener = this.Listener = Class({
if (cn){
cn._onConnect(req, res);
} else {
req.connection.close();
sys.log('Couldnt find client with session id "' + parts[2] + '"');
req.connection.end();
this.options.log('Couldnt find client with session id "' + parts[2] + '"');
}
} else {
this._onConnection(parts[1], req, res);
}
this._onConnection(parts[1], req, res, httpUpgrade, head);
}
return true;
}
}
return false;
},
@@ -88,12 +86,12 @@ Listener = this.Listener = Class({
_onClientConnect: function(client){
if (!(client instanceof Client) || !client.sessionId){
return sys.log('Invalid client');
return this.options.log('Invalid client');
}
client.i = this.clients.length;
this.clients.push(client);
this.clientsIndex[client.sessionId] = client;
sys.log('Client '+ client.sessionId +' connected');
this.options.log('Client '+ client.sessionId +' connected');
this.emit('clientConnect', client);
},
@@ -104,18 +102,18 @@ Listener = this.Listener = Class({
_onClientDisconnect: function(client){
this.clientsIndex[client.sessionId] = null;
this.clients[client.i] = null;
sys.log('Client '+ client.sessionId +' disconnected');
this.options.log('Client '+ client.sessionId +' disconnected');
this.emit('clientDisconnect', client);
},
// new connections (no session id)
_onConnection: function(transport, req, res){
if (this.options.transports.indexOf(transport) === -1){
req.connection.close();
return sys.log('Illegal transport "'+ transport +'"');
_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 +'"');
}
sys.log('Initializing client with transport "'+ transport +'"');
new Transports[transport](this, req, res);
this.options.log('Initializing client with transport "'+ transport +'"');
new transports[transport](this, req, res, this.options.transportOptions[transport], head);
}
});
});

View File

@@ -1,14 +1,14 @@
var websocket = require('./websocket').websocket,
tcp = require('tcp'),
net = require('net'),
listeners = [];
this.flashsocket = websocket.extend({});
this.flashsocket.init = function(listener){
exports.flashsocket = websocket.extend({});
exports.flashsocket.httpUpgrade = true;
exports.flashsocket.init = function(listener){
listeners.push(listener);
};
tcp.createServer(function(socket){
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');
@@ -21,5 +21,5 @@ tcp.createServer(function(socket){
});
socket.write('</cross-domain-policy>\n');
socket.close();
}).listen(843);
socket.end();
}).listen(843);

View File

@@ -1,46 +1,50 @@
var Client = require('../client').Client,
qs = require('querystring');
this['htmlfile'] = Client.extend({
exports.htmlfile = Client.extend({
_onConnect: function(req, res){
var self = this, body = '';
switch (req.method){
case 'GET':
var self = this;
this.__super__(req, res);
this.request.addListener('end', function(){
if (!('hijack' in self.connection)){
throw new Error('You have to patch Node! Please refer to the README');
}
self.connection.addListener('end', function(){ self._onClose(); });
self.connection.hijack();
self.connection.setTimeout(0);
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.writeHead(200, { 'Content-type': 'text/html' });
this.response.flush();
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(message);
var msg = qs.parse(body);
self._onMessage(msg.data);
} catch(e){}
} catch(e){}
res.writeHead(200);
res.write('ok');
res.close();
res.end();
});
break;
}
},
_write: function(message){
// not sure if this is enough escaping. looks lousy
this.response.write("<script>parent.callback('"+ message.replace(/'/, "\'") +"')</script>");
this.response.write('<script>parent.s._('+ message +', document);</script>');
if ('flush' in this.response) this.response.flush();
}
});

View File

@@ -1,46 +0,0 @@
var Client = require('../client').Client,
qs = require('querystring');
this['server-events'] = Client.extend({
_onConnect: function(req, res){
switch (req.method){
case 'GET':
var self = this;
this.__super__(req, res);
this.request.addListener('end', function(){
if (!('hijack' in self.connection)){
throw new Error('You have to patch Node! Please refer to the README');
}
self.connection.addListener('end', function(){ self._onClose(); });
self.connection.hijack();
self.connection.setTimeout(0);
});
this.response.writeHead(200, { 'Content-type': 'application/x-dom-event-stream' });
this.response.flush();
this._payload();
break;
case 'POST':
req.addListener('data', function(message){
try {
var msg = qs.parse(message);
self._onMessage(msg.data);
} catch(e){}
res.writeHead(200);
res.write('ok');
res.close();
});
break;
}
},
_write: function(message){
this.response.write("Event: socket.io");
this.response.write("data: " + message);
}
});

View File

@@ -1,61 +1,111 @@
var Client = require('../client').Client,
url = require('url'),
sys = require('sys');
url = require('url'),
Buffer = require('buffer').Buffer,
crypto = require('crypto');
this.websocket = Client.extend({
exports.websocket = Client.extend({
_onConnect: function(req, res){
var self = this;
this.__super__(req, res);
_onConnect: function(req, socket){
var self = this, headers = [];
this.request = req;
this.connection = socket;
this.data = '';
if (this.request.headers['connection'] !== 'Upgrade'
|| this.request.headers['upgrade'] !== 'WebSocket'
|| !this._verifyOrigin(this.request.headers['origin'])){
if (this.request.headers.upgrade !== 'WebSocket' || !this._verifyOrigin(this.request.headers.origin)){
this.listener.options.log('WebSocket connection invalid');
this.connection.close();
return;
this.connection.end();
}
this.request.addListener('end', function(){
if (!('hijack' in self.connection)){
throw new Error('You have to patch Node! Please refer to the README');
this.connection.setTimeout(0);
this.connection.setEncoding('utf8');
this.connection.setNoDelay(true);
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();
}
setInterval(function(){
self._write(JSON.stringify({heartbeat: '1'}));
}, 10000);
},
_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];
},
self.connection.hijack();
self.connection.setTimeout(0);
self.connection.setNoDelay(true);
self.connection.addListener('end', function(){ self._onClose(); });
self.connection.addListener('data', function(data){
if (data[0] !== '\u0000' && data[data.length - 1] !== '\ufffd'){
self.connection.close();
} else {
self._onMessage(data.substr(1, data.length - 2));
// 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;
}
});
});
this.response.use_chunked_encoding_by_default = false;
this.response.writeHeader(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
});
this.response.flush();
this._payload();
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;
},
_verifyOrigin: function(origin){
var parts = url.parse(origin);
return this.listener.options.origins.indexOf('*:*') !== -1
|| this.listener.options.origins.indexOf(parts.host + ':' + parts.port) !== -1
|| this.listener.options.origins.indexOf(parts.host + ':*') !== -1
|| this.listener.options.origins.indexOf('*:' + parts.port) !== -1;
},
_write: function(message){
this.connection.write('\u0000' + message + '\uffff');
try {
this.connection.write('\u0000', 'binary');
this.connection.write(message, 'utf8');
this.connection.write('\uffff', 'binary');
} catch(e){
this._onClose();
}
}
});
});
exports.websocket.httpUpgrade = true;

View File

@@ -1,54 +1,69 @@
var Client = require('../client').Client,
qs = require('querystring');
qs = require('querystring');
this['xhr-multipart'] = Client.extend({
exports['xhr-multipart'] = Client.extend({
options: {
pingInterval: 7000
},
_pingInterval: null,
_onConnect: function(req, res){
var self = this;
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':
var self = this;
this.__super__(req, res);
this.request.addListener('end', function(){
if (!('hijack' in self.connection)){
throw new Error('You have to patch Node! Please refer to the README');
}
self.connection.addListener('end', function(){ self._onClose(); });
self.connection.hijack();
self.connection.setTimeout(0);
});
this.response.use_chunked_encoding_by_default = false;
this.response.writeHead(200, {
'Content-Type': 'multipart/x-mixed-replace;boundary=socketio',
'Connection': 'keep-alive'
});
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");
this.response.flush();
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(message);
var msg = qs.parse(body);
self._onMessage(msg.data);
} catch(e){}
res.writeHead(200);
} catch(e){}
res.writeHead(200, headers);
res.write('ok');
res.close();
res.end();
body = '';
});
break;
}
},
_write: function(message){
this.response.write("Content-Type: text/plain\n\n");
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");
this.response.flush();
if ('flush' in this.response) this.response.flush();
}
});

View File

@@ -1,43 +1,56 @@
var Client = require('../client').Client,
qs = require('querystring'),
sys = require('sys');
qs = require('querystring');
this['xhr-polling'] = Client.extend({
exports['xhr-polling'] = Client.extend({
options: {
closeTimeout: 5000,
duration: 20000
},
_onConnect: function(req, res){
var self = this;
var self = this, body = '';
switch (req.method){
case 'GET':
this.__super__(req, res);
this.__super__(req, res);
this._closeTimeout = setTimeout(function(){
self._write('');
}, this.options.duration);
}, this.options.duration);
this._payload();
break;
case 'POST':
req.addListener('data', function(message){
body += message;
});
req.addListener('end', function(){
try {
var msg = qs.parse(message);
var msg = qs.parse(body);
self._onMessage(msg.data);
} catch(e){}
} catch(e){}
res.writeHead(200);
res.write('ok');
res.close();
res.end();
});
break;
}
},
_write: function(message){
if (this._closeTimeout) clearTimeout(this._closeTimeout);
this.response.writeHead(200, {'Content-Type': 'text/plain', 'Content-Length': message.length});
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.close();
this.response.end();
this._onClose();
}

View File

@@ -1,16 +1,20 @@
// Based on Mixin.js from MooTools (MIT)
// Copyright (c) 2006-2009 Valerio Proietti, <http://mad4milk.net/>
this.flatten = function(arr){
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);
if (item !== null) {
array = array.concat(item instanceof Array ? array.flatten(item) : item);
}
}
return array;
};
this.include = function(arr, item){
if (arr.indexOf(item) == -1) arr.push(item);
exports.include = function(arr, item){
if (arr.indexOf(item) === -1) {
arr.push(item);
}
return arr;
};

View File

@@ -1,38 +1,51 @@
// Based on Mixin.js from MooTools (MIT)
// Copyright (c) 2006-2009 Valerio Proietti, <http://mad4milk.net/>
var clone = this.clone = function(item){
var clone;
exports.clone = function(item) {
var cloned;
if (item instanceof Array){
clone = [];
for (var i = 0; i < item.length; i++) clone[i] = clone(item[i]);
return clone;
} else if (typeof item == 'object') {
clone = {};
for (var key in object) clone[key] = clone(object[key]);
return clone;
} else {
return item;
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;
};
mergeOne = function(source, key, current){
var mergeOne = function(source, key, current){
if (current instanceof Array){
source[key] = clone(current);
} else if (typeof current == 'object'){
if (typeof source[key] == 'object') object.merge(source[key], current);
else source[key] = clone(current);
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;
};
this.merge = function(source, k, v){
if (typeof k == 'string') return mergeOne(source, k, v);
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]);
for (var key in object) {
mergeOne(source, key, object[key]);
}
}
return source;
};
};

View File

@@ -2,7 +2,7 @@
// Copyright (c) 2006-2009 Valerio Proietti, <http://mad4milk.net/>
var object = require('./object'), sys = require('sys');
this.Options = {
exports.options = {
options: {},
@@ -12,13 +12,20 @@ this.Options = {
},
setOptions: function(options){
for (var key in options) this.setOption(key, options[key]);
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])/, function(full, first){
return first.toLowerCase();
}), this.options[i]);
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;
}
}

View File

@@ -1,83 +0,0 @@
diff -rup node-v0.1.32-orig/src/node_http.cc node-v0.1.32/src/node_http.cc
--- node-v0.1.32-orig/src/node_http.cc 2010-03-13 13:14:00.000000000 -0800
+++ node-v0.1.32/src/node_http.cc 2010-03-13 13:23:48.000000000 -0800
@@ -57,6 +57,7 @@ HTTPConnection::Initialize (Handle<Objec
client_constructor_template->InstanceTemplate()->SetInternalFieldCount(1);
client_constructor_template->SetClassName(String::NewSymbol("Client"));
NODE_SET_PROTOTYPE_METHOD(client_constructor_template, "resetParser", ResetParser);
+ NODE_SET_PROTOTYPE_METHOD(client_constructor_template, "hijack", Hijack);
target->Set(String::NewSymbol("Client"), client_constructor_template->GetFunction());
t = FunctionTemplate::New(NewServer);
@@ -64,6 +65,7 @@ HTTPConnection::Initialize (Handle<Objec
server_constructor_template->Inherit(Connection::constructor_template);
server_constructor_template->InstanceTemplate()->SetInternalFieldCount(1);
NODE_SET_PROTOTYPE_METHOD(server_constructor_template, "resetParser", ResetParser);
+ NODE_SET_PROTOTYPE_METHOD(server_constructor_template, "hijack", Hijack);
server_constructor_template->SetClassName(String::NewSymbol("ServerSideConnection"));
end_symbol = NODE_PSYMBOL("end");
@@ -101,6 +103,14 @@ Handle<Value> HTTPConnection::ResetParse
}
+Handle<Value> HTTPConnection::Hijack(const Arguments& args) {
+ HandleScope scope;
+ HTTPConnection *connection = ObjectWrap::Unwrap<HTTPConnection>(args.Holder());
+ connection->Hijack();
+ return Undefined();
+}
+
+
void
HTTPConnection::OnReceive (const void *buf, size_t len)
{
@@ -109,6 +119,11 @@ HTTPConnection::OnReceive (const void *b
assert(refs_);
size_t nparsed;
+ if (hijacked) {
+ Connection::OnReceive(buf, len);
+ return;
+ }
+
nparsed = http_parser_execute(&parser_, static_cast<const char*>(buf), len);
if (nparsed != len) {
diff -rup node-v0.1.32-orig/src/node_http.h node-v0.1.32/src/node_http.h
--- node-v0.1.32-orig/src/node_http.h 2010-03-13 13:14:00.000000000 -0800
+++ node-v0.1.32/src/node_http.h 2010-03-13 13:25:05.000000000 -0800
@@ -12,17 +12,21 @@ public:
static void Initialize (v8::Handle<v8::Object> target);
static v8::Persistent<v8::FunctionTemplate> client_constructor_template;
- static v8::Persistent<v8::FunctionTemplate> server_constructor_template;
+ static v8::Persistent<v8::FunctionTemplate> server_constructor_template;
protected:
static v8::Handle<v8::Value> NewClient (const v8::Arguments& args);
static v8::Handle<v8::Value> NewServer (const v8::Arguments& args);
static v8::Handle<v8::Value> ResetParser(const v8::Arguments& args);
+ static v8::Handle<v8::Value> Hijack(const v8::Arguments& args);
+
+ bool hijacked;
HTTPConnection (enum http_parser_type t)
: Connection()
{
type_ = t;
+ hijacked = false;
ResetParser();
}
@@ -41,6 +45,10 @@ protected:
parser_.data = this;
}
+ void Hijack() {
+ hijacked = true;
+ }
+
void OnReceive (const void *buf, size_t len);
void OnEOF ();

View File

@@ -25,10 +25,10 @@
document.getElementById('text').value = '';
}
var socket = new io.Socket('localhost', {rememberTransport: false, port: 8080});
var socket = new io.Socket(null, {rememberTransport: false, port: 8080});
socket.connect();
socket.addEvent('message', function(data){
var obj = io.util.JSON.decode(data);
var obj = JSON.parse(data);
if ('buffer' in obj){
document.getElementById('form').style.display='block';

View File

@@ -7,7 +7,7 @@ var http = require('http'),
send404 = function(res){
res.writeHead(404);
res.write('404');
res.close();
res.end();
},
server = http.createServer(function(req, res){
@@ -17,16 +17,16 @@ server = http.createServer(function(req, res){
case '/':
res.writeHead(200, {'Content-Type': 'text/html'});
res.write('<h1>Welcome. Try the <a href="/chat.html">chat</a> example.</h1>');
res.close();
res.end();
break;
default:
if (/\.(js|html|swf)$/.test(path)){
try {
var swf = path.substr(-4) == '.swf';
res.writeHead(200, {'Content-Type': swf ? 'application/x-shockwave-flash' : ('text/' + (path.substr(-3) == '.js' ? 'javascript' : 'html'))});
var swf = path.substr(-4) === '.swf';
res.writeHead(200, {'Content-Type': swf ? 'application/x-shockwave-flash' : ('text/' + (path.substr(-3) === '.js' ? 'javascript' : 'html'))});
res.write(fs.readFileSync(__dirname + path, swf ? 'binary' : 'utf8'), swf ? 'binary' : 'utf8');
res.close();
res.end();
} catch(e){
send404(res);
}
@@ -58,7 +58,9 @@ io.listen(server, {
onClientMessage: function(message, client){
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));
}