• 7.8.1 Core module (Cont2)

    Except that the Buffer module is quite special and put in the limax.node.js package, the other code modules are all put in the limax.node.js.modules package, and make a pair based on one .java file and one .js file, and the first letter is capital and all letters are lowcase when requiring according to the Java custom.

    • Readline

      Too many terminal-related behaviors, rarely use, and is not yet implemented.


    • REPL

      Rarely use, and not yet implemented.


    • Stream

      Core of the core, pure js code, is completely implemented. As described in the node.js document, flow model is prefered.


    • String Decoder

      Use java.nio.charset.CharsetDecoder to implement.


    • Timer

      The tools with many usefulness is completely implemented.


    • TLS/SSL

      The design of Node.js is too redundant, so the Limax version directly implements it in the Net module. (Actually, there is no tls module.) The implementation of the Limax version not only supports the TLSSocket way of Node.js, but also supports some actual network protocols using the STARTTLS way and allowing to start and stop TLS on non-secure connection.


      The expansion of Net module

      1. net.Server class

      1.1. The options.tls property is added into the constructor parameter options. If this property is set, the server is launched according to the TLSSocket server way. (the using way of http module)

      2. net.Socket class

      2.1. The options.tls property is added into the constructor parameter options. If this property is set, the client is launched according to the TLSSocket client way. (the using way of the http module)

      2.2. The method socket.starttls(tls) uses the tls configuration information to launch TLS on the non-secure connection. After starttls, the data is allowd to be sent immediately. In fact, the data will be sent after the tls handshake is completed.

      2.3. The method socket.stoptls([function()]) ends the TLS session and returns to the non-secure session state. If a callback function is passed in, this function is called when the tlsDown message is triggered as described in the tlsDown message. In general, if it needs to design a STARTTLS class application, it must correctly design the STOPTLS negotiation mechanism and perform a non-secure connection session after the TLS ends handshake. The Limax implementation ensures that no data is lost. Due to some asynchronous features, if the data is sent immediately after stoptls, the data might be sent in the secure session, or sent after the secure session ends.

      2.4. The method socket.tlsrenegotiate() starts the TLS renegotiation. This method only sets a mark to indicate the handshake again in the next socket activity, so there is no any error to return.

      2.5. The message tlsHandshakes is triggered after the handshake is completed and the parameter mode is function(principal, cert0, cert1 …). The properties socket.tlsPeerprincipal and socket.tlsPeerCertificateChain might be directly read after the message is triggered to obtain the certificate and certificate chain of the subject, among them the socket.tlsPeerPrincipal is javax.security.auth.x500.X500Principal type, socket.tlsPeerCertificateChain is js array and the member type is java.security.cert.X509Certificate.

      2.6. The message tlsDown is triggered after the TLS session ends and the parameter mode is function(err). If if(err) is true, which means the session is caused by some exceptionand the err.printStackTrace() may get the exception information. After the exception, the user should call socket.destroy(err) itself to close the network connection. (Please reference the implementation of the http module.)

      3. The net.createTLS(function(c)) creates the TLS configuration. The parameter is a callback function and c is a java object with limax.node.js.modules.tls.TLSConfig as type. All the configuration must be called on the corresponding method of the c called by this callback, which could minimize the exception propagation.

      3.1. The configuration of trust detection class must be set on the client. If the server needs to verify the client, it also must be set.

      3.1.1. The c.addAllCA() addes all the trusted certificates in the cacerts file of jdk.

      3.1.2. The c.addTrustCertificate(data) addes the trusted certificate, the type of the data may be the String or Buffer, and the content may be the file path or actual content. The format of the actual content may be the PEM or DER. There may be one certificate, or multiple certificates, or PKCS7 certificate.

      3.1.3. The c.addCRL(data) addes the CRL list. The description of the data is the same as above.

      3.1.4. The c.setRevocationEnabled(revocationEnabled) method configures whether to allow the CRL test and the revocationEnabled is boolean type. If it is not allow, the CRL list added through c.addCRL has no meaning. If it is allowd, the virtual machine parameter com.sun.security.enableCRLDP=true must be added when launching the virtual machine, otherwise the test (such as OCSP) which need to be performed over the network will fail. In most cases, the CRL test is extremely time consuming and the default is false.

      3.1.5. c.setTrustChecker(function(chain, exception)), the chain is the certificate chain represented with java array, and the exception is the exception thrown after the above configuration detection fails. This method returns true, indicating that the certificate chain is accepted and the TLS handshake process can continue. Usually, this implementation is used when the browser accesses https site, finds some certificates exception and prompts the user whether to continue. The complex certificate chain detection should forward the chain and exception to java module implementd by the user itself.

      3.1.6. The c.setPositiveTrustChecker(function(chain, exception)) performs the positive certificate chain detection. The description of the paramter is the same as above. Using this method to configure will ingore the detection to the above configuration and directly use the set detector. This method can be used when the detection ability of the above configuration does not match the requirement. In addition, using this method to set the detector which always returns true could assist in some certificates to debug on the configuration.

      3.2. The configuration of the server certificate private key, and the configuration of the client certificate private key in the condition that requres the client verification.

      3.2.1. The method c.addPKCS12(data, pass) addes the PKCS12 certificate package. The type of data may be the String or Buffer, the content may be the file path or the actual content of the PKCS12 certificate package, the type of pass may be the String or Buffer and the content may be the password of the PKCS12 certificate package.

      3.2.2. The method c.addPrivateKeyAndCertificatePack(pkey, cert, pass) addes the private key and corresponding certificate chain, in fact, which is the information required when generating the PKCS12 certificate package. All parameters are allowed to be String or Buffer, in particular, the actual content related to pkey requires a variety of private format represened by PEM format, RSA, DSA, EC or PKCS8. The pass provides the password of private key. If the private key does not set the password, the pass is ignored. The requirement of the cert is the same as the previous c.addTrustCertificate(data).

      3.3. TLS engine configuration

      3.3.1. c.setProtocol(protocol), protocol is the String type, and allows SSLv3, TLSv1, TLSv1.1, TLSv1.2, and default is TLSv1.2.

      3.3.2. c.setNeedClientAuth(enable), enable is boolean type and default is false. When it is set as true, the client is forced to provide the certificate to verify.

      3.4. Virtual service related configuration

      3.4.1. c.setSNIHostName(hostname), hostname is String type or Buffer. The client calles this method to set the required virtual server name.

      3.4.2. c.addSNIServerName(type, name), type is int type, and name is Buffer type. The client calles this method to add the required virtual server name accroding to the type. c.setSNIHostName actually provides type=0. All these type can not be repeated, otherwise there is exception thrown.

      3.4.3. The server does not use the SNIMatcher mechanism prvoided by JDK to implement. The method it used is that if the server-side configuration addes multiple private key certificate package, it launches the virtual server mode. The SNIServerName uses the domain name template set by CN in the certificate Subject and domain name template of dNSName type in SubjectAlternativeNames for pattern matching and selecting the correct certificate chain and private key. If all the matching is not successful, the pkixKeyManager of JDK selects a pair of certificate chain and private key. The reason for this implementation is that the program provided by JSSE is not complete and has logic bug. The method javax.net.ssl.ExtendedSSLSession.getRequestedServerNames() could get the server name reqiured by the other side. The problem is that when selecting the certificate in the handshake phase, this method returns empty set and retruns the certificate required by the client until the handshake completes.


      For example:

      server

      testtlsserver.js


      var net = require('net')
      var tls = net.createTLS(function(c) {
      	c.addPKCS12('/work/js.test/testtls.p12', '123456');
      	c.addTrustCertificate('/work/js.test/testtls.cer');
      	c.setNeedClientAuth(true);
      });
      var server = net.createServer(function(c) {
      	c.on('error', function(e){
      		console.log('error', e.stack)
      		e.printStackTrace();
      	});
      	c.once('tlsHandshaked', function() { 
      		console.log('tlsHandshaked:');
      		for (var i = 0; i < arguments.length; i++)
      			console.log(arguments[i].toString());
      	});
      	c.once('tlsDown', function(err) { 
      		console.log('tlsDown'); 
      		if (err)
      			client.destroy(err)
      	})
      	c.on('end', function() {
      		console.log('client disconnected');
      	});
      	c.starttls(tls);
      	c.write('hello\r\n');
      	c.pipe(c);
      	c.pipe(process.stdout)
      });
      server.on('error', function(err) {
      	throw err;
      });
      server.listen(8124, function() {
      	console.log('server bound');
      });
      

      Client

      testtlsclient.js


      var net = require('net')
      var tls = net.createTLS(function(c) {
      	c.addPKCS12('/work/js.test/testtls.p12', '123456');
      	c.addTrustCertificate('/work/js.test/testtls.cer');
      });
      var client = net.createConnection({
      	port : 8124
      }, function() {
      	client.starttls(tls);
      	console.log('connected to server!');
      	client.write('world!\r\n', function() {
      		print('send done')
      		client.stoptls(function() {
      			print('stopped');
      			client.write('wwww?');
      		});
      	});
      });
      client.on('tlsHandshaked', function() {
      	console.log('tlsHandshaked:');
      	for (var i = 0; i < arguments.length; i++)
      		console.log(arguments[i].toString());
      });
      client.on('tlsDown', function(err) {
      	console.log('tlsDown')
      	if (err)
      		client.destroy(err)
      });
      client.on('data', function(data) {
      	data = data.toString();
      	console.log(data);
      	if (data[data.length - 1] == '?')
      		client.end();
      });
      client.on('close', function() {
      	console.log('disconnected from server');
      });
      client.on('error', function(e) {
      	console.log('error', e.stack)
      	e.printStackTrace();
      })
      

      1. This example makes the TLS modification on the server and client with ECHO function, which requires the client to provide the authentication. The client and server simply uses the same private key and certificate, testtls.p12 and tetstls.cer, which could be implemented according to the example in the Node.js document.

      2. The client immediately sends the "world\r\n" to the server after STARTTLS. This string can be defined to send in the TLS session. The client immediately STOPTLS after the sending completes. When stop is completed, the client sends the string "wwww?" and defines this string to send in the non-secure session. These two strings are finally ECHO back by the server.

      3. After the server accept the client connection, it immediately STARTTLS on this connection, and then sends "hello\r\n" to the client, defining that this string is sent in the TLS session. After the server receives the "world\r\n", it ECHO "world\r\n" to the client. It should be noted here that the server does not need to concert whether the client STOPTLS. The switch is completed by the bottom level and the ECHO of "wwww?" is completed in the non-secure session. Logically, even though "world\r\n" is received in the TLS session, it should not assume whether the data returned by ECHO is sent in the TLS session, because the client is immediately STOPTLS after sending data. Seen from here that the application level must carefully consider the handshake mechanism of the STOPTLS when designing protocl like the STARTTLS.

      4. Run the example and observer the effect of the tlsHandshaked and tlsDown two messages.


    • TTY

      The terminal is rarely used and not yet implemented.


    • UDP/Datagram

      • 1. In theory, the UDP server can not provide the Cluster ability. If launching with Cluster mode, the exception that address has been used will be generated when the following server binds. Currently, the largest usage of UDP is multicast in the LAN environment to achieve inter-server collaboration, and does not need to carry too heavy load. In heavy load condition, it could be designed as a virtual machine to send and receive message, distributing the tasks to service Cluster through inter-thead communication.

      • 2. The implementation of socket.addMembership(multicastAddress[, multicastInterface]) is different with the description of node.js. In the Limax implementation, the default multicastInterface behavior is to search all interfaces which are UP and have the multicast ability to bind. The interface name of different system is too different, and it is problem how to choose the correct name and is difficult to ensure correctness. Now, the computer network interface is too much, so that it is not reliable to select an interface by operating system itself because it can not ensure that the selected one is the wanted. So the java.nio.channels.MulticastChannel interface does not provide the automatic select ability.

      • 3. The socket.setTTL(ttl) is equivalent to the socket.setMulticastTTL(ttl), because only one TTL setting method is provided on DatagramChannel.


    • URL

      Completely implement. There is problem after carefully reading node.js document that the HTTP module uses this URL parser. The urlObject.host returned by this parser is not the host required by HTTP module, and the host required by HTTP module document is actually urlObject.hostname here. The basic data dictionary is not unified and not clear how to plan.


    • Util

      Provide util.format(), util.inherits() and util.inspect() three methods. The util.format() uses java.lang.String.format to format the parameters, and the format parameter is a little different with the sprintf format parameter. The inspect() is used to recursively display the object data and does not support the feature such as displaying color. Actually, the output string of console.log is extracted by inspect.


    • V8

      Nashorn, not related to V8.


    • VM

      • 1. All the methods' options are related to V8 and ignored.

      • 2. The DebugContext is V8 feature, so the vm.runInDebugContext() is implemented as the alias of vm.runInNewContext().

      • 3. Not suggest to use the existing sandbox to create Context. First sandbox = vm.createContext() then initiate the sandbox, which helps to improve the performance. This is the limit of Nashorn. When the non-global object is setting on the Context to access, a nashorn.global member will be added on this object as the real global object to access. In order to ensure the correctness, a copy must be executed after the completion of the implementation.


    • ZLIB

      The node.js uses zlib to implement, and the limax uses java.util.zip to implement.

      • 1. The java.util.zip does not provide too many constants for configuration. In most cases, the options are not required and the default implemention is enough.

      • 2. In the example about time push server in node.js document, the usage of flush does not match the prototype defined by the document itself. The callback parameter in the prototype is mandatory, but not supply in the example.

      • 3. When experimenting the time push server example, it is best to change the gzip algorithm to deflate. Because the cache of java.util.zip.GZIPOutputStream is too big, the result is that the server does not respond for a long time.


Prev Next