[Node] 工具类API

1. 加密


1.1 Hashing

Node的加密算法是以OpenSSL库为基础的,所以需要在编译Node的时候指定添加OpenSSL支持,才能使用加密算法。

要在Node里使用哈希,需要调用工厂方法crypto.createHash()来创建一个Hash对象。它会返回指定哈希算法的Hash新实例,几个常见的算法有:md5、sha1、sha256、sha512、ripemd160。

在哈希中使用数据时,可以调用hash.update()来生成数据摘要。可以用更多的数据不停地更新哈希,直到需要把它输出为止。要把哈希输出,只需调用hash.digest()方法,之后就不可以再添加任何输入了,例如:

var crypto = require('crypto');
var md5 = crypto.createHash('md5');
md5.update('test');
md5.digest();

上面代码中的输出是以二进制格式呈现的,可以为hash.digest()提供进制选项,例如:

var crypto = require('crypto'); 
var md5 = crypto.createHash('md5'); 
md5.update('test'); 
md5.digest(); 
md5.update('test2'); 
md5.digest('hex');


1.2 HMAC

HMAC结合了哈希算法和加密密钥,是为了阻止对签名完整性的一些恶意***。这意味着HMAC同时使用了哈希算法以及一个加密密钥。Node提供的HMAC API和Hash API是一样的,只是在创建hmac对象时需要再传入一个密钥,例如:

var crypto = require('crypto');  
var fs = require('fs');
var pem = fs.readFileSync('key.pem');
var key = pem.toString('ascii');
var hamc = crypto.createHmac('sha1', key);  
hmac.update('test');  
hmac.digest('hex');

创建Hmac对象的密钥必须是一个PEM编码的密钥,以字符串的格式传入。在命令行用OpenSSL可以轻松创建一个密钥。


1.3 公钥加密

公钥加密功能分布在4个类中:Cipher、Decipher、Sign和Verify。和加密模块一样,它们也有工厂方法。Cipher把数据加密,Decipher解密数据,Sign为数据创建加密签名,Verify验证加密签名。

公钥加密算法需要一组配对的密钥:一个是私钥,由物主保存,用来解密和数据签名。另一个是公钥,提供给第三方,可以用来加密数据,或者用来验证数据是否被对应的私钥所签名。

Cipher类提供了用私钥加密数据的功能。该工厂方法输入一个算法和私钥,然后创建cipher对象。Cipher API也采用update()方法来输入数据,但是不太一样。首先,如果条件允许,cipher.update()会返回一块加密的数据,如果cipher中的数据加上传给cipher.update()的数据足够用来创建一个或多个加密块,那这些加密块就会被返回,否则输入会被保存在cipher对象内。Cipher还有一个新的方法cipher.final()用以代替degest()方法,当被调用时,cipher对象中剩余的所有数据都会被加密并返回,但会添加足够填充使其满足块大小的要求,例如:

var crypto = require('crypto');
var fs = require('fs');
var pem = fs.readFileSync('key.pem');
var key = pem.toString('ascii');
var cipher = crypto.createCipher('test', key);
cipher.update(new Buffer(4), 'binary', 'hex');
cipher.update(new Buffer(4), 'binary', 'hex');
cipher.final('hex');

第一次调用cipher.update()时传入了4个字符的数据,得到的会是一个空字符串,第二次因为有足够的数据来生成加密块,可以得到十六进制格式的加密数据。如果发送的数据超过一个块所需要的大小,cipher.final()会先返回尽可能多的加密块,然后才会采用补全的办法。

Decipher类是Cipher类的反面,可以把加密的数据通过decipher.update()传给一个Decipher对象,它会把数据以流的形式保存成块,并在数据足够的时候输出解密数据,例如:

var crypto = require('crypto');
var fs = require('fs');
var pem = fs.readFileSync('key.pem');
var key = pem.toString('ascii');
var plaintext = new Buffer('test');
var encrypted = '';
var cipher = crypto.createCipher('test', $key);
encrypted += cipher.update(plaintext, 'binary', 'hex');
encrypted += cipher.final('hex');
var decrypted = '';
var decipher = crypto.createDecipher('test', $key);
decrypted += decipher.update(encrypted, 'hex', 'binary');
decrypted += decipher.final('binary');
var output = new Buffer(decrypted);

Signatures验证的是签名者是否用其私钥对数据进行授权,Sign类的API与HMAC的几乎一样,crypto.createSign()用来创建sign对象,createSign()只需要传入签名算法,sign.update()可给sign对象添加数据,例如:

var crypto = require('crypto');
var fs = require('fs');
var pem = fs.readFileSync('key.pem');
var key = pem.toString('ascii');
var sign = crypto.createSign('RSA-SHA256');
sign.update('test');   
var sig = sign.sign(key, 'hex');

Verify API使用的方法类似,它用verify.update()来添加数据,之后就可以调用verify.verify()对签名进行验证,例如:

var crypto = require('crypto');
var fs = require('fs');  
var privatePem = fs.readFileSync('key.pem');  
var publicPem = fs.readFileSync('cert.pem');  
var key = privatePem.toString();  
var pubkey = publicPem.toString();
var sign = crypto.createSign('RSA-SHA256');  
sign.update('test');  
var sig = sign.sign(key, 'hex'); 
var verify = crypto.createVerify('RSA-SHA256');
verify.update('test');
verify.verify(pubkey, sig, 'hex');


2. 进程


2.1 process

可以使用process模块从当前的Node进程中获取信息,并可以修改配置。和其他大部分模块不同,process模块是全局的,并且可以一直通过变量process获得。

process提供了基于对Node进程的系统调用的事件。exit事件提供了在Node进程退出前的最终响应时机,例如:

process.on('exit', function() {
  setTimeout(function() {
    console.log('This will not run');
  }, 100);
  console.log('Bye.');
});

process提供的一个非常有用的事件是uncaughtException,它会提供一个暴力的方法来捕获进程退出的异常,例如:

process.on('uncaughtException', function(err) {
  console.log('Caught exception: ' + err);
});
setTimeout(function() {
  console.log('This will still run');
}, 500);

我们还能利用process来访问一些系统事件。当进程得到一个信号时,它会通过process触发的事件通知Node程序。比如当用户在终端的程序按下CTRL+C的时候,SIGINT就会发生,除非通过process来处理信号事件,否则Node会采取默认方法进行处理,例如:

process.on('SIGINT', function() {
  console.log('Got SIGINI. Press Control-D to exit.');
});

process包含了有关Node进程的许多元信息:

1) process.version: 包含了正在运行的Node的版本号。

2) process.installPrefix: 包含了安装时指定的安装目录。

3) process.platform: 列出正在运行的平台名称。

4) process.uptime(): 列出当前进程运行了多少秒。

此外,还可以从Node进程得到或设置一些属性。

可以调用process.getgid()、process.setgid()、process.getuid()和process.setuid()来获得或修改进程用户及用户组的属性,set方法除了可以接受用户名/用户组所对应的数字ID外,还可以直接使用用户组/用户名本身。

正在运行的Node实例的进程ID,或称为PID,可以通过process.pid属性得到。此外还能修改process.title属性来设置Node显示在系统的标题名称。

其他可用的信息包括process.execPath,它显示当前执行的node程序所在的路径。当前的工作目录可以用process.cwd()获取,工作目录是Node启动的目录,可以调用process.chdir()来修改。还可以使用process.memoryUsage()来得到当前进程的内存使用情况。

通过process,还有若干方法可以与操作系统交互。其中一个主要功能就是可以访问操作系统的标准I/O流,stdin是进程的默认输入流,stdout是进程的默认输出流,stderr是错误输出流。它们对应的接口是process.stdin、process.stdout和process.stderr。

因为任何时候都能使用process,所以process.stdin也会为所有的Node进程初始化。但它一开始处于暂停状态,这是Node可以对它进行写入,但不能读取,在尝试从stdin读数据前,需要先调用它的resume()方法,Node会为此数据流填入供读取的缓存,并等待处理,例如:

process.stdin.resume();
process.stdin.setEncoding('utf8');
process.stdin.on('data', function(data) {
  process.stdout.write('data: ' + data);
});
process.stdin.on('end', function() {
  process.stdout.write('end');
});

因为stdin和stdout都是真正的数据流,我们也可以采用更简便的方法,使用流据流的pipe()方法,例如:

process.stdin.resume();
process.stdin.pipe(process.stdout);

stderr用来输出异常和程序运行过程中遇到的问题。当写入stderr时,Node将保证该次写入的会被完成,但是,这会以堵塞的方式执行。通常,调用Stream.wirte()会返回一个布尔值,用来表示Node是否能够写到内核缓存中,对于process.stderr来说这个返回值永远是真,但它需要等待一会儿。

process.stderr永远是UTF-8编码的数据流,不需要设置编码格式,而且,编码格式不能被更改。

另外,Node程序员要从操作系统读取的内容还包括程序启动时的参数。argv是包含命令行参数的数组,以node命令为第一个参数,例如:

console.log(process.argv);

在Node里,我们可以访问事件循环,并且可以推延工作。process.nextTick()创建了一个回调函数,它会在下一个tick或者事件循环下一次迭代时被调用。因为实现是使用队列的,所以它会取代其他事件,例如:

var http = require('http');
var s = http.createServer(function(req, res) {
  res.writeHead(200, {});
  res.end('test');
  console.log('http response');
  process.nextTick(function(){console.log('tick')});
});
s.listen(9000);


2.2 child_process

可以使用child_process模块来为Node主进程创建子进程。因为Node的单进程只有一个事件循环,所以有时可能需要用子程序来更好地利用CPU的多核,或者可以用child_process来启动其他程序,然后与其交互。

child_process有两个主要的方法。spawn()会创建一个子进程,并且有独立的stdin、stdout和stderr文件描述符。exec()会创建子进程,并会在进程结束时以回调函数的方式返回结果。

所有的子进程都有一些公共的属性,它们每个都包含了stdin、stdout和stderr的牧场生,此外它们还有一个pid属性,它包含了该子进程的OS进程ID。子进程在退出时会触发exit事件,其他data事件可通过child_process.stdin、child_process.stdout和child_process.stderr的流方法获得。

使用exec(),可以创建一个子进程来运行其他程序,然后在回调函数中返回执行的结果,例如:

var cp = require('child_process');
cp.exec('ls -l', function(e, stdout, stderr) {
  if (!e) {
    console.log(stdout);
    console.log(stderr);
  }
});

回调函数接收3个参数:一个error对象、stdout的结果和stderr的结果。如果子进程返回了错误的状态码或有其他异常发生,error对象就不会是null。当子进程退出时,它会把状态码传回给父进程。error对象会包含错误代码和stderr,但是,若一个子进程运行是成功的,stderr中依然可以有数据。

exec()的第二个参数可以是一个可选的配置对象,这个对象包含了如下属性:

1) encoding: I/O流输入字符的编码格式。

2) timeout: 进程运行的时间,以毫秒为单位。

3) killSignal: 当时间或Buffer大小超过限制时,用来终止进程的信号。

4) maxBuffer: stdout或stderr允许最大的大小。

5) setsid: 是否创建Node子进程的新会话。

6) cwd: 为子进程初始化工作目录。

7) env: 进程的环境变量,所有的环境变量都可以从父进程继承。

spawn()和exec()很像,但它是一个更加通用的方法,它要求你自己处理流和它们的回调函数。所以spawn()最常见的用途是用来在服务器开发中创建服务器程序的子模块。

spawn()的API与exec()有些差异。第一个参数依然是让进程运行的命令,但它不再是一个命令字符串,而只是可执行程序。进程的参数以数组的形式作为第二个参数(可选)传给spawn()。最后spawn()还可以接受一个选项数组作为最后一个参数,配置的部分属性与exec()相同,例如:

var cp = require('child_process');
var cat = cp.spawn('cat');
cat.stdout.on('data', function(data) {
  console.log(data.toString());
});
cat.on('exit', function() {
  console.log('bye.');
});
cat.stdin.write('meow');
cat.stdin.end();

传给spawn()的配置内容并非和exec()完全一样,这是因为需要对spawn()进行更多的手工操作。evn、setsid和cwd属性都是spawn()的可选项,还有uid和gid,分别用来设置用户ID和组ID,这会引起短暂堵塞。spawn()还比exec()多一个配置项,可以设置自定义的文件描述符来传给新建立的子进程。


3. 其他API


3.1 DNS

DNS模块提供了用域名来替代IP地址的查找功能,也为那些使用域名的模块提供支持,如HTTP客户端。该模块包含了两个主要方法:resolve()和reverse(),前者把域名转换成DNS记录,后者将IP地址转换成域名。DNS模块的其他方法都是这两种方法的特殊形式。

dns.resolve()接受3个参数:待解析的域名、请求的记录类型和回调函数,例如:

dns.resolve('test.com', 'A', function(e, r) {
  if (e) {
    console.log(e);
  }
  console.log(r);
});

因为resolve()通常会返回一个包含许多IP地址的列表,所以需要有dns模板提供的dns.lookup()方法,可以从一个A记录查询中只返回一个IP地址。该方法参数是域名、IP类型(4或6)和回调函数,例如:

var dns = require('dns');
dns.lookup('test.com', 4, function(e, a) {
  console.log(a);
});

此外,API还提供了resolve4()和resolve6()方法,分别用来解析IPv4和IPv6地址。


3.2 assert

assert是为测试代码提供基础功能的核心库。Node的断言功能与其他开发语言类似,允许为对象或耿函数调用提出要求,并在破坏断言时发出信息。Node自己的测试也是用assert编写的。

assert的许多方法都是成对出现的,一个方法提供正面测试,另一个就提供反面功能,例如:

var assert = require('assert');
assert.equal(1, true, 'Truthy');
assert.notEqual(1, true, 'Truthy');

当assert方法不通过时,会抛出异常。

只有几个断言函数,如equal()和notEqual(),会检查相等(==)和不相等(!=)操作,其他测试只会弱化地检查真值和假值。当测试作为一个布尔值时,假值包含了false、0、空字符串、null、undefined和NaN,所有其他值都为真值。

strictEqual()和notstrictEqual()方法检测两个数值是否相等时会采用"==="和"!==",这样可以确保测试时的true和false可分别被作为真和假来对待。

deepEqual()和notDeepEqual()方法提供了深入比较两个对象值的方法。这些方法会进行若干测试,而无需太多细节。如果任何一个检查失败了,测试就会抛出异常。它们是很有用的,但是代价可能很大,所以应该只在需要的时候才使用它们。

throws()和doesNotThrow()会检查指定的代码块是否会抛出异常,可以检测指定的异常,或者是任意的异常是否抛出。要把代码块传给throws()和doesNotThrow(),需要把它们包含在一个没有参数的函数里。待测试的异常是可选的,如果没有传入,throws()会检查是否有异常发生,而doesNotThrow()会确保不抛出异常。


3.3 虚拟机

虚拟机模块可以运行任意一块代码,并得到运行结果。它提供了一些功能,可以修改指定代码的上下文。vm和eval()类似,但提供了更多功能和更好的API来管理代码,然而它不像eval()那样能提供与本地作用域互动的功能。

用vm运行代码有两种方法,第一种与eval()类似,把代码内嵌运行,第二种是先把代码预编译成vm.Script对象,例如:

var vm = require('vm');
vm.runInThisContext('1+1');

vm实际上会在每一个实例的内部,维护一套独立的本地上下文,并且能够保持状态。所以如果在vm的作用域内创建了变量v,该变量就能够在同一个vm的后续操作中有效,并且保持上一次调用时的状态。此外,也可以传给vm一个已经存在的上下文内容,该上下言语会作为默认的上下文使用,例如:

var vm =  require('vm');
var context = {alphabet: ''};
vm.runInNewContext{"alphabet+='a'", context};
vm.runInNewContext{"alphabet+='b'", context};

或者也可以把代码编译成vm.Script对象,这样就可以重复运行同一段代码,在运行的时候,可以选择用哪个上下文来执行,例如:

var vm = require('vm');
var fs = require('fs');
var code = fs.readFileSync('test.js');
var script = vm.createScript(code);
script.runInNewContext({'console':console,'output': 'Hello'});



评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值