Skip to content
Browse files

ready

  • Loading branch information...
1 parent 4ef0e56 commit 8d44db64f694fc63bd429197e75ecf005f73451b @dotmaster committed
Showing with 734 additions and 2 deletions.
  1. +1 −1 .gitmodules
  2. 0 README
  3. +96 −0 README.md
  4. +6 −0 index.js
  5. +74 −0 lib/setup.js
  6. +499 −0 nodeClient.js
  7. +57 −0 nodeTransport.js
  8. +1 −1 node_modules/socket.io
View
2 .gitmodules
@@ -1,3 +1,3 @@
[submodule "node_modules/socket.io"]
path = node_modules/socket.io
- url = https://github.com/LearnBoost/Socket.IO-node.git
+ url = git@github.com:dotmaster/Socket.IO-node.git
View
0 README
No changes.
View
96 README.md
@@ -0,0 +1,96 @@
+Node2Node: Socket.io
+============================================
+
+This module adds a new Transport to Socket.io called "nodeTransport", with which you can make socket.io connections between instances of nodes.
+The way this is done is quite similar to the xhr-multipart transport. There is also a module from [remy](https://github.com/remy) called [Socket.io-node-client](https://github.com/remy/Socket.io-node-client), which depends on webSocket transport.
+
+## Requirements
+
+- a patched version of socket.IO-Node. Unfortunately right now this is not part of the official release yet. I have come up however with an architecture, thats needs nearly no changes to Socket.IO, and have started a pull request. I will update to the official version of socket.io-Node as soon as this modification is in the master tree.
+
+## How to use
+
+To install and run do the following
+
+ git clone https://github.com/dotmaster/node2node-socket.io node2node-socket.io
+ git submodules update --init //this clones the patched version of socket.io
+
+### Implementing it on your project
+
+On the server:
+
+ var http = require('http'),
+ io = require('./path/to/node2node-socket.io'),
+
+ server = http.createServer(function(req, res){
+ // your normal server code
+ res.writeHead(200, {'Content-Type': 'text/html'});
+ res.end('<h1>Hello world</h1>');
+ });
+
+ server.listen(80);
+
+ // socket.io, I choose you
+ var socket = io.listen(server);
+
+ socket.on('connection', function(client){
+ // new client is here!
+ client.on('message', function(){ … })
+ client.on('disconnect', function(){ … })
+ });
+
+On the client:
+
+ io = require('node2node-socket.io')
+
+ var Client = new io.nodeClient('0.0.0.0', 1234);
+ Client.connect(); //is async!! but send requests will buffers up if no connection there yet
+ Client.on('connect', function(){...})
+ Client.on('message', function(client){...})
+ Client.on('disconnect', function(){...})
+
+## Features
+
+NodeClient features a client side heartbeat function, which means that if the server was to hang up the client would get a timeout too and will try to reconnect in an exponantially augmenting time interval until a maximum of retries is reached. Socket.io normally features this only on the server side.
+
+Some more 'error' events are added to the Client Object, so you can subscribe to them. The error Object always has a type and a message field.
+
+Logging can be turned on/off in nodeClient by passing an option to function nodeClient lieke io.nodeClient('0.0.0.0', 1234, {logging: true})
+
+## Modifications to Socket.io
+
+- a static function called addTransport is exposed to the outside world to add new transports to socket.io
+- the Client base class of every transport is exposed to the outside world
+
+## Credits
+
+- Gregor Schwab &lt;greg@synaptic-labs.net&gt; ([dotmaster](http://github.com/dotmaster))
+
+- Guillermo Rauch &lt;guillermo@learnboost.com&gt; ([Guille](http://github.com/guille))
+
+- Arnout Kazemier ([3rd-Eden](http://github.com/3rd-Eden))
+
+## License
+
+(The MIT License)
+
+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
+'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.
View
6 index.js
@@ -0,0 +1,6 @@
+if (new RegExp('v(\\d)\.(\\d)\.(\\d)').exec(process.version)[2]<4) require(__dirname + "/lib/setup").ext('node_modules');
+module.exports = require('./node_modules/socket.io');
+var Listener = module.exports.Listener;
+var nodeTransport = require('./nodeTransport')
+Listener.addTransport('nodeTransport', nodeTransport)
+module.exports.nodeClient = require ('./nodeClient').nodeClient;
View
74 lib/setup.js
@@ -0,0 +1,74 @@
+//Setup.js created by Ben Weaver (http://gist.github.com/508314)
+
+var fs = require('fs'),
+ path = require('path');
+
+exports.app = app;
+exports.lib = lib;
+exports.ext = ext;
+exports.run = run;
+
+
+/// --- Methods
+
+var run = require;
+
+// A shortcut for adding `lib' and `ext' subfolders, then running a
+// main program.
+function app(base, main) {
+ lib(path.join(base, 'lib'));
+ ext(path.join(base, 'ext'));
+ return main ? run(path.join(base, main)) : exports;
+}
+
+// Local libraries
+function lib(folder) {
+ if (exists(folder))
+ require.paths.unshift(folder);
+ return exports;
+}
+
+// Third-party libraries
+function ext(folder) {
+ if (!exists(folder))
+ return exports;
+
+
+
+ fs.readdirSync(folder).forEach(function(name) {
+ var base = path.join(folder, name),
+ linked = false;
+
+ // Pure-JS packages have a `lib' folder with LIBRARY.js files in it.
+ // Packages with C++ bindings will have a `build/default' folder
+ // with LIBRARY.node files in it after running node-waf.
+ [path.join(base, '/build/default'), path.join(base, '/lib')]
+ .forEach(function(folder) {
+ if (exists(folder)) {
+ require.paths.unshift(folder);
+ linked = true;
+ }
+ });
+
+ // If neither `lib' or `build' were found, fallback to linking the
+ // folder itself.
+ //if (!linked)
+ require.paths.unshift(base);
+
+ // Some programmers refer to third-party libraries in a fully-qualified manner.
+ require.paths.unshift(folder);
+ });
+ return exports;
+}
+
+
+/// --- Aux
+
+function exists(filename) {
+ try {
+ fs.statSync(filename);
+ return true;
+ } catch (x) {
+ return false;
+ }
+}
View
499 nodeClient.js
@@ -0,0 +1,499 @@
+//exports.report=report;
+//exports.prof=prof;
+
+//require('./reporter') // reporter must be running
+//require(__dirname + "/lib/setup").ext('support');
+//require('log4js') // logging doesnt depend on log4js
+var urlparse = require('url').parse,
+ frame = '~m~',
+ qs = require('querystring');
+ var multipart = require("multipart");
+var events = require('events');
+var util=require('util');
+util.inherits(Socket, events.EventEmitter);
+/**
+ * @desc simulates a socket.io client with HTTP client
+ */
+var http = require('http'),
+ url = require('url');
+/*
+ PROTOTYPING SOCKET.IO CLIENT BEHAVIOUR
+ var socket = new io.Socket({node_server_url});
+ socket.connect();
+ socket.on('connect', function(){ … })
+ socket.on('message', function(){ … })
+ socket.on('disconnect', function(){ … })
+*/
+
+
+
+function Socket(ip, port, opts){
+ //this.client = http.createClient(port, ip);
+ if (typeof port !== 'number') throw new Error('[nodeClient] Socket Constructor: need a number for port. But port was '+ typeof port);
+ this.host = ip;
+ this.port = port;
+ this.requestUriBase = "socket.io";
+ //this.type = 'xhr-multipart';
+ this.type = 'nodeTransport'
+ this.headers={};
+ //we write json in the message body
+ //this.headers['Content-Type'] = 'application/json';
+ this.headers['connection'] = 'keep-alive';
+ //this.headers['Transfer-Encoding']= 'chunked';
+ events.EventEmitter.call(this);
+ //process.EventEmitter.call(this);
+ var self=this;
+ options({
+ secure: false,
+ logging: false,
+ timeout: 8000,
+ resource:self.requestUriBase,
+ heartbeatInterval: 16000, //be a bit generous, cause this must be larger than the serverside heartbeat interval (which now is 10 seconds)
+ closeTimeout: 0,
+ maxRetries: 20,
+ initialTimeBetweenTries: 1000
+ }, opts, self);
+ this.connected = false;
+ this.connecting = false;
+ this._heartbeats = 0;
+ this._posting=false;
+ this._heartbeatTimeout = {};
+ this.timeBetweenTries = this.options.initialTimeBetweenTries;
+ this.shouldConnect=true;
+ this.initial = true;
+ this.maxRetries = this.options.maxRetries;
+ this.retries = 0;
+
+ //LOG4JS LOGGING
+ this._addContext = function(a){
+ var args = Array.prototype.slice.call(a);
+ args.unshift('[NODE CLIENT] ' + '--' + now()+ ' ');
+ return args;
+ }
+ this.log = function(a){ if (this.options.logging) console.log.apply(this, this._addContext(arguments));}
+ this.warn = function(a){ if (this.options.logging) console.warn.apply(this, this._addContext(arguments));}
+ this.info = function(a){ if (this.options.logging) console.info.apply(this, this._addContext(arguments));}
+ this.error = function(a){ if (this.options.logging) console.error.apply(this, this._addContext(arguments));}
+ // we try connecting every n milli seconds. On errors n is always doubled.
+ this.connectWaitTimer = function interval() {
+ setTimeout(function () {
+ self.connect();
+ //self.startInterval();
+ }, self.timeBetweenTries); // we cannot use setInterval because we need to change the time all the time.
+ }
+
+ this._connect= function(){
+ self.shouldConnect=true;
+ self.connectWaitTimer();
+ }
+}
+
+Socket.prototype._prepareUrl = function(){
+ return (this.options.secure ? 'https' : 'http')
+ + '://' + this.host
+ + ':' + this.port
+ + '/' + this.options.resource
+ + '/' + this.type
+ + (this.sessionid ? ('/' + this.sessionid) : '/');
+};
+
+Socket.prototype._request = function(url, method, multipart){
+ if (method == 'POST'){
+ this.headers['Content-type']= 'application/x-www-form-urlencoded; charset=utf-8';
+ }
+ //var req = this.client.request(method || 'GET', this._prepareUrl(), this.headers);
+ var options = {
+ host: this.host,
+ port: this.port,
+ path: this._prepareUrl(),
+ method: method || 'GET',
+ headers: this.headers
+ };
+ var req = http.request(options);
+ var self=this;
+ req.connection.addListener('end', function(){ self.warn('req.connection.addListener end called'); self._onDisconnect('connection end'); });
+ req.on('error', function(e){
+ self.log("Got Request error: " + e.message);
+ self.emit('error', {type: (req.method=='GET'?'connect':'send'), 'error':e, 'message': e.message})
+ })
+ //if (multipart) req.multipart = true;
+ //req.open(method || 'GET', this._prepareUrl() + (url ? '/' + url : ''));
+
+ return req;
+};
+
+Socket.prototype.connect = function(){
+ if (!this.shouldConnect) return;
+ this.log('connecting...')
+ if (this.shouldConnect) this.shouldConnect=false;
+
+ var self=this;
+ this.connecting = true;
+ // SETUP PARSER FOR MULTIPART GET RESPONSES
+ this.parser = multipart.parser();
+ this.parser.boundary = "socketio";
+ // in all event handlers, "this" is the parser, and "this.part" is the
+ // part that's currently being dealt with.
+ var buffer="";sendBuffer="";
+
+ //subscribe to own error events
+ this.on('error', function(e){
+ this.send({status:'error', 'message':e.message, 'data':e})//will queue in sendbuffer if no connection available yet, be JSend compliant
+ });
+
+ this.parser.onpartbegin = function (part) {
+ //self.log('content type '+(part.headers['content-type']));
+ };
+ this.parser.ondata = function (chunk) {
+ //self.log('chunk '+ chunk);
+ buffer+=chunk.toString();
+ };
+ this.parser.onpartend = function (part) {
+ //self.log('parser boundary end ');
+ self._onData(buffer); buffer="";
+ };
+
+ if (!('_sendBuffer' in this)) this._sendBuffer = [];
+ //nothing will get sent until request end will be called
+ //this.request = client.request('GET', this._prepareUrl, this.headers);
+ this.request = this._request('', 'GET', true);
+ var buffer;
+ this.request.on('error', function (e){
+ self.emit('error', {'type':'connect', 'message': 'Multipart GET request error ' + e.message})
+ self._handleConnectError();
+ });
+ this.request.on('response', function (response){
+ self.response=response;
+ // bail hard on non-200, something must be wrong
+ if (response.statusCode != 200) {
+ self.emit('error', {'type':'connect', message: 'response statuscode was '+response.statuscode, 'status': http.STATUS_CODES[response.statusCode]})
+ self._handleConnectError();
+ return;
+ }
+
+ //var json;
+
+ response.setEncoding('utf8');
+ response.on('error', function (e) {
+ self.emit('error', {'type':'connect', 'message': 'Multipart GET request response error ' + e.message})
+ self._handleConnectError();
+ });
+ response.on('end', function () {
+ //self._onData(buffer);
+ self.emit('error', {'type':'disconnect', 'message': 'Multipart GET request should not receive end.'})
+ self._handleConnectError();
+ });
+ response.on('data', function (chunk) {
+ try {
+ self._onMultipartData(chunk);
+
+ } catch (Err) {
+ console.log((Err.stack));
+ self.emit('error', {'type':'onMessage', 'message': Err.message, 'stack': Err.stack})
+ return; // continue
+ }
+ });
+ //this._onConnect(this.request, this.response);
+ })
+ this.request.end(); // we send a normal GET request without a body //ends the GET request (however as we get back a session id, next time we can reuse the same transport by passing in the session id in POSt)
+}
+
+Socket.prototype.disconnect = function(){
+ // clode the parser
+ this.parser.close();
+ try {
+ //the GET request
+ if('response' in this) this.response.connection.destroy();
+ if ('request' in this) {
+ this.request.end(); this.request.connection.destroy();
+ if (typeof this.request.abort == 'function')
+ this.request.abort();//new since node v3.8
+ }
+ this.log("[Response] Closing connection ");
+ } catch(e) {
+ this.warn("[Response] Error ending connection "+e)
+ }
+ try {
+ //the post request
+ if('sendResponse' in this) this.response.connection.destroy();
+ if ('_sendRequest' in this) {
+ this._sendRequest.connection.destroy();
+ if (typeof this._sendRequest.abort == 'function')
+ this._sendRequest.abort();//new since node v3.8
+ }
+ this._posting = false;
+ this.connecting = false;
+ this.log("[sendResponse] Closing connection ");
+ } catch(e) {
+ this.warn("[sendResponse] Error ending connection "+e)
+ }
+ this.sessionid = undefined;
+ this.connected = false;
+ this.emit('disconnect', {message:'disconnect'});
+}
+
+Socket.prototype.send = function(data){
+ //send the message body
+ //this.request.write(JSON.stringify(message), this.headers);
+ if (Array.isArray(data)){
+ this._sendBuffer.push.apply(this._sendBuffer, data);
+ } else {
+ if (typeof data !== 'string') data=JSON.stringify(data);
+ this._sendBuffer.push(data);
+ }
+ this._checkSend();
+ return this;
+ //this.request.write('data=' + encodeURIComponent(data), this.headers);
+ //this.request.end(); //sends delimiters for chunked encoding but keeps connection
+}
+
+Socket.prototype._checkSend = function(){
+ if (!this._posting && this._sendBuffer.length){//if we aren't posting and there is something in the buffer: send it
+ var encoded = this._encode(this._sendBuffer);
+ this._sendBuffer = [];
+ this._send(encoded);
+ }
+};
+
+Socket.prototype._send = function(data){
+ var self = this;
+ this._posting = true;
+ this._sendRequest = this._request('send', 'POST');
+ this._sendRequest.write('data=' + /*encodeURIComponent*/qs.escape(data));
+ this._sendRequest.on('response', function(response){
+ self._sendResponse=response;
+ if (response.statusCode != 200){
+ self.emit('error', {'type': 'send', 'message':'error sending message, got statuscode ' + http.STATUS_CODES[response.statusCode]})
+ }
+ //throw "response: " + response.statusCode;
+ response.setEncoding('utf8');
+ response.on('end', function () {
+ //nothing to be done here all Ok if we receive end from Post request
+ });
+ response.on('data', function (chunk) {
+ try {
+ //should be multipart message saying ok
+ if(chunk !== 'ok'){
+ self.log('Bad response for message received from server, Message was not delivered ' + chunk);
+ self.emit('error', {'type': 'send', 'message':'error sending message ' + chunk})
+ return;
+ }
+ //buffer+=chunk.toString();
+ //json = JSON.parse(chunk); // let's not get crazy here
+ //this.emit('message', json);
+
+ } catch (Err) {
+ console.log(Err.stack);
+ return; // continue
+ }
+ });
+ self._posting = false;
+ self._checkSend(); //poll again to see if something new entered the buffer meanwhile
+ })
+ this._sendRequest.end(); //sends delimiters for chunked encoding but keeps connection
+}
+
+Socket.prototype._onMultipartData = function (multipart){
+ // feed the multipart stream throught the multipart stream parser
+ this.parser.write(multipart);
+}
+
+//send a heartbeat message
+Socket.prototype._heartbeat = function(h){
+ var self = this;
+ this.info('echoing heartbeat ' + h)
+ self.send('~h~' + h); //pong
+};
+
+//HANDLING CONNECTION ERRORS
+Socket.prototype._handleConnectError = function(){
+ //destroy everything, that has been created until now
+ this.parser.close();
+ try {
+ //the GET request
+ if('response' in this) this.response.connection.destroy();
+ if ('request' in this) {
+ this.request.end(); this.request.connection.destroy();
+ if (typeof this.request.abort == 'function')
+ this.request.abort();//new since node v3.8
+ }
+ this.log("[Response] Closing connection ");
+ } catch(e) {
+ this.warn("[Response] Error ending connection "+e)
+ }
+ this.request.destroy && this.request.destroy();
+ this.response.destroy && this.response.destroy();
+
+ // now lets reconnect
+ if (this._checkMaxTimesConnectionError('connect')) return;
+ this.timeBetweenTries *= 2;
+ this.emit('error', {type: 'connect', message: 'handleConnectionError ' + ' retrying in ' + this.timeBetweenTries/1000 + ' seconds'})
+ //we are not yet connected so no heartbeat interval is set, lets just try again
+ this.connecting=false;
+ this._connect(); //starts a timer before effectively connecting
+}
+
+Socket.prototype.setupHeartbeatInterval = function(){
+ var self = this;
+ if(this._heartbeatTimeout._onTimeout !== null) clearTimeout(this._heartbeatTimeout);
+ //this.log('heartbeat Timeout cleared ' + self._heartbeatTimeout._idleStart )
+ self._heartbeatTimeout = setTimeout(function(){
+ self._onDisconnect('heartbeat timeout');
+ }, self.options.heartbeatInterval);
+ //self.log('settup heartbeat Timeout ' + self._heartbeatTimeout._idleStart)
+}
+//HANDLING ANSWERS
+//request on response
+Socket.prototype._onConnect = function(){
+ //when we connect the server will send a heartbeat within its interval (on the client we start counting a bit longer so that we take into account the transmission time)
+ this.setupHeartbeatInterval();
+ this.connected = true;
+ this.connecting = false;
+ //this._doQueue();
+ //if (this.options.rememberTransport) this.options.document.cookie = 'socket.io=' + encodeURIComponent(this.transport.type);
+ this.emit('connect');
+};
+
+//response on data
+Socket.prototype._onData = function(data){
+ //first me must mime decode the data
+ var msgs = this._decode(data);
+ if (msgs === false) return this.error('Bad message received from server ' + data);
+ if (msgs){
+ for (var i = 0, l = msgs.length; i < l; i++){
+ this._onMessage(msgs[i]);
+ }
+ }
+};
+
+//response on data
+Socket.prototype._onMessage = function(message){
+ if (!('sessionid' in this)){
+ this.sessionid = message;
+ this.info(' session '+ this.sessionid + ' established - connected')
+ this._onConnect();
+ } else if (message.substr(0, 3) == '~h~'){
+ this._onHeartbeat(message.substr(3)); //pong
+ } else {
+ this.emit('message', message);
+ }
+};
+
+//response on data = hearbeat message
+Socket.prototype._onHeartbeat = function(h){
+ var self=this;
+ //if (h == this._heartbeats){
+ //this.log('heartbeat received ' + h)
+ //this.log('heartbeat Timeout before clear ' + self._heartbeatTimeout._idleStart)
+ //when we receive a heartbeat weclear the timeout and start counting again
+ this.setupHeartbeatInterval();
+ this._heartbeat(h); // echo
+ //}
+};
+
+/*Socket.prototype._checkStartInterval = function(){
+ if (!this.initial) return;
+ if (this.initial) {
+ this.initial = false;
+ this.startInterval();
+ }
+}
+*/
+
+Socket.prototype._checkMaxTimesConnectionError = function(type){
+ //start the timer on first usage
+ //this._checkStartInterval();
+ //reopen connection
+ if (this.retries++ >= this.maxRetries){
+ this.emit('error', {type: type, message: 'max retry times reached, retried ' + this.maxRetries + ' times. bailing out'})
+ //we shouldnt be in a timer, when we come here, cause the timer gets rearmed only after a disconnect took place and a _connect is called
+ if (this.connectWaitTimer._onTimeout !== null ) clearTimeout(this._connectWaitTimer); //that's it, definitely clear the reconnect timeout
+ return true;
+ }
+ return false;
+}
+Socket.prototype._onDisconnect = function(spec){
+ //called when we lost connection (actually the connection might still be active so disconnect it!)
+ //check if limit reached
+ if (this._checkMaxTimesConnectionError('disconnect')) return;
+ //reopen connection
+ //if (spec == 'heartbeat timeout'){
+ this.emit('error', {type: 'disconnect', message: spec + ' retrying in ' + this.timeBetweenTries/1000 + ' seconds'})
+ this.disconnect();
+ this._connect();
+ this.timeBetweenTries *= 2;
+
+ //}
+ //we will not receive any heartbeat anymore, so clear the heartbeat timeout
+ if (this._heartbeatTimeout._onTimeout !== null) clearTimeout(this._heartbeatTimeout);
+ //if (this.startInterval._onTimeout !== null ) clearTimeout(this._startInterval);
+
+}
+
+
+
+//HELPERS
+Socket.prototype._encode = function(messages){
+ var ret = '', message,
+ messages = Array.isArray(messages) ? messages : [messages];
+ for (var i = 0, l = messages.length; i < l; i++){
+ message = messages[i] === null || messages[i] === undefined ? '' : String(messages[i]);
+ ret += frame + message.length + frame + message;
+ }
+ return ret;
+};
+
+Socket.prototype._decode = function(data){
+ var messages = [], number, n;
+ do {
+ if (data.substr(0, 3) !== frame) return messages;
+ data = data.substr(3);
+ number = '', n = '';
+ for (var i = 0, l = data.length; i < l; i++){
+ n = Number(data.substr(i, 1));
+ if (data.substr(i, 1) == n){
+ number += n;
+ } else {
+ data = data.substr(number.length + frame.length)
+ number = Number(number);
+ break;
+ }
+ }
+ messages.push(data.substr(0, number)); // here
+ data = data.substr(number);
+ } while(data !== '');
+ return messages;
+};
+
+
+
+
+
+/*if (db.user && db.pass) {
+ var basicAuth = 'Basic ' + new Buffer(db.user + ':' + db.pass).toString('base64');
+}
+
+var headers = {};
+if (typeof basicAuth != 'undefined') {
+ headers["Authorization"] = basicAuth;
+}*/
+function now(){
+ return new Date().toUTCString();
+}
+
+function options(opts, mergeOpts, self){
+ self.options = merge(opts || {}, mergeOpts || {});
+}
+
+function merge(source, merge){
+ for (var i in merge) source[i] = merge[i];
+ return source;
+};
+
+//factory method for closure
+exports.makeSocket=function(ip,port,opts){return new Socket(ip, port, opts);};
+exports.nodeClient = Socket;
+
+
+
+
View
57 nodeTransport.js
@@ -0,0 +1,57 @@
+//var Client = require('../client')
+var Client = require('./node_modules/socket.io/').Client
+ , util = require(process.binding('natives').util ? 'util' : 'sys')
+ , qs = require('querystring');
+
+var NodeClient = module.exports = function(){
+ Client.apply(this, arguments);
+};
+
+util.inherits(NodeClient, Client);
+
+NodeClient.prototype._onConnect = function(req, res){
+ var self = this, body = '', headers = {};
+ switch (req.method){
+ case 'GET':
+ Client.prototype._onConnect.apply(this, [req, res]);
+ headers['Content-Type'] = 'multipart/x-mixed-replace;boundary="socketio"';
+ headers['Connection'] = 'keep-alive';
+ this.request.connection.addListener('end', function(){ self.listener.options.log('[SERVER] _onConnect addListener end called'); self._onClose(); });
+ // this.response.useChunkedEncodingByDefault = false; //this breaks the node clientRequest, perhaps Browsers are better in handling this
+ this.response.shouldKeepAlive = true;
+ this.response.writeHead(200, headers);
+ this.response.write("--socketio\r\n");
+ if ('flush' in this.response) this.response.flush();
+ this._payload();
+ break;
+
+ case 'POST':
+ headers['Content-Type'] = 'text/plain';
+ req.addListener('data', function(message){
+ body += message.toString();
+ });
+ req.addListener('end', function(){
+ try {
+ var msg = qs.parse(body);
+ self._onMessage(msg.data);
+ } catch(e){
+ self.listener.options.log('[SERVER] xhr-multipart message handler error - ' + e.stack);
+ }
+ res.writeHead(200, headers);
+ res.write('ok');
+ res.end();
+ body = '';
+ });
+ break;
+ }
+};
+
+NodeClient.prototype._write = function(message){
+ if (this._open){
+ //socket.io's multipart xhr doesn't send CLRF but just RF. This breaks isaacs multipart.js library so fixed this here. Perhaps browser implementations are more tolerant here
+ this.response.write("Content-Type: text/plain" + (message.length === 1 && message.charCodeAt(0) === 6 ? "; charset=us-ascii" : "") + "\r\n\r\n");
+ this.response.write(message + "\r\n");
+ this.response.write("--socketio");
+ //this.response.end(); //we use chunked encoding but do boundary definition over mime
+ }
+};
2 node_modules/socket.io
@@ -1 +1 @@
-Subproject commit 5308452b8a1564c3f95d0f1b569a9ccb70e12dbc
+Subproject commit 4d2219c2e8b148d1bef01a91c82e317b07ba4c4c

0 comments on commit 8d44db6

Please sign in to comment.
Something went wrong with that request. Please try again.