commit c8edad861a1b920470a3ebe45b3ae64fed6bdd75 Author: Guillermo Rauch Date: Tue Mar 16 17:53:52 2010 -0700 Initial commit (some transports untested!) diff --git a/.gitmodules b/.gitmodules new file mode 100644 index 00000000..a6bd65fe --- /dev/null +++ b/.gitmodules @@ -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 diff --git a/README.md b/README.md new file mode 100644 index 00000000..553c3153 --- /dev/null +++ b/README.md @@ -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('

Hello world

'); + 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(, [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. \ No newline at end of file diff --git a/lib/socket.io.js b/lib/socket.io.js new file mode 100644 index 00000000..5ee8a085 --- /dev/null +++ b/lib/socket.io.js @@ -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); +}; \ No newline at end of file diff --git a/lib/socket.io/client.js b/lib/socket.io/client.js new file mode 100644 index 00000000..9ec7a31a --- /dev/null +++ b/lib/socket.io/client.js @@ -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; + } + +}); \ No newline at end of file diff --git a/lib/socket.io/listener.js b/lib/socket.io/listener.js new file mode 100644 index 00000000..56bd2e76 --- /dev/null +++ b/lib/socket.io/listener.js @@ -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); + } + +}); \ No newline at end of file diff --git a/lib/socket.io/transports/flashsocket.js b/lib/socket.io/transports/flashsocket.js new file mode 100644 index 00000000..7f8f347c --- /dev/null +++ b/lib/socket.io/transports/flashsocket.js @@ -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('\n'); + socket.write('\n'); + socket.write('\n'); + + listeners.forEach(function(l){ + [].concat(l.options.origins).forEach(function(origin){ + var parts = origin.split(':'); + socket.write('\n'); + }); + }); + + socket.write('\n'); + socket.close(); +}).listen(843); \ No newline at end of file diff --git a/lib/socket.io/transports/htmlfile.js b/lib/socket.io/transports/htmlfile.js new file mode 100644 index 00000000..bd54f073 --- /dev/null +++ b/lib/socket.io/transports/htmlfile.js @@ -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(""); + } + +}); \ No newline at end of file diff --git a/lib/socket.io/transports/server-events.js b/lib/socket.io/transports/server-events.js new file mode 100644 index 00000000..76deacaa --- /dev/null +++ b/lib/socket.io/transports/server-events.js @@ -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); + } + +}); \ No newline at end of file diff --git a/lib/socket.io/transports/websocket.js b/lib/socket.io/transports/websocket.js new file mode 100644 index 00000000..625bef2d --- /dev/null +++ b/lib/socket.io/transports/websocket.js @@ -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'); + } + +}); \ No newline at end of file diff --git a/lib/socket.io/transports/xhr-multipart.js b/lib/socket.io/transports/xhr-multipart.js new file mode 100644 index 00000000..bce5300a --- /dev/null +++ b/lib/socket.io/transports/xhr-multipart.js @@ -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"); + } + +}); \ No newline at end of file diff --git a/lib/socket.io/transports/xhr-polling.js b/lib/socket.io/transports/xhr-polling.js new file mode 100644 index 00000000..114336d9 --- /dev/null +++ b/lib/socket.io/transports/xhr-polling.js @@ -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(); + } + +}); \ No newline at end of file diff --git a/lib/socket.io/util/array.js b/lib/socket.io/util/array.js new file mode 100644 index 00000000..4b02bf1e --- /dev/null +++ b/lib/socket.io/util/array.js @@ -0,0 +1,16 @@ +// Based on Mixin.js from MooTools (MIT) +// Copyright (c) 2006-2009 Valerio Proietti, + +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; +}; \ No newline at end of file diff --git a/lib/socket.io/util/object.js b/lib/socket.io/util/object.js new file mode 100644 index 00000000..25503cf1 --- /dev/null +++ b/lib/socket.io/util/object.js @@ -0,0 +1,38 @@ +// Based on Mixin.js from MooTools (MIT) +// Copyright (c) 2006-2009 Valerio Proietti, + +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; +}; \ No newline at end of file diff --git a/lib/socket.io/util/options.js b/lib/socket.io/util/options.js new file mode 100644 index 00000000..446b21de --- /dev/null +++ b/lib/socket.io/util/options.js @@ -0,0 +1,28 @@ +// Based on Mixin.js from MooTools (MIT) +// Copyright (c) 2006-2009 Valerio Proietti, +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; + } + +}; \ No newline at end of file diff --git a/lib/vendor/js-oo b/lib/vendor/js-oo new file mode 160000 index 00000000..1f94bd89 --- /dev/null +++ b/lib/vendor/js-oo @@ -0,0 +1 @@ +Subproject commit 1f94bd897994a952114cb1f280c1949d5e6a0648 diff --git a/patch/0.1.32.patch b/patch/0.1.32.patch new file mode 100644 index 00000000..1eeb7495 --- /dev/null +++ b/patch/0.1.32.patch @@ -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 (HandleInstanceTemplate()->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 (HandleInherit(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 HTTPConnection::ResetParse + } + + ++Handle HTTPConnection::Hijack(const Arguments& args) { ++ HandleScope scope; ++ HTTPConnection *connection = ObjectWrap::Unwrap(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(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 target); + + static v8::Persistent client_constructor_template; +- static v8::Persistent server_constructor_template; ++ static v8::Persistent server_constructor_template; + + protected: + static v8::Handle NewClient (const v8::Arguments& args); + static v8::Handle NewServer (const v8::Arguments& args); + static v8::Handle ResetParser(const v8::Arguments& args); ++ static v8::Handle 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 (); + diff --git a/test/chat.html b/test/chat.html new file mode 100644 index 00000000..081eb3de --- /dev/null +++ b/test/chat.html @@ -0,0 +1,77 @@ + + + + socket.io client test + + + + + + + + + + + + + + + + + + + + + + + + +

Sample chat client

+

Connecting...

+
+ +
+ + + + + \ No newline at end of file diff --git a/test/client b/test/client new file mode 160000 index 00000000..042b4d7f --- /dev/null +++ b/test/client @@ -0,0 +1 @@ +Subproject commit 042b4d7f9ae36a00a46a474c9e2a4684df74604e diff --git a/test/server.js b/test/server.js new file mode 100644 index 00000000..82c58869 --- /dev/null +++ b/test/server.js @@ -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('

Welcome. Try the chat example.

'); + 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)); + } + +}); \ No newline at end of file