[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'});



發表評論
所有評論
還沒有人評論,想成為第一個評論的人麼? 請在上方評論欄輸入並且點擊發布.
相關文章