THRIFT-2398:Improve Node Server Library\nClient: Node\nPatch: Randy Abernethy\n\nGeneral server parameter harmonization and comments
diff --git a/lib/js/test/server_http.js b/lib/js/test/server_http.js
index 01174bc..ce09afc 100644
--- a/lib/js/test/server_http.js
+++ b/lib/js/test/server_http.js
@@ -35,12 +35,12 @@
 var ThriftTestSvcOpt = {

 	transport: TBufferedTransport,

 	protocol: TJSONProtocol,

-	cls: ThriftTestSvc,

+	processor: ThriftTestSvc,
 	handler: ThriftTestHandler

 };

 

 var ThriftWebServerOptions = {

-	staticFilePath: ".",

+	files: ".",
 	services: {

 		"/service": ThriftTestSvcOpt

 	}

diff --git a/lib/js/test/server_https.js b/lib/js/test/server_https.js
index 28f0585..69d7e89 100644
--- a/lib/js/test/server_https.js
+++ b/lib/js/test/server_https.js
@@ -35,21 +35,21 @@
 

 //Setup the I/O stack options for the ThriftTest service

 var ThriftTestSvcOpt = {

-	transport: TBufferedTransport,

-	protocol: TJSONProtocol,

-	cls: ThriftTestSvc,

-	handler: ThriftTestHandler

+  transport: TBufferedTransport,
+  protocol: TJSONProtocol,
+  processor: ThriftTestSvc,
+  handler: ThriftTestHandler
 };

 

 var ThriftWebServerOptions = {

-	staticFilePath: ".",

-	tlsOptions: {

-      key: fs.readFileSync("../../../test/keys/server.key"),

-      cert: fs.readFileSync("../../../test/keys/server.crt")

-    },	

-	services: {

-		"/service": ThriftTestSvcOpt

-	}

+  files: ".",
+  tls: {
+     key: fs.readFileSync("../../../test/keys/server.key"),
+     cert: fs.readFileSync("../../../test/keys/server.crt")
+  },	
+  services: {
+    "/service": ThriftTestSvcOpt
+  }
 };

 

 var server = thrift.createWebServer(ThriftWebServerOptions);

@@ -57,6 +57,3 @@
 server.listen(port);

 console.log("Serving files from: " + __dirname);

 console.log("Http/Thrift Server running on port: " + port);

-

-

-

diff --git a/lib/nodejs/lib/thrift/index.js b/lib/nodejs/lib/thrift/index.js
index 8487464..dd965d2 100644
--- a/lib/nodejs/lib/thrift/index.js
+++ b/lib/nodejs/lib/thrift/index.js
@@ -28,12 +28,7 @@
 
 var server = require('./server');
 exports.createServer = server.createServer;
-exports.createSSLServer = server.createSSLServer;
-exports.createHttpServer = server.createHttpServer;
-exports.createHttpsServer = server.createHttpsServer;
-exports.httpMiddleware = server.httpMiddleware;
 exports.createMultiplexServer = server.createMultiplexServer;
-exports.createMultiplexSSLServer = server.createMultiplexSSLServer;
 
 var web_server = require('./web_server');
 exports.createWebServer = web_server.createWebServer;
diff --git a/lib/nodejs/lib/thrift/server.js b/lib/nodejs/lib/thrift/server.js
index 398d398..06ee90b 100644
--- a/lib/nodejs/lib/thrift/server.js
+++ b/lib/nodejs/lib/thrift/server.js
@@ -17,23 +17,24 @@
  * under the License.
  */
 var net = require('net');
-var http = require('http');
-var https = require('https');
 var tls = require('tls');
-var url = require("url");
-var path = require("path");
-var fs = require("fs");
 
 var ttransport = require('./transport'),
     TBinaryProtocol = require('./protocol').TBinaryProtocol;
 
 
+/** 
+ * Create a Thrift server which can serve one or multiple services. 
+ * @param {object} processor - A normal or multiplexedProcessor (must
+ *                             be preconstructed with the desired handler).
+ * @param {ServerOptions} options - Optional additional server configuration.
+ * @returns {object} - The Apache Thrift Multipled Server.
+ */
 exports.createMultiplexServer = function(processor, options) {
-
   var transport = (options && options.transport) ? options.transport : ttransport.TBufferedTransport;
   var protocol = (options && options.protocol) ? options.protocol : TBinaryProtocol;
 
-  return net.createServer(function(stream) {
+  function serverImpl(stream) {
     var self = this;
     stream.on('data', transport.receiver(function(transportWithData) {
       var input = new protocol(transportWithData);
@@ -51,8 +52,7 @@
           processStatus = processor.process(input, output);
           transportWithData.commitPosition();
         } while (true);
-      }
-      catch (err) {
+      } catch (err) {
         if (err instanceof ttransport.InputBufferUnderrunError) {
           //The last data in the buffer was not a complete message, wait for the rest
           transportWithData.rollbackPosition();
@@ -81,128 +81,24 @@
     stream.on('end', function() {
       stream.end();
     });
-  });
-};
-
-exports.createMultiplexSSLServer = function(processor, options) {
-
-  var transport = (options && options.transport) ? options.transport : ttransport.TBufferedTransport;
-  var protocol = (options && options.protocol) ? options.protocol : TBinaryProtocol;
-
-  return tls.createServer(options, function(stream) {
-    var self = this;
-    stream.on('data', transport.receiver(function(transportWithData) {
-      var input = new protocol(transportWithData);
-      var output = new protocol(new transport(undefined, function(buf) {
-        try {
-            stream.write(buf);
-        } catch (err) {
-            self.emit('error', err);
-            stream.end();
-        }
-      }));
-
-      try {
-        do {
-          processStatus = processor.process(input, output);
-          transportWithData.commitPosition();
-        } while (true);
-      }
-      catch (err) {
-        if (err instanceof ttransport.InputBufferUnderrunError) {
-          //The last data in the buffer was not a complete message, wait for the rest
-          transportWithData.rollbackPosition();
-        }
-        else if (err.message === "Invalid type: undefined") {
-          //No more data in the buffer
-          //This trap is a bit hackish
-          //The next step to improve the node behavior here is to have
-          //  the compiler generated process method throw a more explicit
-          //  error when the network buffer is empty (regardles of the
-          //  protocol/transport stack in use) and replace this heuristic.
-          //  Also transports should probably not force upper layers to
-          //  manage their buffer positions (i.e. rollbackPosition() and
-          //  commitPosition() should be eliminated in lieu of a transport
-          //  encapsulated buffer management strategy.)
-          transportWithData.rollbackPosition();
-        }
-        else {
-          //Unexpected error
-          self.emit('error', err);
-          stream.end();
-        }
-      }
-    }));
-
-    stream.on('end', function() {
-      stream.end();
-    });
-  });
-};
-
-
-function httpRequestHandler(cls, handler, options) {
-  if (cls.Processor) {
-    cls = cls.Processor;
-  }
-  var processor = new cls(handler);
-  var transport = (options && options.transport) ? options.transport : ttransport.TBufferedTransport;
-  var protocol = (options && options.protocol) ? options.protocol : TBinaryProtocol;
-
-  return function(request, response) {
-    request.on('data', transport.receiver(function(transportWithData) {
-      var input = new protocol(transportWithData);
-      var output = new protocol(new transport(undefined, function(buf) {
-        try {
-          response.write(buf);
-        } catch (err) {
-          response.writeHead(500);
-        }
-        response.end();
-      }));
-
-      try {
-        processor.process(input, output);
-        transportWithData.commitPosition();
-      }
-      catch (err) {
-        if (err instanceof ttransport.InputBufferUnderrunError) {
-          transportWithData.rollbackPosition();
-        } else {
-          response.writeHead(500);
-          response.end();
-          throw err;
-        }
-      }
-    }));
   };
-}
-
-exports.httpMiddleware = httpRequestHandler;
-
-exports.createHttpServer = function(cls, handler, options) {
-    return http.createServer(options, httpRequestHandler(cls, handler, options));
-};
-
-exports.createHttpsServer = function(cls, handler, options) {
-    return https.createServer(options, httpRequestHandler(cls, handler, options));
-};
-
-
-exports.createServer = function(cls, handler, options) {
-  if (cls.Processor) {
-    cls = cls.Processor;
+  
+  if (options.tls) {
+    return tls.createServer(options.tls, serverImpl);
+  } else {
+    return net.createServer(serverImpl);
   }
-  var processor = new cls(handler);
-
-  return exports.createMultiplexServer(processor,options);
 };
 
-exports.createSSLServer = function(cls, handler, options) {
-  if (cls.Processor) {
-    cls = cls.Processor;
+/** 
+ * Create a single service Apache Thrift server. 
+ * @param {object} processor - A service class or processor function.
+ * @param {ServerOptions} options - Optional additional server configuration.
+ * @returns {object} - The Apache Thrift Multipled Server.
+ */
+exports.createServer = function(processor, handler, options) {
+  if (processor.Processor) {
+    processor = processor.Processor;
   }
-  var processor = new cls(handler);
-
-  return exports.createMultiplexSSLServer(processor,options);
+  return exports.createMultiplexServer(new processor(handler), options);
 };
diff --git a/lib/nodejs/lib/thrift/web_server.js b/lib/nodejs/lib/thrift/web_server.js
index a040380..fbe4f02 100644
--- a/lib/nodejs/lib/thrift/web_server.js
+++ b/lib/nodejs/lib/thrift/web_server.js
@@ -23,6 +23,7 @@
 var fs = require("fs");
 var crypto = require("crypto");
 
+var MultiplexedProcessor = require('./multiplexed_processor').MultiplexedProcessor;
 var TTransport = require('./transport');
 var TBufferedTransport = require('./transport').TBufferedTransport;
 var TBinaryProtocol = require('./protocol').TBinaryProtocol;
@@ -39,7 +40,7 @@
  * configured for a max of 4GB presently and needs to be adjusted
  * if Node/Browsers become capabile of > 4GB frames.
  *
- *  - FIN is always 1, ATRPC messages are sent in a single frame
+ *  - FIN is 1 if the message is complete
  *  - RSV1/2/3 are always 0
  *  - Opcode is 1(TEXT) for TJSONProtocol and 2(BIN) for TBinaryProtocol
  *  - Mask Present bit is 1 sending to-server and 0 sending to-client
@@ -122,11 +123,13 @@
    * @property {Buffer} nextFrame - Multiple ATRPC messages may be sent in a 
    *                                single WebSocket frame, this Buffer contains
    *                                any bytes remaining to be decoded
+   * @property {Boolean} FIN - True is the message is complete
    */
    
    /** Decodes a WebSocket frame 
    *
-   * @param {Buffer} frame - The raw inbound frame
+   * @param {Buffer} frame - The raw inbound frame, if this is a continuation 
+   *                         frame it must have a mask property with the mask.
    * @returns {WSDecodeResult} - The decoded payload
    *
    * @see {@link WSDecodeResult}
@@ -136,11 +139,13 @@
         data: null,
         mask: null,
         binEncoding: false,
-        nextFrame: null
+        nextFrame: null,
+        FIN: true
       };
+
       //Byte 0 - FIN & OPCODE
       if (wsFrame.fin.FIN != (frame[0] & wsFrame.fin.FIN)) {
-        console.log("WebSocket frame error: Received a frame without fin set.");
+        result.FIN = false;
       }
       result.binEncoding = (wsFrame.frameOpCodes.BIN == (frame[0] & wsFrame.frameOpCodes.BIN));
       //Byte 1 or 1-3 or 1-9 - SIZE
@@ -159,17 +164,22 @@
         result.mask = new Buffer(4);
         frame.copy(result.mask, 0, dataOffset, dataOffset + 4);
         dataOffset += 4;
-      }
+      } 
       //Payload
       result.data = new Buffer(len);
       frame.copy(result.data, 0, dataOffset, dataOffset+len);
-      wsFrame.applyMask(result.data, result.mask);
-      
-      //Residual Frames
+      if (result.mask) {
+        wsFrame.applyMask(result.data, result.mask);
+      }
+      //Next Frame
       if (frame.length > dataOffset+len) {
         result.nextFrame = new Buffer(frame.length - (dataOffset+len));
         frame.copy(result.nextFrame, 0, dataOffset+len, frame.length);
       }
+      //Don't forward control frames
+      if (frame[0] & wsFrame.frameOpCodes.FINCTRL) {
+        result.data = null;
+      }
 
       return result;
   },
@@ -206,7 +216,8 @@
   frameOpCodes: {
     CONT:     0x00,
     TEXT:     0x01,
-    BIN:      0x02
+    BIN:      0x02,
+    CTRL:     0x80
   },
 
   mask: {
@@ -226,44 +237,42 @@
 
 /**
  * @class
- * @name WebServerOptions
- * @property {string} staticFilePath - Path to serve static files from, if absent or "" 
- *                                     static file service is disabled
- * @property {object} tlsOptions - Node.js TLS options (see: nodejs.org/api/tls.html), 
- *                                 if not present or null regular http is used,
- *                                 at least a key and a cert must be defined to use SSL/TLS
- * @property {object} services - An object hash mapping service URI strings 
- *                               to ThriftServiceOptions objects
- * @property {object} headers - An object hash mapping header strings to header value,
+ * @name ServerOptions
+ * @property {array} cors - Array of CORS origin strings to permit requests from. 
+ * @property {string} files - Path to serve static files from, if absent or "" 
+ *                               static file service is disabled.
+ * @property {object} headers - An object hash mapping header strings to header value
  *                              strings, these headers are transmitted in response to 
  *                              static file GET operations.
- * @see {@link ThriftServiceOptions}
+ * @property {object} services - An object hash mapping service URI strings 
+ *                               to ServiceOptions objects
+ * @property {object} tls - Node.js TLS options (see: nodejs.org/api/tls.html), 
+ *                          if not present or null regular http is used,
+ *                          at least a key and a cert must be defined to use SSL/TLS
+ * @see {@link ServiceOptions}
  */
 
 /**
  * @class
- * @name ThriftServiceOptions
+ * @name ServiceOptions
  * @property {object} transport - The layered transport to use (defaults 
  *                                to TBufferedTransport).
- * @property {object} protocol - The Thrift Protocol to use (defaults to 
+ * @property {object} protocol - The serialization Protocol to use (defaults to 
  *                               TBinaryProtocol).
- * @property {object} processor - The Thrift Service class generated by the IDL 
- *                                Compiler for the service (the "cls" key can also 
- *                                be used for this attribute).
+ * @property {object} processor - The Thrift Service class/processor generated 
+ *                                by the IDL Compiler for the service (the "cls" 
+ *                                key can also be used for this attribute).
  * @property {object} handler - The handler methods for the Thrift Service.
- * @property {array} corsOrigins - Array of CORS origin strings to permit requests from. 
  */
 
 /** 
- * Creates a Thrift server which can serve static files and/or one or
+ * Create a Thrift server which can serve static files and/or one or
  * more Thrift Services. 
- * @param {WebServerOptions} options - The server configuration.
- * @returns {object} - The Thrift server.
- *
- * @see {@link WebServerOptions}
+ * @param {ServerOptions} options - The server configuration.
+ * @returns {object} - The Apache Thrift Web Server.
  */
 exports.createWebServer = function(options) {
-  var baseDir = options.staticFilePath;
+  var baseDir = options.files;
   var contentTypesByExtension = {
     '.txt': 'text/plain',
     '.html': 'text/html',
@@ -282,53 +291,62 @@
   var services = options.services;
   for (uri in services) {
     var svcObj = services[uri];
-    var processor = (svcObj.processor) ? (svcObj.processor.Processor || svcObj.processor) : 
-                                         (svcObj.cls.Processor || svcObj.cls);
-    svcObj.processor = new processor(svcObj.handler);
+    
+    //Setup the processor
+    if (svcObj.processor instanceof MultiplexedProcessor) {
+      //Multiplex processors have pre embeded processor/handler pairs, save as is
+      svcObj.processor = svcObj.processor;
+    } else {
+      //For historical reasons Node.js supports processors passed in directly or via the
+      //  IDL Compiler generated class housing the processor. Also, the options property
+      //  for a Processor has been called both cls and processor at different times. We 
+      //  support any of the four possibilities here.
+      var processor = (svcObj.processor) ? (svcObj.processor.Processor || svcObj.processor) : 
+                                           (svcObj.cls.Processor || svcObj.cls);
+      //Processors can be supplied as constructed objects with handlers already embeded,
+      //  if a handler is provided we construct a new processor, if not we use the processor
+      //  object directly
+      if (svcObj.handler) {
+        svcObj.processor = new processor(svcObj.handler);
+      } else {
+        svcObj.processor = processor;
+      }
+    }
     svcObj.transport = svcObj.transport ? svcObj.transport : TBufferedTransport;
     svcObj.protocol = svcObj.protocol ? svcObj.protocol : TBinaryProtocol;
   }
   
   //Verify CORS requirements
-  function VerifyCORSAndSetHeaders(request, response, svc) {
-    if (request.headers.origin && svc.corsOrigins) {
-      if (svcObj.corsOrigins["*"] || svcObj.corsOrigins[request.headers.origin]) {
-        //Sucess, origin allowed
+  function VerifyCORSAndSetHeaders(request, response) {
+    if (request.headers.origin && options.cors) {
+      if (options.cors["*"] || options.cors[request.headers.origin]) {
+        //Allow, origin allowed
         response.setHeader("access-control-allow-origin", request.headers.origin);
-        response.setHeader("access-control-allow-methods", "POST, OPTIONS");
+        response.setHeader("access-control-allow-methods", "GET, POST, OPTIONS");
         response.setHeader("access-control-allow-headers", "content-type, accept");
         response.setHeader("access-control-max-age", "60");
         return true;
       } else {
-        //Failure, origin denied
+        //Disallow, origin denied
         return false;
       }
     }
-    //Success, CORS is not in use
+    //Allow, CORS is not in use
     return true;
   }
   
 
-  //Handle OPTIONS method (CORS support)
+  //Handle OPTIONS method (CORS)
   ///////////////////////////////////////////////////
   function processOptions(request, response) {
-    var uri = url.parse(request.url).pathname;
-    var svc = services[uri];
-    if (!svc) {
-      //Unsupported service
-      response.writeHead("403", "No Apache Thrift Service at " + uri, {});
-      response.end();
-      return;
-    }
-    
-    //Verify CORS requirements
-    if (VerifyCORSAndSetHeaders(request, response, svc)) {
+    if (VerifyCORSAndSetHeaders(request, response)) {
       response.writeHead("204", "No Content", {"content-length": 0});
     } else {
       response.writeHead("403", "Origin " + request.headers.origin + " not allowed", {});
     }
     response.end();
   }
+  
     
   //Handle POST methods (TXHRTransport)
   ///////////////////////////////////////////////////
@@ -343,7 +361,7 @@
     }
 
     //Verify CORS requirements
-    if (!VerifyCORSAndSetHeaders(request, response, svc)) {
+    if (!VerifyCORSAndSetHeaders(request, response)) {
       response.writeHead("403", "Origin " + request.headers.origin + " not allowed", {});
       response.end();
       return;
@@ -365,12 +383,10 @@
       try {
         svc.processor.process(input, output);
         transportWithData.commitPosition();
-      }
-      catch (err) {
+      } catch (err) {
         if (err instanceof TTransport.InputBufferUnderrunError) {
           transportWithData.rollbackPosition();
-        }
-        else {
+        } else {
           response.writeHead(500);
           response.end();
         }
@@ -378,6 +394,7 @@
     }));
   }
 
+
   //Handle GET methods (Static Page Server)
   ///////////////////////////////////////////////////
   function processGet(request, response) {
@@ -387,6 +404,14 @@
       response.end();
       return;      
     }
+
+    //Verify CORS requirements
+    if (!VerifyCORSAndSetHeaders(request, response)) {
+      response.writeHead("403", "Origin " + request.headers.origin + " not allowed", {});
+      response.end();
+      return;
+    }
+
     //Locate the file requested and send it
     var uri = url.parse(request.url).pathname;
     var filename = path.join(baseDir, uri);
@@ -422,12 +447,10 @@
     });
   }
 
+
   //Handle WebSocket calls (TWebSocketTransport)
   ///////////////////////////////////////////////////
-  function processWS(data, socket) {
-    var svc = services[Object.keys(services)[0]];
-    //TODO: add multiservice support (maybe multiplexing is the answer for both XHR and WS?)
-
+  function processWS(data, socket, svc) {
     svc.transport.receiver(function(transportWithData) {
       var input = new svc.protocol(transportWithData);
       var output = new svc.protocol(new svc.transport(undefined, function(buf) {
@@ -456,8 +479,8 @@
 
   //Create the server (HTTP or HTTPS)
   var server = null;
-  if (options.tlsOptions) {
-    server = https.createServer(options.tlsOptions);
+  if (options.tls) {
+    server = https.createServer(options.tls);
   } else {
     server = http.createServer();
   }
@@ -478,19 +501,55 @@
       response.end();
     }
   }).on('upgrade', function(request, socket, head) {
+    //Lookup service
+    var svc;
+    try {
+      svc = services[Object.keys(services)[0]];
+    } catch(e) {
+      if (!route) {
+        socket.write("HTTP/1.1 403 No Apache Thrift Service availible\r\n\r\n");
+        return;
+      }
+    }
+    //Perform upgrade
     var hash = crypto.createHash("sha1")
     hash.update(request.headers['sec-websocket-key'] + "258EAFA5-E914-47DA-95CA-C5AB0DC85B11");
     socket.write("HTTP/1.1 101 Switching Protocols\r\n" +
                    "Upgrade: websocket\r\n" +
                    "Connection: Upgrade\r\n" +
                    "Sec-WebSocket-Accept: " + hash.digest("base64") + "\r\n" +
-                   "\r\n");  
+                   "\r\n");
+    //Handle WebSocket traffic
+    var data = null;
     socket.on('data', function(frame) {
-      do {
-        var result = wsFrame.decode(frame);
-        processWS(result.data, socket);
-        frame = result.nextFrame;
-      } while (frame);
+      try {
+        while (frame) {
+          result = wsFrame.decode(frame);
+          //Prepend any existing decoded data
+          if (data) {
+            if (result.data) {
+              var newData = new Buffer(data.length + result.data.length);
+              data.copy(newData);
+              result.data.copy(newData, data.length);
+              result.data = newData;
+            } else {
+              result.data = data;
+            }
+            data = null;
+          }
+          //If this completes a message process it
+          if (result.FIN) {
+            processWS(result.data, socket, svc);            
+          } else {
+            data = result.data;
+          }
+          //Prepare next frame for decoding (if any)
+          frame = result.nextFrame;
+        };
+      } catch(e) {
+        console.log("TWebSocketTransport Exception: " + e);
+        socket.destroy();
+      }
     });
   });
 
diff --git a/lib/nodejs/test/client.js b/lib/nodejs/test/client.js
index 2aa2295..a6fabfc 100644
--- a/lib/nodejs/test/client.js
+++ b/lib/nodejs/test/client.js
@@ -24,8 +24,6 @@
 var fs = require('fs');
 var assert = require('assert');
 var thrift = require('thrift');
-var ThriftTransports = require('thrift/transport');
-var ThriftProtocols = require('thrift/protocol');
 var ThriftTest = require('./gen-nodejs/ThriftTest');
 var ThriftTestDriver = require('./thrift_test_driver').ThriftTestDriver;
 var ThriftTestDriverPromise = require('./thrift_test_driver_promise').ThriftTestDriver;
@@ -39,32 +37,15 @@
   .option('--promise', 'test with promise style functions')
   .parse(process.argv);
 
-var protocol = undefined;
-var transport =  undefined;
-var testDriver = undefined;
 
-if (program.protocol === "binary") {
-  protocol = ThriftProtocols.TBinaryProtocol;
-} else if (program.protocol === "json") {
-  protocol = ThriftProtocols.TJSONProtocol;
-} else {
-  //default
-  protocol = ThriftProtocols.TBinaryProtocol;
-}
+var protocol = thrift.TBinaryProtocol;
+if (program.protocol === "json") {
+  protocol = thrift.TJSONProtocol;
+} 
 
+var transport =  thrift.TBufferedTransport;
 if (program.transport === "framed") {
-  transport = ThriftTransports.TFramedTransport;
-} else if (program.transport === "buffered") {
-  transport = ThriftTransports.TBufferedTransport;
-} else {
-  //default
-  transport = ThriftTransports.TBufferedTransport;
-}
-
-if (program.promise) {
-  testDriver = ThriftTestDriverPromise;
-} else {
-  testDriver = ThriftTestDriver;
+  transport = thrift.TFramedTransport;
 }
 
 var options = {
@@ -87,6 +68,10 @@
   assert(false, err);
 });
 
+var testDriver = ThriftTestDriver;
+if (program.promise) {
+  testDriver = ThriftTestDriverPromise;
+} 
 testDriver(client, function (status) {
   console.log(status);
   connection.end();
diff --git a/lib/nodejs/test/multiplex_client.js b/lib/nodejs/test/multiplex_client.js
index 9ef716b..6580cb5 100644
--- a/lib/nodejs/test/multiplex_client.js
+++ b/lib/nodejs/test/multiplex_client.js
@@ -17,13 +17,12 @@
  * under the License.
  */
 var thrift = require('thrift');
-var ThriftTransports = require('thrift/transport');
-var ThriftProtocols = require('thrift/protocol');
 var assert = require('assert');
 
 var ThriftTest = require('./gen-nodejs/ThriftTest'),
     SecondService = require('./gen-nodejs/SecondService'),
     ttypes = require('./gen-nodejs/ThriftTest_types');
+var ThriftTestDriver = require('./thrift_test_driver').ThriftTestDriver;
 
 var program = require('commander');
 
@@ -33,27 +32,16 @@
   .option('--ssl', 'use ssl transport')
   .parse(process.argv);
 
-var protocol = undefined;
-var transport =  undefined;
-
-if (program.protocol === "binary") {
-  protocol = ThriftProtocols.TBinaryProtocol;
-} else if (program.protocol === "json") {
-  protocol = ThriftProtocols.TJSONProtocol;
-} else {
-  //default
-  protocol = ThriftProtocols.TBinaryProtocol;
-}
-
+var transport =  thrift.TBufferedTransport;
 if (program.transport === "framed") {
-  transport = ThriftTransports.TFramedTransport;
-} else if (program.transport === "buffered") {
-  transport = ThriftTransports.TBufferedTransport;
-} else {
-  //default
-  transport = ThriftTransports.TBufferedTransport;
+  transport = thrift.TFramedTransport;
 }
 
+var protocol = thrift.TBinaryProtocol;
+if (program.protocol === "json") {
+  protocol = thrift.TJSONProtocol;
+} 
+
 var options = {
   transport: transport,
   protocol: protocol
@@ -76,290 +64,17 @@
     assert(false, err);
 });
 
-// deepEqual doesn't work with fields using node-int64
-
-function checkRecursively(map1, map2) {
-    if (typeof map1 !== 'function' && typeof map2 !== 'function') {
-        if (!map1 || typeof map1 !== 'object') {
-            assert.equal(map1, map2);
-        } else {
-            for (var key in map1) {
-                checkRecursively(map1[key], map2[key]);
-            }
-        }
-    }
-}
-
-client.testString("Test", function(err, response) {
+connection.on('connect', function() {
+  secondclient.secondtestString("Test", function(err, response) {
     assert(!err);
     assert.equal("Test", response);
-});
-secondclient.secondtestString("Test", function(err, response) {
-    assert(!err);
-    assert.equal("Test", response);
-});
+  });
 
-
-client.testVoid(function(err, response) {
-    assert(!err);
-    assert.equal(undefined, response); //void
-});
-
-
-secondclient.secondtestString("Test", function(err, response) {
-    assert(!err);
-    assert.equal("Test", response);
-});
-
-client.testString("", function(err, response) {
-    assert(!err);
-    assert.equal("", response);
-});
-
-// all Languages in UTF-8
-var stringTest = "Afrikaans, Alemannisch, Aragonés, العربية, مصرى, " +
-    "Asturianu, Aymar aru, Azərbaycan, Башҡорт, Boarisch, Žemaitėška, " +
-    "Беларуская, Беларуская (тарашкевіца), Български, Bamanankan, বাংলা, " +
-    "Brezhoneg, Bosanski, Català, Mìng-dĕ̤ng-ngṳ̄, Нохчийн, Cebuano, ᏣᎳᎩ, " +
-    "Česky, Словѣ́ньскъ / ⰔⰎⰑⰂⰡⰐⰠⰔⰍⰟ, Чӑвашла, Cymraeg, Dansk, Zazaki, " +
-    "ދިވެހިބަސް, Ελληνικά, Emiliàn e rumagnòl, English, Esperanto, " +
-    "Español, Eesti, Euskara, فارسی, Suomi, Võro, Føroyskt, Français, " +
-    "Arpetan, Furlan, Frysk, Gaeilge, 贛語, Gàidhlig, Galego, Avañe'ẽ, " +
-    "ગુજરાતી, Gaelg, עברית, हिन्दी, Fiji Hindi, Hrvatski, Kreyòl ayisyen, " +
-    "Magyar, Հայերեն, Interlingua, Bahasa Indonesia, Ilokano, Ido, " +
-    "Íslenska, Italiano, 日本語, Lojban, Basa Jawa, ქართული, Kongo, " +
-    "Kalaallisut, ಕನ್ನಡ, 한국어, Къарачай-Малкъар, Ripoarisch, Kurdî, Коми, " +
-    "Kernewek, Кыргызча, Latina, Ladino, Lëtzebuergesch, Limburgs, " +
-    "Lingála, ລາວ, Lietuvių, Latviešu, Basa Banyumasan, Malagasy, " +
-    "Македонски, മലയാളം, मराठी, Bahasa Melayu, مازِرونی, Nnapulitano, " +
-    "Nedersaksisch, नेपाल भाषा, Nederlands, ‪Norsk (nynorsk)‬, ‪Norsk (" +
-    "bokmål)‬, Nouormand, Diné bizaad, Occitan, Иронау, Papiamentu, " +
-    "Deitsch, Norfuk / Pitkern, Polski, پنجابی, پښتو, Português, Runa " +
-    "Simi, Rumantsch, Romani, Română, Русский, Саха тыла, Sardu, " +
-    "Sicilianu, Scots, Sámegiella, Simple English, Slovenčina, " +
-    "Slovenščina, Српски / Srpski, Seeltersk, Svenska, Kiswahili, தமிழ், " +
-    "తెలుగు, Тоҷикӣ, ไทย, Türkmençe, Tagalog, Türkçe, Татарча/Tatarça, " +
-    "Українська, اردو, Tiếng Việt, Volapük, Walon, Winaray, 吴语, isiXhosa, " +
-    "ייִדיש, Yorùbá, Zeêuws, 中文, Bân-lâm-gú, 粵語";
-
-client.testString(stringTest, function(err, response) {
-    assert(!err);
-    assert.equal(stringTest, response);
-});
-
-var specialCharacters = 'quote: \" backslash:' +
-    ' forwardslash-escaped: \/ ' +
-    ' backspace: \b formfeed: \f newline: \n return: \r tab: ' +
-    ' now-all-of-them-together: "\\\/\b\n\r\t' +
-    ' now-a-bunch-of-junk: !@#$%&()(&%$#{}{}<><><';
-client.testString(specialCharacters, function(err, response) {
-    assert(!err);
-    assert.equal(specialCharacters, response);
-});
-
-
-client.testByte(1, function(err, response) {
-    assert(!err);
-    assert.equal(1, response);
-});
-client.testByte(0, function(err, response) {
-    assert(!err);
-    assert.equal(0, response);
-});
-client.testByte(-1, function(err, response) {
-    assert(!err);
-    assert.equal(-1, response);
-});
-client.testByte(-127, function(err, response) {
-    assert(!err);
-    assert.equal(-127, response);
-});
-
-client.testI32(-1, function(err, response) {
-    assert(!err);
-    assert.equal(-1, response);
-});
-
-client.testI64(5, function(err, response) {
-    assert(!err);
-    assert.equal(5, response);
-});
-client.testI64(-5, function(err, response) {
-    assert(!err);
-    assert.equal(-5, response);
-});
-client.testI64(-34359738368, function(err, response) {
-    assert(!err);
-    assert.equal(-34359738368, response);
-});
-
-client.testDouble(-5.2098523, function(err, response) {
-    assert(!err);
-    assert.equal(-5.2098523, response);
-});
-client.testDouble(7.012052175215044, function(err, response) {
-    assert(!err);
-    assert.equal(7.012052175215044, response);
-});
-
-
-var out = new ttypes.Xtruct({
-    string_thing: 'Zero',
-    byte_thing: 1,
-    i32_thing: -3,
-    i64_thing: 1000000
-});
-client.testStruct(out, function(err, response) {
-    assert(!err);
-    checkRecursively(out, response);
-});
-
-
-var out2 = new ttypes.Xtruct2();
-out2.byte_thing = 1;
-out2.struct_thing = out;
-out2.i32_thing = 5;
-client.testNest(out2, function(err, response) {
-    assert(!err);
-    checkRecursively(out2, response);
-});
-
-
-var mapout = {};
-for (var i = 0; i < 5; ++i) {
-    mapout[i] = i - 10;
-}
-client.testMap(mapout, function(err, response) {
-    assert(!err);
-    assert.deepEqual(mapout, response);
-});
-
-
-var mapTestInput = {
-    "a": "123",
-    "a b": "with spaces ",
-    "same": "same",
-    "0": "numeric key",
-    "longValue": stringTest,
-    stringTest: "long key"
-};
-client.testStringMap(mapTestInput, function(err, response) {
-    assert(!err);
-    assert.deepEqual(mapTestInput, response);
-});
-
-
-var setTestInput = [1, 2, 3];
-client.testSet(setTestInput, function(err, response) {
-    assert(!err);
-    assert.deepEqual(setTestInput, response);
-});
-client.testList(setTestInput, function(err, response) {
-    assert(!err);
-    assert.deepEqual(setTestInput, response);
-});
-
-client.testEnum(ttypes.Numberz.ONE, function(err, response) {
-    assert(!err);
-    assert.equal(ttypes.Numberz.ONE, response);
-});
-
-client.testTypedef(69, function(err, response) {
-    assert(!err);
-    assert.equal(69, response);
-});
-
-
-var mapMapTest = {
-    "4": {
-        "1": 1,
-        "2": 2,
-        "3": 3,
-        "4": 4
-    },
-    "-4": {
-        "-4": -4,
-        "-3": -3,
-        "-2": -2,
-        "-1": -1
-    }
-};
-client.testMapMap(mapMapTest, function(err, response) {
-    assert(!err);
-    assert.deepEqual(mapMapTest, response);
-});
-
-var crazy = new ttypes.Insanity({
-    "userMap": {
-        "5": 5,
-        "8": 8
-    },
-    "xtructs": [new ttypes.Xtruct({
-            "string_thing": "Goodbye4",
-            "byte_thing": 4,
-            "i32_thing": 4,
-            "i64_thing": 4
-        }), new ttypes.Xtruct({
-            "string_thing": "Hello2",
-            "byte_thing": 2,
-            "i32_thing": 2,
-            "i64_thing": 2
-        })]
-});
-var insanity = {
-    "1": {
-        "2": crazy,
-        "3": crazy
-    },
-    "2": {
-        "6": {
-            "userMap": null,
-            "xtructs": null
-        }
-    }
-};
-client.testInsanity(crazy, function(err, response) {
-    assert(!err);
-    checkRecursively(insanity, response);
-});
-
-
-client.testException('TException', function(err, response) {
-    //assert(err); //BUG?
-    assert(!response);
-});
-client.testException('Xception', function(err, response) {
-    assert(!response);
-    assert.equal(err.errorCode, 1001);
-    assert.equal('Xception', err.message);
-});
-client.testException('no Exception', function(err, response) {
-    assert(!err);
-    assert.equal(undefined, response); //void
-});
-
-
-client.testOneway(1, function(err, response) {
-    assert(!response); //should not answer
-});
-
-/**
- * redo a simple test after the oneway to make sure we aren't "off by one" --
- * if the server treated oneway void like normal void, this next test will
- * fail since it will get the void confirmation rather than the correct
- * result. In this circumstance, the client will throw the exception:
- *
- *   TApplicationException: Wrong method namea
- */
-client.testI32(-1, function(err, response) {
-    assert(!err);
-    assert.equal(-1, response);
-});
-
-setTimeout(function() {
-    console.log("Server successfully tested!");
+  ThriftTestDriver(client, function (status) {
+    console.log(status);
     connection.end();
-}, 1500);
+  });
+});
 
 // to make it also run on expresso
 exports.expressoTest = function() {};
diff --git a/lib/nodejs/test/multiplex_server.js b/lib/nodejs/test/multiplex_server.js
index a2ea535..6331f6f 100644
--- a/lib/nodejs/test/multiplex_server.js
+++ b/lib/nodejs/test/multiplex_server.js
@@ -17,9 +17,6 @@
  * under the License.
  */
 var thrift = require('thrift');
-var Thrift = thrift.Thrift;
-var ThriftTransports = require('thrift/transport');
-var ThriftProtocols = require('thrift/protocol');
 
 var ThriftTest = require('./gen-nodejs/ThriftTest'),
   SecondService = require('./gen-nodejs/SecondService'),
@@ -36,25 +33,14 @@
   .option('--ssl', 'use ssl transport')
   .parse(process.argv);
 
-var protocol = undefined;
-var transport =  undefined;
-
-if (program.protocol === "binary") {
-  protocol = ThriftProtocols.TBinaryProtocol;
-} else if (program.protocol === "json") {
-  protocol = ThriftProtocols.TJSONProtocol;
-} else {
-  //default
-  protocol = ThriftProtocols.TBinaryProtocol;
+var protocol = thrift.TBinaryProtocol;
+if (program.protocol === "json") {
+  protocol = thrift.TJSONProtocol;
 }
 
+var transport =  thrift.TBufferedTransport;
 if (program.transport === "framed") {
-  transport = ThriftTransports.TFramedTransport;
-} else if (program.transport === "buffered") {
-  transport = ThriftTransports.TBufferedTransport;
-} else {
-  //default
-  transport = ThriftTransports.TBufferedTransport;
+  transport = thrift.TFramedTransport;
 }
 
 var ThriftTestHandler = require("./test_handler").ThriftTestHandler;
@@ -81,14 +67,12 @@
   protocol: protocol
 };
 
-var server = undefined;
 if (program.ssl) {
   //ssl options
-  options.key = fs.readFileSync(path.resolve(__dirname, 'server.key'));
-  options.cert = fs.readFileSync(path.resolve(__dirname, 'server.crt'));
-  server = thrift.createMultiplexSSLServer(processor, options);
-} else {
-  server = thrift.createMultiplexServer(processor, options);
+  options.tls = {
+    key: fs.readFileSync(path.resolve(__dirname, 'server.key')),
+    cert: fs.readFileSync(path.resolve(__dirname, 'server.crt'))
+  }
 }
 
-server.listen(9090);
+thrift.createMultiplexServer(processor, options).listen(9090);
diff --git a/lib/nodejs/test/server.js b/lib/nodejs/test/server.js
index acc0398..6f5abe6 100644
--- a/lib/nodejs/test/server.js
+++ b/lib/nodejs/test/server.js
@@ -25,13 +25,10 @@
 var fs = require('fs');
 var path = require('path');
 var thrift = require('thrift');
-var ThriftTransports = require('thrift/transport');
-var ThriftProtocols = require('thrift/protocol');
 var ThriftTest = require('./gen-nodejs/ThriftTest');
 var ThriftTestHandler = require('./test_handler').ThriftTestHandler;
 var ThriftTestHandlerPromise = require('./test_handler_promise').ThriftTestHandler;
 
-
 var program = require('commander');
 
 program
@@ -41,46 +38,30 @@
   .option('--promise', 'test with promise style functions')
   .parse(process.argv);
 
-var protocol = undefined;
-var transport =  undefined;
-var handler = undefined;
-
-if (program.protocol === "binary") {
-  protocol = ThriftProtocols.TBinaryProtocol;
-} else if (program.protocol === "json") {
-  protocol = ThriftProtocols.TJSONProtocol;
-} else {
-  //default
-  protocol = ThriftProtocols.TBinaryProtocol;
-}
-
+var transport =  thrift.TBufferedTransport;
 if (program.transport === "framed") {
-  transport = ThriftTransports.TFramedTransport;
-} else if (program.transport === "buffered") {
-  transport = ThriftTransports.TBufferedTransport;
-} else {
-  //default
-  transport = ThriftTransports.TBufferedTransport;
-}
+  transport = thrift.TFramedTransport;
+} 
 
+var protocol = thrift.TBinaryProtocol;
+if (program.protocol === "json") {
+  protocol = thrift.TJSONProtocol;
+} 
+
+var handler = ThriftTestHandler;
 if (program.promise) {
   handler = ThriftTestHandlerPromise;
-} else {
-  handler = ThriftTestHandler;
-}
+} 
 
 var options = {
   protocol: protocol,
   transport: transport
 };
-
 if (program.ssl) {
-  //ssl options
-  options.key = fs.readFileSync(path.resolve(__dirname, 'server.key'));
-  options.cert = fs.readFileSync(path.resolve(__dirname, 'server.crt'));
-  thrift.createSSLServer(ThriftTest, handler, options).listen(9090);
-
-} else {
-  //default
-  thrift.createServer(ThriftTest, handler, options).listen(9090);
+  options.tls = {
+    key: fs.readFileSync(path.resolve(__dirname, 'server.key')),
+    cert: fs.readFileSync(path.resolve(__dirname, 'server.crt'))
+  }
 }
+thrift.createServer(ThriftTest, handler, options).listen(9090);
+
diff --git a/lib/nodejs/test/test_handler.js b/lib/nodejs/test/test_handler.js
index 33c8941..ea1dc1d 100644
--- a/lib/nodejs/test/test_handler.js
+++ b/lib/nodejs/test/test_handler.js
@@ -21,7 +21,7 @@
 //  Apache Thrift test service.

 

 var ttypes = require('./gen-nodejs/ThriftTest_types');

-var TException = require('thrift/thrift').TException;

+var TException = require('thrift').Thrift.TException;
 

 var ThriftTestHandler = exports.ThriftTestHandler = {

   testVoid: function(result) {

diff --git a/lib/nodejs/test/test_handler_promise.js b/lib/nodejs/test/test_handler_promise.js
index fc698eb..c1561bc 100644
--- a/lib/nodejs/test/test_handler_promise.js
+++ b/lib/nodejs/test/test_handler_promise.js
@@ -21,7 +21,7 @@
 //  Apache Thrift test service.

 

 var ttypes = require('./gen-nodejs/ThriftTest_types');

-var TException = require('thrift/thrift').TException;

+var TException = require('thrift').Thrift.TException;
 

 var ThriftTestHandler = exports.ThriftTestHandler = {

   testVoid: function() {