Details

    • Improvement
    • Status: Closed
    • Major
    • Resolution: Fixed
    • 0.9.2
    • 0.9.2
    • Node.js - Library
    • None
    • all

    Description

      Improve Node Server Library
      =======================

      Background
      ----------------
      In the last 7 months Node.js has gone from one server constructor:
      • createServer()
      to seven, adding:
      • createSSLServer()
      • createMultiplexServer()
      • createMultiplexSSLServer()
      • createHttpServer()
      • createHttpsSSLServer()
      • createWebServer()
      there are really only two implementations:
      • createMultiplexSSLServer()
      • createWebServer()
      Several of these servers have undocumented options and share options objects with other libraries.

      Proposal
      -------------
      1. Remove all of the create signatures except these three:
      o createServer()
      o createMultiplexServer()
      o createWebServer()
      with createServer() implemented by createMultiplexServer(). All signatures will support optional multiplexing and optional SSL/TLS. Eliminate no present functionality and maintain signature compatibility in the three signatures preserved.

      2. Document and standardize all server options and parameters with notes describing any deprecated features being preserved for backward compatibility.

      Motivation
      -------------
      The dispersion of create methods makes it harder for developers to know which server to use and leads to diffusion in the way that options and features are provided. This also complicates testing. Reducing the servers to the two currently supported end point transports (TCP and HTTP) will enforce standardization and simplify adoption. Now is the time to address these issues before the new create signatures show up in a released version.

      Approach to Options
      ----------------------------
      Presently the non-web server options objects may have transport and protocol properties. Undocumented key and cert properties are used to enable the options object to be passed to the Node.js tls and https createServer() methods. This approach requires Apache Thrift options to be visible to the Node.js library methods and vice versa. A better approach might be to place Node.js tls options in a tlsOptions object which is itself a property of the server options object. This will allow any tlsOptions to be passed through to Node.js without concerns for conflicts on the Node.js or Apache Thrift side, now or in the future. The presence of a tlsOptions object can also be used to enable SSL/TLS in the two server implementations rather than having separate functions.

      Would like to know what others think about this.

      Attachments

        1. 0001-updated-nodejs-hello-example.patch
          4 kB
          Randy Abernethy
        2. 0001-nodejs-server-and-option-cleanup.patch
          42 kB
          Randy Abernethy

        Activity

          Given the proposed approach the Node server create signatures would include the following:

          var server = thrift.createServer(processor, handler, options);
          var server = thrift.createServer(processor, options);
          var server = thrift.createServer(options);

          var server = thrift.createMultiplexServer(processor, options);
          var server = thrift.createMultiplexServer(options);

          var server = thrift.createWebServer(options);

          The shared ServerOptions accepted by all servers would look something like this (not all properties are used by all servers but no server should redefine the semantics of any existing property):

          /**

          • @class
          • @name ServerOptions
            *
          • Used to define the settings for a Node.js Apache Thrift Server.
            *
          • @property {object} handler - [optional]The handler object containing Thrift service methods
            * @property {object}

            processor - [optional] Thrift service class or processor generated by

          • the IDL Compiler for the service to serve ("cls" is an alternative name for this property)
          • @property {object} protocol - [optional] protocol to use (defaults to TBinaryProtocol)
            * @property {object}

            transport - [optional] layered transport to use (defaults to TBufferedTransport)

          • @property {object} tlsOptions - [optional] Node.js TLS options, if not present or null a clear channel is used,
            * at least a key and a cert must be defined on this object to use SSL/TLS
            * (see: nodejs.org/api/tls.html for documentation)
            * @property {object}

            services - [optional] an object hash which maps (service URI strings) -> (ServiceOptions objects)

          • @property {string}

            staticFilePath - [optional] Path to serve static files from, if absent or "" static file service is disabled

          • @property {object} headers - [optional] An object hash mapping (header strings) -> (header values), headers are
            * transmitted in response to static file GET requests
            * @property {array} corsOrigins - [optional] Array of CORS origin strings to permit requests from.
            */

            In the context of the WebServer an additional ServiceOptions object is defined to specify per route service I/O stack details:

            /**
            * @class
            * @name ServiceOptions
            * Used to define the I/O stack for a particular service.
            *
            * @property {object}

            handler - [optional] The handler object containing Thrift service methods

          • @property {object} processor - [optional] Thrift service class or processor generated by the IDL Compiler for the service
            * @property {object}

            protocol - [optional] protocol to use (defaults to TBinaryProtocol)

          • @property {object}

            transport - [optional] layered transport to use (defaults to TBufferedTransport)
            */

          codesf Randy Abernethy added a comment - Given the proposed approach the Node server create signatures would include the following: var server = thrift.createServer(processor, handler, options); var server = thrift.createServer(processor, options); var server = thrift.createServer(options); var server = thrift.createMultiplexServer(processor, options); var server = thrift.createMultiplexServer(options); var server = thrift.createWebServer(options); The shared ServerOptions accepted by all servers would look something like this (not all properties are used by all servers but no server should redefine the semantics of any existing property): /** @class @name ServerOptions * Used to define the settings for a Node.js Apache Thrift Server. * @property {object} handler - [optional] The handler object containing Thrift service methods * @property {object} processor - [optional] Thrift service class or processor generated by the IDL Compiler for the service to serve ("cls" is an alternative name for this property) @property {object} protocol - [optional] protocol to use (defaults to TBinaryProtocol) * @property {object} transport - [optional] layered transport to use (defaults to TBufferedTransport) @property {object} tlsOptions - [optional] Node.js TLS options, if not present or null a clear channel is used, * at least a key and a cert must be defined on this object to use SSL/TLS * (see: nodejs.org/api/tls.html for documentation) * @property {object} services - [optional] an object hash which maps (service URI strings) -> (ServiceOptions objects) @property {string} staticFilePath - [optional] Path to serve static files from, if absent or "" static file service is disabled @property {object} headers - [optional] An object hash mapping (header strings) -> (header values), headers are * transmitted in response to static file GET requests * @property {array} corsOrigins - [optional] Array of CORS origin strings to permit requests from. */ In the context of the WebServer an additional ServiceOptions object is defined to specify per route service I/O stack details: /** * @class * @name ServiceOptions * Used to define the I/O stack for a particular service. * * @property {object} handler - [optional] The handler object containing Thrift service methods @property {object} processor - [optional] Thrift service class or processor generated by the IDL Compiler for the service * @property {object} protocol - [optional] protocol to use (defaults to TBinaryProtocol) @property {object} transport - [optional] layered transport to use (defaults to TBufferedTransport) */
          roger Roger Meier added a comment -

          Randy, this is a great idea!

          roger Roger Meier added a comment - Randy, this is a great idea!
          chubinou Pierre Lamot added a comment -

          Hi,

          While I agree that we can (should) re-factor the server interface, I'm not really sure about the way you propose.

          I think that we should try to be more consistent with the API proposed in the other languages. Most other languages (C++, python, C#, D, go, …) propose something like

          ...
          transport = MyTransport()
          transportFactory = MyTransportFactory()
          server = MyServer(processor, transport, transportFactory, protocolFactory)
          server.serve()

          where MyTransport is TServerSocket, TSSLServerSocket, THttpServer, … so the kind of transport is explicit and not implied by options (in the case of SSL). The createXXXserver had the benefit of keeping things explicit and a bit like the other language but it merge the concept of transport and server which is bad, to my mind.

          So, ideally I would:

          • make each transport as its own class, with the same naming scheme as in the other language
          • add a TSimpleServer class (same behavior as the other languages)
          • keep createServer/createMultiplexedServer for backward compatibility with 0.9.1, mark them as deprecated

          regarding tls/https options, I don't really care, I did it that way because options didn't overlapped (I find it more practical to have only one float options map), but i agree that it can become a problem if we (or node tls/https) require new options

          cheers,

          Pierre

          chubinou Pierre Lamot added a comment - Hi, While I agree that we can (should) re-factor the server interface, I'm not really sure about the way you propose. I think that we should try to be more consistent with the API proposed in the other languages. Most other languages (C++, python, C#, D, go, …) propose something like ... transport = MyTransport() transportFactory = MyTransportFactory() server = MyServer(processor, transport, transportFactory, protocolFactory) server.serve() where MyTransport is TServerSocket, TSSLServerSocket, THttpServer, … so the kind of transport is explicit and not implied by options (in the case of SSL). The createXXXserver had the benefit of keeping things explicit and a bit like the other language but it merge the concept of transport and server which is bad, to my mind. So, ideally I would: make each transport as its own class, with the same naming scheme as in the other language add a TSimpleServer class (same behavior as the other languages) keep createServer/createMultiplexedServer for backward compatibility with 0.9.1, mark them as deprecated regarding tls/https options, I don't really care, I did it that way because options didn't overlapped (I find it more practical to have only one float options map), but i agree that it can become a problem if we (or node tls/https) require new options cheers, Pierre

          Hi Pierre,

          Thanks for the comments. I agree, it would be nice to have a conventional Apache Thrift set of end point transports and layering functionality. At present we have a relationship something like:

          • createConnection() == TSocket (or TSSLSocket is if using TLS)
          • createServer() == TServerSocket (or TSSLServerSocket if using TLS)

          It takes createClient() combined with createConnection() to build something akin to the typical "new Client" assembly and the I/O stack has a rigid definition (only 1 layered transport supported). On the server side the event driven nature of Node has trivialized the TXXSevers out of existence. TSimpleServer boils down to a processor.process() read loop in most languages, which there is no need for in the face of the 'data' event. In essence, Node.js == TSimpleServer.

          In Node.js we also have no file or memory end point transports (e.g. TFileTransport and TMemoryBuffer (which should be TMemoryTransport, because it is)). Another shortfall is the lack of a TCompactProtocol. On a total tangent, I also think we should have an Apache Thrift wide VERSION_X of TBinaryProtocol that can transmit little endian if it is native on both sides, falling back to VERSION_1 otherwise (saw Ben mention this the other day as well).

          On the bright side using the create idiom is safer than relying on the user to call "new" in JavaScript and it is an idiom immediately recognizable to a Node.js programmer. Maybe thrift.createTSocket() makes sense. I'm not sure where convention ends and dogma begins in this regard. Node.js trivializes many end point tasks that take a lot of code in C++/Java. We don't want to loose the elegance that Node.js offers.

          I do particularly miss the ability to use more than one layered transport. For example, in C++ you can (assume all the make_shared & shared_ptrs):

          I/O Stack
          TTransport ep_trans1 = new TSocket(localhost, 5050);
          TTransport ep_trans2 = new TSimpleFileTransport(/tmp/blah);
          TTransport l1_trans  = new TFramedTransport(ep_trans1);
          TTransport mux_trans = new TMyMux(l1_trans, ep_trans2);
          TBinaryAutoEndianProtocol proto = new TBinaryAutoEndianProtocol(mux_trans);
          

          To some degree we need to let each language play to its strengths. Interoperability is a must, but after that when do we favor Thrift convention and when do language idioms make more sense? Maybe we need an imperative set of guidelines and a functional set of guidelines. This could tie in with do Scala justice (the imperative approach is no doubt one of the reasons Scrooge and Finagle exist). Would be interesting to hear what folks think is the right idiomatic answer for Apache Thrift in Node.js given a blank canvas.

          Anyway, sounds like you agree with the need for createServer and options clean up. Let me know if not, otherwise I will post a patch which makes these incremental improvements:

          • documenting and centralizing all of the createConnection() and createServer() options
          • refining and documenting the server API to:
            var server = thrift.createServer(processor, handler, options);
            var server = thrift.createMultiplexServer(processor, options);
            var server = thrift.createWebServer(options);
          • making the presence of options.tlsOptions add an SLL/TLS to all three servers

          (without any loss of functionality or breaks in createServer()/createMultiplexServer() backward compatibility of course)

          I will also update all of the tests, kudos to Roger and co for the awesome work on the test front!

          Very best,
          Randy

          codesf Randy Abernethy added a comment - Hi Pierre, Thanks for the comments. I agree, it would be nice to have a conventional Apache Thrift set of end point transports and layering functionality. At present we have a relationship something like: createConnection() == TSocket (or TSSLSocket is if using TLS) createServer() == TServerSocket (or TSSLServerSocket if using TLS) It takes createClient() combined with createConnection() to build something akin to the typical "new Client" assembly and the I/O stack has a rigid definition (only 1 layered transport supported). On the server side the event driven nature of Node has trivialized the TXXSevers out of existence. TSimpleServer boils down to a processor.process() read loop in most languages, which there is no need for in the face of the 'data' event. In essence, Node.js == TSimpleServer. In Node.js we also have no file or memory end point transports (e.g. TFileTransport and TMemoryBuffer (which should be TMemoryTransport, because it is)). Another shortfall is the lack of a TCompactProtocol. On a total tangent, I also think we should have an Apache Thrift wide VERSION_X of TBinaryProtocol that can transmit little endian if it is native on both sides, falling back to VERSION_1 otherwise (saw Ben mention this the other day as well). On the bright side using the create idiom is safer than relying on the user to call "new" in JavaScript and it is an idiom immediately recognizable to a Node.js programmer. Maybe thrift.createTSocket() makes sense. I'm not sure where convention ends and dogma begins in this regard. Node.js trivializes many end point tasks that take a lot of code in C++/Java. We don't want to loose the elegance that Node.js offers. I do particularly miss the ability to use more than one layered transport. For example, in C++ you can (assume all the make_shared & shared_ptrs): I/O Stack TTransport ep_trans1 = new TSocket(localhost, 5050); TTransport ep_trans2 = new TSimpleFileTransport(/tmp/blah); TTransport l1_trans = new TFramedTransport(ep_trans1); TTransport mux_trans = new TMyMux(l1_trans, ep_trans2); TBinaryAutoEndianProtocol proto = new TBinaryAutoEndianProtocol(mux_trans); To some degree we need to let each language play to its strengths. Interoperability is a must, but after that when do we favor Thrift convention and when do language idioms make more sense? Maybe we need an imperative set of guidelines and a functional set of guidelines. This could tie in with do Scala justice (the imperative approach is no doubt one of the reasons Scrooge and Finagle exist). Would be interesting to hear what folks think is the right idiomatic answer for Apache Thrift in Node.js given a blank canvas. Anyway, sounds like you agree with the need for createServer and options clean up. Let me know if not, otherwise I will post a patch which makes these incremental improvements: documenting and centralizing all of the createConnection() and createServer() options refining and documenting the server API to: var server = thrift.createServer(processor, handler, options); var server = thrift.createMultiplexServer(processor, options); var server = thrift.createWebServer(options); making the presence of options.tlsOptions add an SLL/TLS to all three servers (without any loss of functionality or breaks in createServer()/createMultiplexServer() backward compatibility of course) I will also update all of the tests, kudos to Roger and co for the awesome work on the test front! Very best, Randy

          This patch reduces the node.js createServer() signature count to 3:

          var server = thrift.createServer(processor, handler, options);
          var server = thrift.createMultiplexServer(processor, options);
          var server = thrift.createWebServer(options);

          This eliminates a lot of redundant code and simplifies the Apache Thrift Node.js UI quite a bit, also adds jsdoc documentation for all of the exported interfaces. I think this is a worthy improvement but still leaves room for discussion of a more Thrift like stack (TSocket, etc.) addition.

          There is a single ServerOptions object shared by all three create functions. The ServerOptions object has a tls attribute which if present causes all three create functions to add ssl to the underlying transport. The tls object is not used by the Apache Thrift lib but passed directly to Node.js, allowing any legal Node.js tls options to be used (key and cert at a minimum).

          I had not used the multiplexer in JavaScript until today. I found the tests not working. They passed but the client side was not actually working (connect is never invoking the processor). I removed the dedicated multiplexer test and replaced it with the shared thrift/lib/nodejs/test/test_driver.js. These were essentially identical except that the prior declared success after a 3 second wait regardless. I cloned the master to make sure, but it is definitely broken at the current HEAD. I'll create a separate ticket for this.

          This patch also includes assorted minor code cleanup and comments. The node.js and js tests all ran clean for me post this patch (with the mux exception mentioned).

          codesf Randy Abernethy added a comment - This patch reduces the node.js createServer() signature count to 3: var server = thrift.createServer(processor, handler, options); var server = thrift.createMultiplexServer(processor, options); var server = thrift.createWebServer(options); This eliminates a lot of redundant code and simplifies the Apache Thrift Node.js UI quite a bit, also adds jsdoc documentation for all of the exported interfaces. I think this is a worthy improvement but still leaves room for discussion of a more Thrift like stack (TSocket, etc.) addition. There is a single ServerOptions object shared by all three create functions. The ServerOptions object has a tls attribute which if present causes all three create functions to add ssl to the underlying transport. The tls object is not used by the Apache Thrift lib but passed directly to Node.js, allowing any legal Node.js tls options to be used (key and cert at a minimum). I had not used the multiplexer in JavaScript until today. I found the tests not working. They passed but the client side was not actually working (connect is never invoking the processor). I removed the dedicated multiplexer test and replaced it with the shared thrift/lib/nodejs/test/test_driver.js. These were essentially identical except that the prior declared success after a 3 second wait regardless. I cloned the master to make sure, but it is definitely broken at the current HEAD. I'll create a separate ticket for this. This patch also includes assorted minor code cleanup and comments. The node.js and js tests all ran clean for me post this patch (with the mux exception mentioned).

          This patch adds updates for the thrift/lib/nodejs/examples hello example.

          codesf Randy Abernethy added a comment - This patch adds updates for the thrift/lib/nodejs/examples hello example.
          chubinou Pierre Lamot added a comment -

          Hi,

          Sorry for the late response, I was out for a couple of days:

          The patch looks good to me but there is stills some things that bugs me:

          • the createXXXServer returns an objet on which we call listen(port) to start it. This object is the actual object returned by (net/tls/http/https).createServer, this is an object from the nodejs core on which we have no control on its interface and might evolve against our will (I admit this is unlikely), on an other side the port parameter given to the listen methods might be irrelevant for an other kind of transport (a memory transport for instance, I would have said unix socket too, but they do allow to give a socket path for the port parameter _), for me the port parameter should be an option of the transport
          • createServer/createSSLServer: I have mixed feelings about this, because:
            • many languages do the distinction
            • the distinction does exists in the node core library, different modules whether the ssl layer is used or not (http <=> https, net <=> tls)
            • ssl might not be present in some future transport (I don't really know if it makes sense, just thinking of it)
            • However I must admit that I rather like to have ssl as an option than a different class, but I want to be sure that we make a good choice.
          • createServer/createConnection: It feel misleading to me that theses functions are about socket servers/client. A createSocketServer/CreateSocketConnection seems more explicit to me.

          In the end most of theses remarks are mainly a matter of taxonomy but I believe it has its importance since the choice we made will require to be maintained in futures version of thrift.

          Best regards,

          Pierre

          chubinou Pierre Lamot added a comment - Hi, Sorry for the late response, I was out for a couple of days: The patch looks good to me but there is stills some things that bugs me: the createXXXServer returns an objet on which we call listen(port) to start it. This object is the actual object returned by (net/tls/http/https).createServer, this is an object from the nodejs core on which we have no control on its interface and might evolve against our will (I admit this is unlikely), on an other side the port parameter given to the listen methods might be irrelevant for an other kind of transport (a memory transport for instance, I would have said unix socket too, but they do allow to give a socket path for the port parameter _ ), for me the port parameter should be an option of the transport createServer/createSSLServer: I have mixed feelings about this, because: many languages do the distinction the distinction does exists in the node core library, different modules whether the ssl layer is used or not (http <=> https, net <=> tls) ssl might not be present in some future transport (I don't really know if it makes sense, just thinking of it) However I must admit that I rather like to have ssl as an option than a different class, but I want to be sure that we make a good choice. createServer/createConnection: It feel misleading to me that theses functions are about socket servers/client. A createSocketServer/CreateSocketConnection seems more explicit to me. In the end most of theses remarks are mainly a matter of taxonomy but I believe it has its importance since the choice we made will require to be maintained in futures version of thrift. Best regards, Pierre

          Hey Pierre,

          Great stuff. I agree that we should return a "thrift" object wrapping the Node.js server. Much better that.

          The port thought I agree with as well. The Apache Thrift Node.js lib is presently very intertwined with the Node.js lib around the end point, client and server.

          I agree on the taxonomy front as well, though some of the things we're talking about are not only taxonomy but structural convention. Standardized conventions are a valuable thing, especially in the context of a framework. A great way to cause your users to create unintentional bugs is to change your conventions within a lib or framework.

          Let's keep working on this stuff. The clear patch I see presently is the server return improvement, one of us should create a new issue for that. We can keep thinking about the best way to bring Node.js in line with the Apache Thrift abstract I/O stack model and push it in the right direction piece by piece.

          Best,
          Randy

          codesf Randy Abernethy added a comment - Hey Pierre, Great stuff. I agree that we should return a "thrift" object wrapping the Node.js server. Much better that. The port thought I agree with as well. The Apache Thrift Node.js lib is presently very intertwined with the Node.js lib around the end point, client and server. I agree on the taxonomy front as well, though some of the things we're talking about are not only taxonomy but structural convention. Standardized conventions are a valuable thing, especially in the context of a framework. A great way to cause your users to create unintentional bugs is to change your conventions within a lib or framework. Let's keep working on this stuff. The clear patch I see presently is the server return improvement, one of us should create a new issue for that. We can keep thinking about the best way to bring Node.js in line with the Apache Thrift abstract I/O stack model and push it in the right direction piece by piece. Best, Randy

          +1, Randy, please feel free to commit it once you get everything setup and welcome aboard
          Not sure about the multiplexer, that's a shame!

          Pierre: Yeah, you're totally right, we should have the port number as a transport option. Also for the naming, we could easily make everything just like the c++ or Java implementations. That's probably the correct way to go.

          Thank you guys for the improvements and ideas!

          henrique Henrique Mendonca added a comment - +1, Randy, please feel free to commit it once you get everything setup and welcome aboard Not sure about the multiplexer, that's a shame! Pierre: Yeah, you're totally right, we should have the port number as a transport option. Also for the naming, we could easily make everything just like the c++ or Java implementations. That's probably the correct way to go. Thank you guys for the improvements and ideas!
          hudson Hudson added a comment -

          SUCCESS: Integrated in Thrift #1098 (See https://builds.apache.org/job/Thrift/1098/)
          THRIFT-2398:Improve Node Server Library\nClient: Node\nPatch: Randy Abernethy\n\nGeneral server parameter harmonization and comments (randy: rev d60f9789dbbe5e8fb05815eb01cc5213f811ea9b)

          • lib/nodejs/test/test_handler_promise.js
          • lib/nodejs/test/client.js
          • lib/js/test/server_https.js
          • lib/nodejs/lib/thrift/index.js
          • lib/nodejs/lib/thrift/web_server.js
          • lib/nodejs/test/server.js
          • lib/nodejs/test/test_handler.js
          • lib/js/test/server_http.js
          • lib/nodejs/test/multiplex_client.js
          • lib/nodejs/lib/thrift/server.js
          • lib/nodejs/test/multiplex_server.js
          hudson Hudson added a comment - SUCCESS: Integrated in Thrift #1098 (See https://builds.apache.org/job/Thrift/1098/ ) THRIFT-2398 :Improve Node Server Library\nClient: Node\nPatch: Randy Abernethy\n\nGeneral server parameter harmonization and comments (randy: rev d60f9789dbbe5e8fb05815eb01cc5213f811ea9b) lib/nodejs/test/test_handler_promise.js lib/nodejs/test/client.js lib/js/test/server_https.js lib/nodejs/lib/thrift/index.js lib/nodejs/lib/thrift/web_server.js lib/nodejs/test/server.js lib/nodejs/test/test_handler.js lib/js/test/server_http.js lib/nodejs/test/multiplex_client.js lib/nodejs/lib/thrift/server.js lib/nodejs/test/multiplex_server.js

          committed node server clean up and examples update

          codesf Randy Abernethy added a comment - committed node server clean up and examples update
          hudson Hudson added a comment -

          FAILURE: Integrated in Thrift #1100 (See https://builds.apache.org/job/Thrift/1100/)
          THRIFT-2398:Improve Node Server Library (ra: rev a2e4e565983d247fbcf634a52ddb85cec872e96f)

          • lib/nodejs/README.md
          • lib/js/test/server_http.js
          • lib/nodejs/test/test_handler.js
          • lib/nodejs/examples/hello.html
          • lib/nodejs/test/test_handler_promise.js
          • lib/js/test/server_https.js
          • lib/nodejs/examples/hello.js
          hudson Hudson added a comment - FAILURE: Integrated in Thrift #1100 (See https://builds.apache.org/job/Thrift/1100/ ) THRIFT-2398 :Improve Node Server Library (ra: rev a2e4e565983d247fbcf634a52ddb85cec872e96f) lib/nodejs/README.md lib/js/test/server_http.js lib/nodejs/test/test_handler.js lib/nodejs/examples/hello.html lib/nodejs/test/test_handler_promise.js lib/js/test/server_https.js lib/nodejs/examples/hello.js

          People

            codesf Randy Abernethy
            codesf Randy Abernethy
            Votes:
            1 Vote for this issue
            Watchers:
            4 Start watching this issue

            Dates

              Created:
              Updated:
              Resolved: