• 7.8.1 核心模块(续)

    除了Buffer模块比较特殊, 放在包limax.node.js内, 其它核心模块均放置在包limax.node.js.modules内, 一个.java文件一个.js文件配对, 按照java习惯, 首字母大写, require时全小写。

    • Events(事件)

      提供了完全的java实现,js只是一个简单包装。

      setMaxListeners方法实际上被忽略, 不限制listener数量, getMaxListeners永远返回Integer.MAX_VALUE。node.js手册的提法是, 限制listener数量有助于寻找内存泄露, java环境下无需考虑这个问题。


    • File System(文件系统)

      使用java.nio.file包实现,与node.js实现有稍许差异。

      • 1. fs.Stats 差异,java能够获取的信息与struct stat有差异,可以用如下代码dump具体信息进行比较。


        fs.stat(file, console.log);
        
      • 2. java不提供硬连接能力,所以fs.link,fs.linkSync不提供。

      • 3. fs.watch,改为通过绝对路径调用callback,便于使用单一listener监听多个目录,options.recursive无效,java不提供支持,node.js手册对options.encoding的描述上下文矛盾。

      • 4. fs.readFile,fs.readFileSync,fs.writeFile,fs.writeFileSync,fs.appendFile,fs.appendFileSync,这6个方法的options参数不支持对象,只作为encoding串进行Buffer.isEncoding测试,测试失败直接默认为UTF8。否则逻辑上存在大量矛盾,比如执行writeFile,提供flag=’r’,纯属自找麻烦。

      • 5. fs.mkdtemp,fs.mkdtempSync,这两个方法的options参数看不出任何意义,忽略;prefix要求一个不带路径分隔符的串,生成用户临时目录下的文件名,避免创建临时文件出现权限问题。

      • 6. fs.constants实际上是一java对象,只有R_OK,W_OK,X_OK,F_OK这4个值。

      • 7. java.nio.file并不支持文件描述符,所以open返回的文件描述符实际上是内部伪造的用来索引FileChannel的整数。


    • Global(全局变量)

      node.js手册描述的全局变量全部支持,require相关成员做了扩充,增加了parameters,java,两个模块级全局变量,详见模块一节。


    • HTTP

      • 1. http.Agent构造参数options中options.keepAlive被忽略, Agent始终支持HTTP/1.1的keep-alive。 options.maxSockets没有明显意义被忽略。 options.keepAliveMsecs被重新解释为连接idle超时, 超时以后连接被关闭, 默认为60000ms, 多数NAT环境TCP IDLE默认5分钟, 不会存在问题, node.js文档中默认的tcp-keepalive=1000ms的配置, linux上通过设置tcp选项TCP_KEEPINTVAL才能实现,不具有通用性。

      • 2. http.Request中,http.setSocketKeepAlive第二个参数initialDelay,被忽略,jdk不支持这个参数,也不知道哪个协议栈能支持这样的行为。


    • HTTPS

      Node.js的设计方式过于冗余,Limax版本直接在HTTP模块中实现(实际上不存在https这个模块),只需要简单修订2个方法。(TLS的相关内容详见TLS/SSL一节)

      1. http.createServer([requestListener])扩展为http.createServer([requestListener], [tls]),其中,tls为TLS配置信息,只要提供了TLS配置,则服务器启动为HTTPS服务器,如果配置要求客户端认证,http.IncomingMessage上可以读取属性socket.tlsPeerPrincipal与socket.tlsPeerCertificateChain,分别获得客户端证书的主题与证书链。

      2. http.ClientRequest的构造参数options中,增加options.tls属性,如果设置了该属性,使用HTTPS发送客户端请求,如果没有提供该字段,检查options.protocol是否为’https:’如果是,使用HTTPS发送客户端请求,这种情况下,使用JDK提供的cacerts中的受信任根证书验证服务器,指定SNIHostName为连接的服务器名,不支持客户端认证,不检查证书撤销状态。


      示例:最简单的https客户端:


      var http = require(‘http’)
      var req = http.get('https://www.alipay.com', function(res) {
      	res.pipe(process.stdout);
      });
      req.once('socket', function(socket) {
      	socket.once('tlsHandshaked', function() {
      		for (var i = 0; i < arguments.length; i++)
      			console.log(arguments[i].toString());
      	});
      })
      

      最后一条语句,通过在socket上监听tlsHandshaked消息可以获得服务器证书的主题以及证书链。


    • Module(模块)

      在实现node.js文档描述功能之外,添加了更多功能。

      • 1. require(module[, parameter1[, parameter2,…]])

        允许向模块传递启动参数,因为模块默认被cache了,所以第一次传入的启动参数才有意义。

      • 2. require.reload(module[, parameter1[, parameter2,…]])

        重新加载模块,不存在C++插件可能导致的麻烦,所有模块都允许重新加载。

      • 3. require.launch(module[, parameter1[, parameter2,…]])

        在新线程中启动新的Nashorn虚拟机,加载模块,被加载的模块是main模块,这个方法可以实现Cluster描述的行为。

      • 4. 使用不包含路径前缀(./,../,/)的名字加载模块, 按核心模块名搜索失败之后, 按Node模块加载之前, 插入一个java模块加载步骤, 如果成功, 直接返回该模块。例如, 当前ClassLoader内存在一个模块类app.Jmodule,require(‘app.Jmodule’),即可加载该模块,优先于Node模块加载。

      • 5. 全局目录加载位置不包括$PREIFX/lib/node,因为没有合适地方配置node_prefix。

      • 6. 加载模块时的添加的外层wrapper,添加了parameters,java两个参数。


        (function (exports, require, module, __filename, __dirname, parameters[, java]) {
            // your module code
        });
        

        parameters为require传入的参数Array。

        实现java插件时,java变量为关联的java对象实例,js通过该变量访问对应的java对象;其他模块类型,java变量为undefined。

      • 7. require.extensions, 尽管被node.js文档废弃了, 实际上用户可以用来扩展更强的模块支持能力, 所以保留, 该对象中key为文件扩展名,value为function(path, parameters); 这里的path为模块文件绝对路径, parameters为require传入的参数。废弃这样一个扩展性,不知道设计者怎么考虑的。

      • 8. node.js经典示例的Cluster版本实现:

        example.js


        if (--parameters[0] > 0)
            require.launch(__filename, parameters[0]);
        var http = require('http');
        var hostname = '127.0.0.1';
        var port = 3000;
        var Thread = Java.type('java.lang.Thread');
        var server = http.createServer(function(req, res) {
            res.statusCode = 200;
            res.setHeader('Content-Type', 'text/plain');
            res.end('Hello World ' + Thread.currentThread() + '\n');
        });
        server.listen(port, hostname, function() {
            console.log('server launch');
        });
        

        java –jar limax.jar node example.js 3 启动服务器, 可以看见启动了3个服务器实例, 关键是前两行, require.launch与require参数配合即可提供Cluster支持。用浏览器访问http://127.0.0.1:3000/可以看见服务线程名, 刷新浏览器, 服务线程名没有改变, 体现了Keep-Alive特性,重启浏览器访问,可以看见线程名发生了改变,体现了Cluster特性。

      • 9. 虚拟机线程间通讯, 是实现Cluster的基础, 可以参考limax.node.js.module.Net的实现, 基本方法就是, 实现自己的插件, 通过类静态成员进行数据交换, 正确使用同步即可。


    • Net(网络)

      该模块使用java.nio.channels中的异步IO支持类实现,与js的异步特性正好对应。

      • 1. java本身只支持TCP服务,不支持LocalDomain服务。

      • 2. server.listen端口bind失败会直接报告异常,node.js不会,反复重试,应该是为了支持Cluster达成的妥协。

      • 3. node.js文档要求server.listen可以执行多次,应该也是为了支持Cluster达成的一种妥协,这里不允许。

      • 4. node.js文档中提及server.maxConnections在Cluster环境下没有意义, limax实现重新找回了这个成员的意义。ServerChannel实际上全局管理的, accept获得的channel向各个虚拟机轮转投递, 某个虚拟机中的maxConnections限制超出以后, 就向下一个虚拟机投递, 所有虚拟机都拒绝接受该channel才关闭该channel, 所以配置这个选项和Cluster数量配合, 可以实现更合理的负载均衡。

      • 5. socket.KeepAlive方法的第二个参数initialDelay,仍不明确。

      • 6. node.js文档中提供的ECHO服务器与客户端,尽管多数时候都能正确运行,实际上并不合理,任何时候都必须明确TCP是流,不应该假设一个报文就能承载所有数据,即便数据量很小。


    • Os(操作系统)

      理论上, 既然应用运行在java虚拟机上, 就不应该再考虑操作系统层面的问题, 该模块仅提供java虚拟机能提供的信息, 包括os.arch(), os.endianess(), os.homedir(), os.hostname(), os.networkInterfaces(), os.platform(), os.release(), os.tmpdir(), os.type(), os.userInfo(), 某些方法返回的信息与node.js文档提到的可能有少许差异, 比如x64, 在这里可能是amd64。


    • Path(路径)

      与node.js文档基本一致,除了path.win32等同于path.posix,应该说都等同于path.java,不需要过多考虑系统相关问题,java虚拟机层面解释即可。


    • Process(进程)

      java虚拟机中不需要太多进程相关特性, 所以这个模块仅提供process.chdir(), process.cwd(), process.env, process.exit(), process.exitCode, process.hrtime(), process.memoryUsage(), process.nextTick(), process.platform, process.stderr, process.stdin, process.stdout, 其中process.memoryUsage()返回的内容是Java虚拟机的内存使用情况, 与node.js描述完全不同, process.nextTick()完全用setImmediate()实现。


    • Punycode

      既然node.js模块自己都废弃了,不实现。


    • Query String(查询字符串)

      纯js代码,完整实现。


上一页 下一页