mirror of
https://github.com/socketio/socket.io.git
synced 2026-04-30 03:00:39 -04:00
Initial commit (some transports untested!)
This commit is contained in:
6
.gitmodules
vendored
Normal file
6
.gitmodules
vendored
Normal file
@@ -0,0 +1,6 @@
|
||||
[submodule "lib/vendor/js-oo"]
|
||||
path = lib/vendor/js-oo
|
||||
url = git://github.com/visionmedia/js-oo.git
|
||||
[submodule "test/client"]
|
||||
path = test/client
|
||||
url = git@github.com:RosePad/Socket.IO-client.git
|
||||
214
README.md
Normal file
214
README.md
Normal file
@@ -0,0 +1,214 @@
|
||||
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
|
||||
|
||||
Requirements
|
||||
------------
|
||||
|
||||
- Node v0.1.32+
|
||||
- [Socket.IO-client](http://github.com/RosePad/Socket.IO-client) to connect on the client side.
|
||||
|
||||
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'),
|
||||
|
||||
server = http.createServer(function(req, res){
|
||||
// your normal server code
|
||||
res.writeHeader(200, {'Content-Type': 'text/html'});
|
||||
res.writeBody('<h1>Hello world</h1>');
|
||||
res.finish();
|
||||
});
|
||||
|
||||
// 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 [Socket.IO-client](https://github.com/RosePad/Socket.IO-client) to connect.
|
||||
|
||||
## Demo
|
||||
|
||||
To run the demo, go to `test` directory and run
|
||||
|
||||
sudo node server.js
|
||||
|
||||
and point your browser to http://localhost:8080. In addition to 8080, if the transport `flashsocket` is enabled, a server will be initialized to listen to requests on the port 843.
|
||||
|
||||
## Documentation
|
||||
|
||||
### Listener
|
||||
|
||||
io.listen(<http.Server>, [options])
|
||||
|
||||
Returns: a `Listener` instance
|
||||
|
||||
Public Properties:
|
||||
|
||||
- *server*
|
||||
|
||||
The instance of _process.http.Server_
|
||||
|
||||
- *options*
|
||||
|
||||
The passed in options combined with the defaults
|
||||
|
||||
- *clients*
|
||||
|
||||
An array of clients. Important: disconnected clients are set to null, the array is not spliced.
|
||||
|
||||
- *clientsIndex*
|
||||
|
||||
An object of clients indexed by their session ids.
|
||||
|
||||
Methods:
|
||||
|
||||
- *addListener(event, λ)*
|
||||
|
||||
Adds a listener for the specified event. Optionally, you can pass it as an option to `io.listen`, prefixed by `on`. For example: `onClientConnect: function(){}`
|
||||
|
||||
- *removeListener(event, λ)*
|
||||
|
||||
Remove a listener from the listener array for the specified event.
|
||||
|
||||
- *broadcast(message, [except])*
|
||||
|
||||
Broadcasts a message to all clients. There's an optional second argument which is an array of session ids or a single session id to avoid broadcasting to.
|
||||
|
||||
Options:
|
||||
|
||||
- *resource*
|
||||
|
||||
socket.io
|
||||
|
||||
The resource is what allows the `socket.io` server to identify incoming connections by `socket.io` clients. Make sure they're in sync.
|
||||
|
||||
- *transports*
|
||||
|
||||
['websocket', 'server-events', 'flashsocket', 'htmlfile', 'xhr-multipart', 'xhr-polling']
|
||||
|
||||
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.
|
||||
|
||||
- *log*
|
||||
|
||||
ƒ(){ sys.log }
|
||||
|
||||
The logging function. Defaults to outputting to stdout through `sys.log`
|
||||
|
||||
Events:
|
||||
|
||||
- *clientConnect(client)*
|
||||
|
||||
Fired when a client is connected. Receives the Client instance as parameter
|
||||
|
||||
- *clientMessage(message, client)*
|
||||
|
||||
Fired when a message from a client is received. Receives the message and Client instance as parameter
|
||||
|
||||
- *clientDisconnect(client)*
|
||||
|
||||
Fired when a client is disconnected. Receives the Client instance as parameter
|
||||
|
||||
Important note: `this` in the event listener refers to the `Listener` instance.
|
||||
|
||||
### Client
|
||||
|
||||
Client(listener, req, res)
|
||||
|
||||
Public Properties:
|
||||
|
||||
- *listener*
|
||||
|
||||
The `Listener` instance this client belongs to.
|
||||
|
||||
- *connected*
|
||||
|
||||
Whether the client is connected
|
||||
|
||||
- *connections*
|
||||
|
||||
Number of times the client connected
|
||||
|
||||
Methods:
|
||||
|
||||
- *send(message)*
|
||||
|
||||
Sends a message to the client
|
||||
|
||||
- *broadcast(message)*
|
||||
|
||||
Sends a message to all other clients. Equivalent to Listener::broadcast(message, client.sessionId)
|
||||
|
||||
## Protocol
|
||||
|
||||
One of the design goals is that you should be able to implement whatever protocol you desire without `Socket.IO` getting in the way. `Socket.IO` has a minimal, unobtrusive protocol layer. It consists of two parts:
|
||||
|
||||
* Connection handshake
|
||||
|
||||
This is required to simulate a full duplex socket with transports such as XHR Polling or Server-sent Events (which is a "one-way socket"). The basic idea is that the first message received from the server will be a JSON object that contains a session id that will be used for further communication exchanged between the client and the server.
|
||||
|
||||
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.
|
||||
|
||||
* 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.
|
||||
|
||||
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.
|
||||
|
||||
## 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.
|
||||
|
||||
## License
|
||||
|
||||
(The MIT License)
|
||||
|
||||
Copyright (c) 2009 RosePad <dev@rosepad.com>
|
||||
|
||||
Permission is hereby granted, free of charge, to any person obtaining
|
||||
a copy of this software and associated documentation files (the
|
||||
'Software'), to deal in the Software without restriction, including
|
||||
without limitation the rights to use, copy, modify, merge, publish,
|
||||
distribute, sublicense, and/or sell copies of the Software, and to
|
||||
permit persons to whom the Software is furnished to do so, subject to
|
||||
the following conditions:
|
||||
|
||||
The above copyright notice and this permission notice shall be
|
||||
included in all copies or substantial portions of the Software.
|
||||
|
||||
THE SOFTWARE IS PROVIDED 'AS IS', WITHOUT WARRANTY OF ANY KIND,
|
||||
EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF
|
||||
MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT.
|
||||
IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY
|
||||
CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION OF CONTRACT,
|
||||
TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION WITH THE
|
||||
SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE.
|
||||
8
lib/socket.io.js
Normal file
8
lib/socket.io.js
Normal file
@@ -0,0 +1,8 @@
|
||||
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);
|
||||
};
|
||||
91
lib/socket.io/client.js
Normal file
91
lib/socket.io/client.js
Normal file
@@ -0,0 +1,91 @@
|
||||
var sys = require('sys');
|
||||
|
||||
this.Client = Class({
|
||||
|
||||
init: function(listener, req, res){
|
||||
this.listener = listener;
|
||||
this.connections = 0;
|
||||
this.connected = false;
|
||||
this._onConnect(req, res);
|
||||
},
|
||||
|
||||
send: function(message){
|
||||
if (!this.connected || !(this.connection.readyState == 'open' || this.connection.readyState == 'writeOnly')) return this._queue(message);
|
||||
sys.log('will deliver ' + message + ' to ' + this.sessionId);
|
||||
this._write(JSON.stringify({messages: [message]}));
|
||||
return this;
|
||||
},
|
||||
|
||||
broadcast: function(message){
|
||||
if (!('sessionId' in this)) return this;
|
||||
this.listener.broadcast(message, this.sessionId);
|
||||
return this;
|
||||
},
|
||||
|
||||
_onMessage: function(data){
|
||||
var messages = JSON.parse(data);
|
||||
for (var i = 0, l = messages.length; i < l; i++){
|
||||
this.listener._onClientMessage(messages[i], this);
|
||||
}
|
||||
},
|
||||
|
||||
_onConnect: function(req, res){
|
||||
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;
|
||||
this.connected = false;
|
||||
this._disconnectTimeout = setTimeout(function(){
|
||||
sys.log('timeout!');
|
||||
self._onDisconnect();
|
||||
}, this.listener.options.timeout);
|
||||
},
|
||||
|
||||
_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;
|
||||
}
|
||||
|
||||
});
|
||||
121
lib/socket.io/listener.js
Normal file
121
lib/socket.io/listener.js
Normal file
@@ -0,0 +1,121 @@
|
||||
var url = require('url'),
|
||||
sys = require('sys'),
|
||||
Options = require('./util/options').Options,
|
||||
Client = require('./client').Client,
|
||||
Transports = {},
|
||||
|
||||
Listener = this.Listener = Class({
|
||||
|
||||
include: [process.EventEmitter.prototype, Options],
|
||||
|
||||
options: {
|
||||
origins: '*:*',
|
||||
resource: 'socket.io',
|
||||
transports: ['websocket', 'server-events', 'flashsocket', 'htmlfile', 'xhr-multipart', 'xhr-polling'],
|
||||
timeout: 8000,
|
||||
log: function(message){
|
||||
sys.log(message);
|
||||
}
|
||||
},
|
||||
|
||||
init: function(server, options){
|
||||
process.EventEmitter.call(this);
|
||||
|
||||
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 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.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);
|
||||
}
|
||||
}
|
||||
return this;
|
||||
},
|
||||
|
||||
check: function(req, res){
|
||||
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.close();
|
||||
sys.log('Couldnt find client with session id "' + parts[2] + '"');
|
||||
}
|
||||
} else {
|
||||
this._onConnection(parts[1], req, res);
|
||||
}
|
||||
return true;
|
||||
}
|
||||
return false;
|
||||
},
|
||||
|
||||
_lookupClient: function(sid){
|
||||
return this.clientsIndex[sid];
|
||||
},
|
||||
|
||||
_onClientConnect: function(client){
|
||||
if (!(client instanceof Client) || !client.sessionId){
|
||||
return sys.log('Invalid client');
|
||||
}
|
||||
client.i = this.clients.length;
|
||||
this.clients.push(client);
|
||||
this.clientsIndex[client.sessionId] = client;
|
||||
sys.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;
|
||||
sys.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 +'"');
|
||||
}
|
||||
sys.log('Initializing client with transport "'+ transport +'"');
|
||||
new Transports[transport](this, req, res);
|
||||
}
|
||||
|
||||
});
|
||||
25
lib/socket.io/transports/flashsocket.js
Normal file
25
lib/socket.io/transports/flashsocket.js
Normal file
@@ -0,0 +1,25 @@
|
||||
var websocket = require('./websocket').websocket,
|
||||
tcp = require('tcp'),
|
||||
listeners = [];
|
||||
|
||||
this.flashsocket = websocket.extend({});
|
||||
|
||||
this.flashsocket.init = function(listener){
|
||||
listeners.push(listener);
|
||||
};
|
||||
|
||||
tcp.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.close();
|
||||
}).listen(843);
|
||||
46
lib/socket.io/transports/htmlfile.js
Normal file
46
lib/socket.io/transports/htmlfile.js
Normal file
@@ -0,0 +1,46 @@
|
||||
var Client = require('../client').Client,
|
||||
qs = require('querystring');
|
||||
|
||||
this['htmlfile'] = 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': 'text/html' });
|
||||
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){
|
||||
// not sure if this is enough escaping. looks lousy
|
||||
this.response.write("<script>parent.callback('"+ message.replace(/'/, "\'") +"')</script>");
|
||||
}
|
||||
|
||||
});
|
||||
45
lib/socket.io/transports/server-events.js
Normal file
45
lib/socket.io/transports/server-events.js
Normal file
@@ -0,0 +1,45 @@
|
||||
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\ndata: " + message);
|
||||
}
|
||||
|
||||
});
|
||||
61
lib/socket.io/transports/websocket.js
Normal file
61
lib/socket.io/transports/websocket.js
Normal file
@@ -0,0 +1,61 @@
|
||||
var Client = require('../client').Client,
|
||||
url = require('url'),
|
||||
sys = require('sys');
|
||||
|
||||
this.websocket = Client.extend({
|
||||
|
||||
_onConnect: function(req, res){
|
||||
var self = this;
|
||||
this.__super__(req, res);
|
||||
|
||||
if (this.request.headers['connection'] !== 'Upgrade'
|
||||
|| this.request.headers['upgrade'] !== 'WebSocket'
|
||||
|| !this._verifyOrigin(this.request.headers['origin'])){
|
||||
this.listener.options.log('WebSocket connection invalid');
|
||||
this.connection.close();
|
||||
return;
|
||||
}
|
||||
|
||||
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.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));
|
||||
}
|
||||
});
|
||||
});
|
||||
|
||||
// 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();
|
||||
},
|
||||
|
||||
_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');
|
||||
}
|
||||
|
||||
});
|
||||
51
lib/socket.io/transports/xhr-multipart.js
Normal file
51
lib/socket.io/transports/xhr-multipart.js
Normal file
@@ -0,0 +1,51 @@
|
||||
var Client = require('../client').Client,
|
||||
qs = require('querystring');
|
||||
|
||||
this['xhr-multipart'] = Client.extend({
|
||||
|
||||
_onConnect: function(req, res){
|
||||
var self = this;
|
||||
switch (req.method){
|
||||
case 'GET':
|
||||
var self = this;
|
||||
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);
|
||||
self.response.use_chunked_encoding_by_default = false;
|
||||
});
|
||||
|
||||
this.response.writeHead(200, {
|
||||
'Content-type': 'multipart/x-mixed-replace;boundary=socketio'
|
||||
});
|
||||
this.response.write("--socketio\r\n");
|
||||
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("Content-type: text/plain\r\n");
|
||||
this.response.write(message);
|
||||
this.response.write("\r\n--socketio\r\n");
|
||||
}
|
||||
|
||||
});
|
||||
44
lib/socket.io/transports/xhr-polling.js
Normal file
44
lib/socket.io/transports/xhr-polling.js
Normal file
@@ -0,0 +1,44 @@
|
||||
var Client = require('../client').Client,
|
||||
qs = require('querystring'),
|
||||
sys = require('sys');
|
||||
|
||||
this['xhr-polling'] = Client.extend({
|
||||
|
||||
options: {
|
||||
duration: 20000
|
||||
},
|
||||
|
||||
_onConnect: function(req, res){
|
||||
var self = this;
|
||||
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){
|
||||
try {
|
||||
var msg = qs.parse(message);
|
||||
self._onMessage(msg.data);
|
||||
} catch(e){}
|
||||
res.writeHead(200);
|
||||
res.write('ok');
|
||||
res.close();
|
||||
});
|
||||
break;
|
||||
}
|
||||
},
|
||||
|
||||
_write: function(message){
|
||||
if (this._closeTimeout) clearTimeout(this._closeTimeout);
|
||||
this.response.writeHead(200, {'Content-Type': 'text/plain', 'Content-Length': message.length});
|
||||
this.response.write(message);
|
||||
this.response.close();
|
||||
this._onClose();
|
||||
}
|
||||
|
||||
});
|
||||
16
lib/socket.io/util/array.js
Normal file
16
lib/socket.io/util/array.js
Normal file
@@ -0,0 +1,16 @@
|
||||
// Based on Mixin.js from MooTools (MIT)
|
||||
// Copyright (c) 2006-2009 Valerio Proietti, <http://mad4milk.net/>
|
||||
|
||||
this.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;
|
||||
};
|
||||
|
||||
this.include = function(arr, item){
|
||||
if (arr.indexOf(item) == -1) arr.push(item);
|
||||
return arr;
|
||||
};
|
||||
38
lib/socket.io/util/object.js
Normal file
38
lib/socket.io/util/object.js
Normal file
@@ -0,0 +1,38 @@
|
||||
// Based on Mixin.js from MooTools (MIT)
|
||||
// Copyright (c) 2006-2009 Valerio Proietti, <http://mad4milk.net/>
|
||||
|
||||
var clone = this.clone = function(item){
|
||||
var clone;
|
||||
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;
|
||||
}
|
||||
},
|
||||
|
||||
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);
|
||||
} else {
|
||||
source[key] = current;
|
||||
}
|
||||
return source;
|
||||
};
|
||||
|
||||
this.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;
|
||||
};
|
||||
28
lib/socket.io/util/options.js
Normal file
28
lib/socket.io/util/options.js
Normal file
@@ -0,0 +1,28 @@
|
||||
// Based on Mixin.js from MooTools (MIT)
|
||||
// Copyright (c) 2006-2009 Valerio Proietti, <http://mad4milk.net/>
|
||||
var object = require('./object'), sys = require('sys');
|
||||
|
||||
this.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){
|
||||
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]);
|
||||
this.options[i] = null;
|
||||
}
|
||||
}
|
||||
return this;
|
||||
}
|
||||
|
||||
};
|
||||
1
lib/vendor/js-oo
vendored
Submodule
1
lib/vendor/js-oo
vendored
Submodule
Submodule lib/vendor/js-oo added at 1f94bd8979
83
patch/0.1.32.patch
Normal file
83
patch/0.1.32.patch
Normal file
@@ -0,0 +1,83 @@
|
||||
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 ();
|
||||
|
||||
77
test/chat.html
Normal file
77
test/chat.html
Normal file
@@ -0,0 +1,77 @@
|
||||
<!doctype html>
|
||||
<html>
|
||||
<head>
|
||||
<title>socket.io client test</title>
|
||||
|
||||
<script src="/client/lib/vendor/js-oo/lib/oo.js"></script>
|
||||
<script src="/client/lib/vendor/LABjs/LAB.src.js"></script>
|
||||
<script src="/client/lib/io.js"></script>
|
||||
<script src="/client/lib/util/util.js"></script>
|
||||
<script src="/client/lib/util/object.js"></script>
|
||||
<script src="/client/lib/util/array.js"></script>
|
||||
<script src="/client/lib/util/options.js"></script>
|
||||
<script src="/client/lib/util/events.js"></script>
|
||||
<script src="/client/lib/util/json.js"></script>
|
||||
<script src="/client/lib/transport.js"></script>
|
||||
<script src="/client/lib/transports/xhr.js"></script>
|
||||
<script src="/client/lib/transports/websocket.js"></script>
|
||||
<script src="/client/lib/transports/flashsocket.js"></script>
|
||||
<script src="/client/lib/transports/htmlfile.js"></script>
|
||||
<script src="/client/lib/transports/server-events.js"></script>
|
||||
<script src="/client/lib/transports/xhr-multipart.js"></script>
|
||||
<script src="/client/lib/transports/xhr-polling.js"></script>
|
||||
<script src="/client/lib/socket.js"></script>
|
||||
</head>
|
||||
<body>
|
||||
|
||||
<script>
|
||||
io.path = '/client/';
|
||||
|
||||
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];
|
||||
document.getElementById('chat').appendChild(el);
|
||||
document.getElementById('chat').scrollTop = 1000000;
|
||||
}
|
||||
|
||||
function send(){
|
||||
var val = document.getElementById('text').value;
|
||||
socket.send(val);
|
||||
message({ message: ['you', val] });
|
||||
document.getElementById('text').value = '';
|
||||
}
|
||||
|
||||
var socket = new io.Socket('localhost', {rememberTransport: false, transports:['xhr-polling'], port: 8080});
|
||||
socket.connect();
|
||||
socket.addEvent('message', function(data){
|
||||
var obj = io.util.JSON.decode(data);
|
||||
|
||||
if ('buffer' in obj){
|
||||
document.getElementById('form').style.display='block';
|
||||
document.getElementById('chat').innerHTML = '';
|
||||
|
||||
for (var i in obj.buffer) message(obj.buffer[i]);
|
||||
} else message(obj);
|
||||
});
|
||||
</script>
|
||||
|
||||
<h1>Sample chat client</h1>
|
||||
<div id="chat"><p>Connecting...</p></div>
|
||||
<form id="form" onsubmit="send(); return false">
|
||||
<input type="text" autocomplete="off" id="text"><input type="submit" value="Send">
|
||||
</form>
|
||||
|
||||
<style>
|
||||
#chat { height: 300px; overflow: auto; width: 800px; border: 1px solid #eee; font: 13px Helvetica, Arial; }
|
||||
#chat p { padding: 8px; margin: 0; }
|
||||
#chat p:nth-child(odd) { background: #F6F6F6; }
|
||||
#form { width: 782px; background: #333; padding: 5px 10px; display: none; }
|
||||
#form input[type=text] { width: 700px; padding: 5px; background: #fff; border: 1px solid #fff; }
|
||||
#form input[type=submit] { cursor: pointer; background: #999; border: none; padding: 6px 8px; -moz-border-radius: 8px; -webkit-border-radius: 8px; margin-left: 5px; text-shadow: 0 1px 0 #fff; }
|
||||
#form input[type=submit]:hover { background: #A2A2A2; }
|
||||
#form input[type=submit]:active { position: relative; top: 2px; }
|
||||
</style>
|
||||
|
||||
</body>
|
||||
</html>
|
||||
1
test/client
Submodule
1
test/client
Submodule
Submodule test/client added at 042b4d7f9a
65
test/server.js
Normal file
65
test/server.js
Normal file
@@ -0,0 +1,65 @@
|
||||
var http = require('http'),
|
||||
url = require('url'),
|
||||
fs = require('fs'),
|
||||
io = require('../lib/socket.io'),
|
||||
sys = require('sys'),
|
||||
|
||||
send404 = function(res){
|
||||
res.writeHead(404);
|
||||
res.write('404');
|
||||
res.close();
|
||||
},
|
||||
|
||||
server = http.createServer(function(req, res){
|
||||
// your normal server code
|
||||
var path = url.parse(req.url).pathname;
|
||||
switch (path){
|
||||
case '/':
|
||||
res.writeHead(200, {'Content-Type': 'text/html'});
|
||||
res.write('<h1>Welcome. Try the <a href="/chat.html">chat</a> example.</h1>');
|
||||
res.close();
|
||||
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'))});
|
||||
res.write(fs.readFileSync(__dirname + path, swf ? 'binary' : 'utf8'), swf ? 'binary' : 'utf8');
|
||||
res.close();
|
||||
} catch(e){
|
||||
send404(res);
|
||||
}
|
||||
break;
|
||||
}
|
||||
|
||||
send404(res);
|
||||
break;
|
||||
}
|
||||
});
|
||||
|
||||
server.listen(8080);
|
||||
|
||||
// socket.io, I choose you
|
||||
// simplest chat application evar
|
||||
var buffer = [], json = JSON.stringify;
|
||||
|
||||
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){
|
||||
var msg = { message: [client.sessionId, message] };
|
||||
buffer.push(msg);
|
||||
if (buffer.length > 15) buffer.shift();
|
||||
client.broadcast(json(msg));
|
||||
}
|
||||
|
||||
});
|
||||
Reference in New Issue
Block a user