THRIFT-2493:Node.js lib needs HTTP client
Client: node
Patch: Randy Abernethy

Clean up of many jshint warnings/errors, jsdoc for HttpConnect,
added support for https and Python to HttpConnect, added tests
for HttpConnect with https and promises.
diff --git a/lib/nodejs/examples/README.md b/lib/nodejs/examples/README.md
index 0773e60..db012df 100644
--- a/lib/nodejs/examples/README.md
+++ b/lib/nodejs/examples/README.md
@@ -18,6 +18,7 @@
 
 #Generate the bindings:
 ../../../compiler/cpp/thrift --gen js:node user.thrift
+../../../compiler/cpp/thrift --gen js:node --gen py hello.thrift
 
 #To run the user example, first start up the server in one terminal:
 NODE_PATH=../lib:../lib/thrift node server.js
@@ -28,5 +29,6 @@
 #For an example using JavaScript in the browser to connect to 
 #a node.js server look at hello.html, hello.js and hello.thrift
 
-
+#HTTP examples are provided also: httpClient.js and httpServer.js
+#You can test HTTP cross platform with the httpServer.py Python server
     
diff --git a/lib/nodejs/examples/httpClient.js b/lib/nodejs/examples/httpClient.js
new file mode 100644
index 0000000..19cc0c3
--- /dev/null
+++ b/lib/nodejs/examples/httpClient.js
@@ -0,0 +1,23 @@
+var thrift = require('thrift');
+var helloSvc = require('./gen-nodejs/HelloSvc.js');
+
+var options = {
+   transport: thrift.TBufferedTransport,
+   protocol: thrift.TJSONProtocol,
+   path: "/hello",
+   headers: {"Connection": "close"},
+   https: false
+};
+
+var connection = thrift.createHttpConnection("localhost", 9090, options);
+var client = thrift.createHttpClient(helloSvc, connection);
+
+connection.on("error", function(err) {
+   console.log("Error: " + err);
+});
+
+client.hello_func(function(error, result) {
+   console.log("Msg from server: " + result);
+});
+
+
diff --git a/lib/nodejs/examples/httpServer.js b/lib/nodejs/examples/httpServer.js
new file mode 100644
index 0000000..acae136
--- /dev/null
+++ b/lib/nodejs/examples/httpServer.js
@@ -0,0 +1,31 @@
+var thrift = require('thrift');                 	
+var helloSvc = require('./gen-nodejs/HelloSvc');
+
+//ServiceHandler: Implement the hello service 
+var helloHandler = {
+  hello_func: function (result) {
+    console.log("Received Hello call");
+    result(null, "Hello from Node.js");
+  }
+};
+
+//ServiceOptions: The I/O stack for the service
+var helloSvcOpt = {                       		
+    handler: helloHandler,                      	
+    processor: helloSvc,                         	
+    protocol: thrift.TJSONProtocol,                 
+    transport: thrift.TBufferedTransport 		
+};                                  
+
+//ServerOptions: Define server features
+var serverOpt = {                          	
+   services: {                         
+      "/hello": helloSvcOpt                 
+   }                               
+}                                   
+
+//Create and start the web server 
+var port = 9090;                            		
+thrift.createWebServer(serverOpt).listen(port);                        		
+console.log("Http/Thrift Server running on port: " + port);
+
diff --git a/lib/nodejs/examples/httpServer.py b/lib/nodejs/examples/httpServer.py
new file mode 100644
index 0000000..b8ba586
--- /dev/null
+++ b/lib/nodejs/examples/httpServer.py
@@ -0,0 +1,19 @@
+import sys
+sys.path.append('gen-py')
+
+from hello import HelloSvc
+from thrift.protocol import TJSONProtocol
+from thrift.server import THttpServer
+
+class HelloSvcHandler:
+  def hello_func(self):
+    print "Hello Called"
+    return "hello from Python"
+
+processor = HelloSvc.Processor(HelloSvcHandler())
+protoFactory = TJSONProtocol.TJSONProtocolFactory()
+port = 9090
+server = THttpServer.THttpServer(processor, ("localhost", port), protoFactory)
+print "Python server running on port " + str(port)
+server.serve()
+
diff --git a/lib/nodejs/lib/thrift/connection.js b/lib/nodejs/lib/thrift/connection.js
index a3c2d79..aa985df 100644
--- a/lib/nodejs/lib/thrift/connection.js
+++ b/lib/nodejs/lib/thrift/connection.js
@@ -152,8 +152,9 @@
           client['recv_' + header.fname](message, header.mtype, dummy_seqid);
         } else {
           delete client._reqs[dummy_seqid];
-          throw new thrift.TApplicationException(thrift.TApplicationExceptionType.WRONG_METHOD_NAME,
-                             "Received a response to an unknown RPC function");
+          self.emit("error", 
+                    new thrift.TApplicationException(thrift.TApplicationExceptionType.WRONG_METHOD_NAME,
+                             "Received a response to an unknown RPC function"));
         }
       }
     }
diff --git a/lib/nodejs/lib/thrift/http_connection.js b/lib/nodejs/lib/thrift/http_connection.js
index 7eab320..ccf882f 100644
--- a/lib/nodejs/lib/thrift/http_connection.js
+++ b/lib/nodejs/lib/thrift/http_connection.js
@@ -16,18 +16,67 @@
  * specific language governing permissions and limitations
  * under the License.
  */
+var util = require('util');
+var http = require('http');
+var https = require('https');
+var EventEmitter = require("events").EventEmitter;
 var thrift = require('./thrift');
 var ttransport = require('./transport');
 var tprotocol = require('./protocol');
 
-var http = require('http');
+/**
+ * @class
+ * @name ConnectOptions
+ * @property {string} transport - The Thrift layered transport to use (TBufferedTransport, etc).
+ * @property {string} protocol - The Thrift serialization protocol to use (TBinaryProtocol, etc.).
+ * @property {string} path - The URL path to POST to (e.g. "/", "/mySvc", "/thrift/quoteSvc", etc.).
+ * @property {object} headers - A standard Node.js header hash, an object hash containing key/value 
+ *        pairs where the key is the header name string and the value is the header value string.
+ * @property {boolean} https - True causes the connection to use https, otherwise http is used.
+ * @property {object} nodeOptions - Options passed on to node.
+ * @example
+ *     //Use a connection that requires ssl/tls, closes the connection after each request,
+ *     //  uses the buffered transport layer, uses the JSON protocol and directs RPC traffic
+ *     //  to https://thrift.example.com:9090/hello 
+ *     var thrift = require('thrift');
+ *     var options = {
+ *        transport: thrift.TBufferedTransport,
+ *        protocol: thrift.TJSONProtocol,
+ *        path: "/hello",
+ *        headers: {"Connection": "close"},
+ *        https: true
+ *     };
+ *     var con = thrift.createHttpConnection("thrift.example.com", 9090, options);
+ *     var client = thrift.createHttpClient(myService, connection);
+ *     client.myServiceFunction();
+ */
 
+/**
+ * Initializes a Thrift HttpConnection instance (use createHttpConnection() rather than 
+ *    instantiating directly).
+ * @constructor
+ * @param {string} host - The host name or IP to connect to.
+ * @param {number} port - The TCP port to connect to.
+ * @param {ConnectOptions} options - The configuration options to use.
+ * @throws {error} Exceptions other than ttransport.InputBufferUnderrunError are rethrown
+ * @event {error} The "error" event is fired when a Node.js error event occurs during
+ *     request or response processing, in which case the node error is passed on. An "error"
+ *     event may also be fired when the connectison can not map a response back to the
+ *     appropriate client (an internal error), generating a TApplicationException.
+ * @classdesc HttpConnection objects provide Thrift end point transport 
+ *     semantics implemented over the Node.js http.request() method.
+ * @see {@link createHttpConnection}
+ */
 var HttpConnection = exports.HttpConnection = function(host, port, options) {
+  //Initialize the emitter base object
+  EventEmitter.call(this);
+
   //Set configuration
   var self = this;
   this.options = options || {};
   this.host = host;
   this.port = port;
+  this.https = this.options.https || false;
   this.transport = this.options.transport || ttransport.TBufferedTransport;
   this.protocol = this.options.protocol || tprotocol.TBinaryProtocol;
 
@@ -37,9 +86,16 @@
     port: this.port || 80,
     path: this.options.path || '/',
     method: 'POST',
-    headers: this.options.headers || {},
-    tls: options.tls || {},
+    headers: this.options.headers || {}
   };
+  for (var attrname in this.options.nodeOptions) { 
+    this.nodeOptions[attrname] = this.options.nodeOptions[attrname]; 
+  }
+  /*jshint -W069 */
+  if (! this.nodeOptions.headers["Connection"]) {
+    this.nodeOptions.headers["Connection"] = "keep-alive";
+  }
+  /*jshint +W069 */
 
   //The sequence map is used to map seqIDs back to the 
   //  calling client in multiplexed scenarios
@@ -83,8 +139,10 @@
           client['recv_' + header.fname](proto, header.mtype, dummy_seqid);
         } else {
           delete client._reqs[dummy_seqid];
-          throw new thrift.TApplicationException(thrift.TApplicationExceptionType.WRONG_METHOD_NAME,
-                             "Received a response to an unknown RPC function");
+          self.emit("error",
+                    new thrift.TApplicationException(
+                       thrift.TApplicationExceptionType.WRONG_METHOD_NAME,
+                       "Received a response to an unknown RPC function"));
         }
       }
     }
@@ -95,7 +153,7 @@
         throw e;
       }
     }
-  };
+  }
           
       
   //Response handler
@@ -104,8 +162,8 @@
     var data = [];
     var dataLen = 0;
 
-    response.on('error', function (err) {
-      console.log("Error in response: " + err); 
+    response.on('error', function (e) {
+      self.emit("error", e);
     });
 
     response.on('data', function (chunk) {
@@ -125,29 +183,55 @@
     });
   };
 };
+util.inherits(HttpConnection, EventEmitter);
 
+/**
+ * Writes Thrift message data to the connection
+ * @param {Buffer} data - A Node.js Buffer containing the data to write
+ * @returns {void} No return value.
+ * @event {error} the "error" event is raised upon request failure passing the 
+ *     Node.js error object to the listener.  
+ */
 HttpConnection.prototype.write = function(data) {
-  var req = http.request(this.nodeOptions, this.responseCallback);
-
-  req.on('error', function(e) {
-    throw new thrift.TApplicationException(thrift.TApplicationExceptionType.UNKNOWN,
-                                           "Request failed");
-  });
-
+  var self = this;
+  self.nodeOptions.headers["Content-length"] = data.length;
+  var req = (self.https) ?
+      https.request(self.nodeOptions, self.responseCallback) :
+      http.request(self.nodeOptions, self.responseCallback);
+  req.on('error', function(err) {
+    self.emit("error", err);
+  });  
   req.write(data);
   req.end();
 };
 
+/**
+ * Creates a new HttpConnection object, used by Thrift clients to connect
+ *    to Thrift HTTP based servers.
+ * @param {string} host - The host name or IP to connect to.
+ * @param {number} port - The TCP port to connect to.
+ * @param {ConnectOptions} options - The configuration options to use.
+ * @returns {HttpConnection} The connection object.
+ * @see {@link ConnectOptions}
+ */
 exports.createHttpConnection = function(host, port, options) {
   return new HttpConnection(host, port, options);
 };
 
+/**
+ * Creates a new client object for the specified Thrift service.
+ * @param {object} cls - The module containing the service client
+ * @param {HttpConnection} httpConnection - The connection to use.
+ * @returns {object} The client object.
+ * @see {@link createHttpConnection}
+ */
 exports.createHttpClient = function(cls, httpConnection) {
   if (cls.Client) {
     cls = cls.Client;
   }
-  return httpConnection.client = 
+  httpConnection.client = 
     new cls(new httpConnection.transport(undefined, function(buf) {httpConnection.write(buf);}), 
             httpConnection.protocol);
+  return httpConnection.client;
 };
 
diff --git a/lib/nodejs/lib/thrift/server.js b/lib/nodejs/lib/thrift/server.js
index 715378c..383d1f5 100644
--- a/lib/nodejs/lib/thrift/server.js
+++ b/lib/nodejs/lib/thrift/server.js
@@ -84,7 +84,7 @@
     stream.on('end', function() {
       stream.end();
     });
-  };
+  }
   
   if (options.tls) {
     return tls.createServer(options.tls, serverImpl);
diff --git a/lib/nodejs/lib/thrift/web_server.js b/lib/nodejs/lib/thrift/web_server.js
index fbe4f02..dd7ad5f 100644
--- a/lib/nodejs/lib/thrift/web_server.js
+++ b/lib/nodejs/lib/thrift/web_server.js
@@ -289,7 +289,7 @@
 
   //Setup all of the services
   var services = options.services;
-  for (uri in services) {
+  for (var uri in services) {
     var svcObj = services[uri];
     
     //Setup the processor
@@ -399,7 +399,7 @@
   ///////////////////////////////////////////////////
   function processGet(request, response) {
     //Undefined or empty base directory means do not serve static files
-    if (!baseDir || "" == baseDir) {
+    if (!baseDir || "" === baseDir) {
       response.writeHead(404);
       response.end();
       return;      
@@ -437,7 +437,7 @@
         if (contentType) {
           headers["Content-Type"] = contentType;
         }
-        for (k in options.headers) {
+        for (var k in options.headers) {
           headers[k] = options.headers[k];
         }
         response.writeHead(200, headers);
@@ -512,7 +512,7 @@
       }
     }
     //Perform upgrade
-    var hash = crypto.createHash("sha1")
+    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" +
@@ -545,7 +545,7 @@
           }
           //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 a6fabfc..8813f91 100644
--- a/lib/nodejs/test/client.js
+++ b/lib/nodejs/test/client.js
@@ -53,7 +53,7 @@
   protocol: protocol
 };
 
-var connection = undefined;
+var connection;
 
 if (program.ssl) {
   options.rejectUnauthorized = false;
diff --git a/lib/nodejs/test/http_client.js b/lib/nodejs/test/http_client.js
index 08a7d27..14c1a29 100644
--- a/lib/nodejs/test/http_client.js
+++ b/lib/nodejs/test/http_client.js
@@ -55,23 +55,22 @@
    path: "/test"
 };
 
-var connection = undefined;
-
 if (program.ssl) {
-  options.rejectUnauthorized = false;
-  connection = thrift.createHttpConnection("localhost", 9090, options);
-} else {
-  connection = thrift.createHttpConnection("localhost", 9090, options);
-}
+  options.nodeOptions = { rejectUnauthorized: false };
+  options.https = true;
+} 
+
+var connection = thrift.createHttpConnection("localhost", 9090, options);
 
 var client = thrift.createHttpClient(ThriftTest, connection);
 
-//connection.on('error', function(err) {
-//  assert(false, err);
-//});
+connection.on('error', function(err) {
+  assert(false, err);
+});
 
 var testDriver = ThriftTestDriver;
 if (program.promise) {
+  console.log("    --Testing promise style client");
   testDriver = ThriftTestDriverPromise;
 } 
 testDriver(client, function (status) {
diff --git a/lib/nodejs/test/http_server.js b/lib/nodejs/test/http_server.js
index d8ef73f..f12e695 100644
--- a/lib/nodejs/test/http_server.js
+++ b/lib/nodejs/test/http_server.js
@@ -54,7 +54,13 @@
     protocol: protocol,                 
     transport: transport 		
 };                                  
-var serverOpt = { services: { "/test": SvcOpt } }                                   
+var serverOpt = { services: { "/test": SvcOpt } };                            
+if (program.ssl) {
+  serverOpt.tls = {
+    key: fs.readFileSync(path.resolve(__dirname, 'server.key')),
+    cert: fs.readFileSync(path.resolve(__dirname, 'server.crt'))
+  };
+}
 thrift.createWebServer(serverOpt).listen(9090);                        		
 
 
diff --git a/lib/nodejs/test/multiplex_client.js b/lib/nodejs/test/multiplex_client.js
index 6580cb5..7b58205 100644
--- a/lib/nodejs/test/multiplex_client.js
+++ b/lib/nodejs/test/multiplex_client.js
@@ -47,9 +47,9 @@
   protocol: protocol
 };
 
-var connection = undefined;
+var connection;
 if (program.ssl) {
-  options.rejectUnauthorized = false
+  options.rejectUnauthorized = false;
   connection = thrift.createSSLConnection('localhost', 9090, options);
 } else {
   connection = thrift.createConnection('localhost', 9090, options);
diff --git a/lib/nodejs/test/multiplex_server.js b/lib/nodejs/test/multiplex_server.js
index 6331f6f..18f9b0d 100644
--- a/lib/nodejs/test/multiplex_server.js
+++ b/lib/nodejs/test/multiplex_server.js
@@ -72,7 +72,7 @@
   options.tls = {
     key: fs.readFileSync(path.resolve(__dirname, 'server.key')),
     cert: fs.readFileSync(path.resolve(__dirname, 'server.crt'))
-  }
+  };
 }
 
 thrift.createMultiplexServer(processor, options).listen(9090);
diff --git a/lib/nodejs/test/server.js b/lib/nodejs/test/server.js
index 6f5abe6..605c700 100644
--- a/lib/nodejs/test/server.js
+++ b/lib/nodejs/test/server.js
@@ -17,11 +17,6 @@
  * under the License.
  */
 
-//Server test for the following I/O stack:
-//    TBinaryProtocol
-//    TFramedTransport
-//    TSocket
-
 var fs = require('fs');
 var path = require('path');
 var thrift = require('thrift');
@@ -61,7 +56,7 @@
   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/testAll.sh b/lib/nodejs/test/testAll.sh
index 87bbb9d..9d1da3f 100755
--- a/lib/nodejs/test/testAll.sh
+++ b/lib/nodejs/test/testAll.sh
@@ -96,5 +96,7 @@
 testHttpClientServer json framed || TESTOK=1
 testHttpClientServer binary buffered || TESTOK=1
 testHttpClientServer binary framed || TESTOK=1
+testHttpClientServer json buffered --promise || TESTOK=1
+testHttpClientServer binary framed --ssl || TESTOK=1
 
 exit $TESTOK
diff --git a/lib/nodejs/test/thrift_test_driver.js b/lib/nodejs/test/thrift_test_driver.js
index a21c9c5..02613ec 100644
--- a/lib/nodejs/test/thrift_test_driver.js
+++ b/lib/nodejs/test/thrift_test_driver.js
@@ -67,6 +67,7 @@
 });

 

 //all Languages in UTF-8

+/*jshint -W100 */

 var stringTest = "Afrikaans, Alemannisch, Aragonés, العربية, مصرى, " +

     "Asturianu, Aymar aru, Azərbaycan, Башҡорт, Boarisch, Žemaitėška, " +

     "Беларуская, Беларуская (тарашкевіца), Български, Bamanankan, " +

@@ -92,6 +93,7 @@
     "Türkçe, Татарча/Tatarça, Українська, اردو, Tiếng Việt, Volapük, " +

     "Walon, Winaray, 吴语, isiXhosa, ייִדיש, Yorùbá, Zeêuws, 中文, " +

     "Bân-lâm-gú, 粵語";

+/*jshint +W100 */

 

 client.testString(stringTest, function(err, response) {

   assert( ! err);

diff --git a/lib/nodejs/test/thrift_test_driver_promise.js b/lib/nodejs/test/thrift_test_driver_promise.js
index b5c1b87..22e2572 100644
--- a/lib/nodejs/test/thrift_test_driver_promise.js
+++ b/lib/nodejs/test/thrift_test_driver_promise.js
@@ -70,6 +70,7 @@
   });

 

 //all Languages in UTF-8

+/*jshint -W100 */

 var stringTest = "Afrikaans, Alemannisch, Aragonés, العربية, مصرى, " +

     "Asturianu, Aymar aru, Azərbaycan, Башҡорт, Boarisch, Žemaitėška, " +

     "Беларуская, Беларуская (тарашкевіца), Български, Bamanankan, " +

@@ -95,6 +96,7 @@
     "Türkçe, Татарча/Tatarça, Українська, اردو, Tiếng Việt, Volapük, " +

     "Walon, Winaray, 吴语, isiXhosa, ייִדיש, Yorùbá, Zeêuws, 中文, " +

     "Bân-lâm-gú, 粵語";

+/*jshint +W100 */

 

 client.testString(stringTest)

   .then(function(response) {

@@ -330,7 +332,7 @@
 client.testException('Xception')

   .then(function(response) {

     assert.equal(err.errorCode, 1001);

-    assert.equal('Xception', err.message)

+    assert.equal('Xception', err.message);

   })

   .fail(function() {

     assert(false);

@@ -365,7 +367,7 @@
   client.testI32(-1)

     .then(function(response) {

         assert.equal(-1, response);

-        test_complete = true

+        test_complete = true;

     })

     .fail(function() {

       assert(false);

@@ -391,4 +393,4 @@
 

   setTimeout(TestForCompletion, retry_interval);

 })();

-}

+};