02 Node.js核心模塊

內容來自《Node.js開發(fā)指南》

核心模塊是 Node.js 的心臟,它由一些精簡而高效的庫組成,為 Node.js 提供了基本的API。本章中,我們挑選了一部分最常用的核心模塊加以詳細介紹,主要內容包括:

  • 全局對象;
  • 常用工具;
  • 事件機制;
  • 文件系統(tǒng)訪問;
  • HTTP 服務器與客戶端。

1. 全局對象


Javascript中有一個特殊的對象,成為全局對象(Global Object),它及其所有屬性都可以在程序的任何地方訪問,即全局變量。在瀏覽器JavaScript中,通常window是全局對象,而Node.js中的全局對象是global,所有全局變量(除了global本身以外)都是gloal對象的屬性。

我們在Node.js中能搞直接訪問到的對象通常都是global的屬性,如console、process等,下面逐一介紹。

1.1 全局對象與全局變量

global 最根本的作用是作為全局變量的宿主。按照 ECMAScript 的定義,滿足以下條件的變量是全局變量:

  • 在最外層定義的變量;
  • 全局對象的屬性;
  • 隱式定義的變量(未定義直接賦值的變量)。
    當你定義一個全局變量時,這個變量同時也會成為全局對象的屬性,反之亦然。需要注意的是,在 Node.js中你不可能在最外層定義變量,因為所有用戶代碼都是屬于當前模塊的,而模塊本身不是最外層上下文。

1.2 process

process 是一個全局變量,即 global 對象的屬性。它用于描述當前 Node.js 進程狀態(tài)的對象,提供了一個與操作系統(tǒng)的簡單接口。通常在你寫本地命令行程序的時候,少不了要和它打交道。下面將會介紹 process 對象的一些最常用的成員方法。

  • process.argv是命令行參數(shù)數(shù)組,第一個元素是 node, 第二個元素是腳本文件名,
    從第三個元素開始每個元素是一個運行參數(shù)。
console.log(process.argv);

將以上代碼存儲為 argv.js,通過以下命令運行:

$ node argv.js 1991 name=byvoid --v "Carbo Kuo"
    [ 'node',
    '/home/byvoid/argv.js',
    '1991',
    'name=byvoid',
    '--v',
    'Carbo Kuo' ]
  • process.stdout 是標準輸出流,通常我們是用的console.log()向標準輸出打印字符,而 process.stdout.write()函數(shù)提供了更底層的接口。
  • process.stdin是標準輸入流,初始時它是被暫停的,要想從標準輸入讀取數(shù)據(jù),你必須恢復流,并手動編寫流的事件響應函數(shù)。
process.stdin.resume();
process.stdin.on('data', function(data) {
    process.stdout.write('read from console: ' + data.toString());
});
  • process.nextTick(callback)的功能是為事件循環(huán)設置一項任務,Node.js會在下次事件循環(huán)響應時調用callback;

process.nextTick() 提供了一個這樣的工具,可以把復雜的工作拆散,變成一個個較小的事件。

    function doSomething(args, callback) {
        somethingComplicated(args);
        callback();
    }
    doSomething(function onEnd() {
        compute();
    });

我們假設 compute() 和 somethingComplicated() 是兩個較為耗時的函數(shù),以上的程序在調用 doSomething() 時會先執(zhí)行 somethingComplicated(),然后立即調用回調函數(shù),在 onEnd() 中又會執(zhí)行 compute()。下面用 process.nextTick() 改寫上面的程序:

function doSomething(args, callback) {
    somethingComplicated(args);
    process.nextTick(callback);
}
doSomething(function onEnd() {
    compute();
});

改寫后的程序會把上面耗時的操作拆分為兩個事件,減少每個事件的執(zhí)行時間,提高事件響應速度。

不要使用 setTimeout(fn,0)代替 process.nextTick(callback),前者比后者效率要低得多。

我們探討了process對象常用的幾個成員,除此之外process還展示了process.platform、process.pid、 process.execPath、 process.memoryUsage() 等方法,以及 POSIX進程信號響應機制。有興趣的讀者可以訪問 http://nodejs.org/api/process.html 了解詳細內容。

1.3 console

console 用于提供控制臺標準輸出,它是由 Internet Explorer 的 JScript 引擎提供的調試工具,后來逐漸成為瀏覽器的事實標準。 Node.js 沿用了這個標準,提供與習慣行為一致的console 對象,用于向標準輸出流(stdout)或標準錯誤流(stderr)輸出字符。

  • console.log():向標準輸出流打印字符并以換行符結束。 console.log 接受若干個參數(shù),如果只有一個參數(shù),則輸出這個參數(shù)的字符串形式。如果有多個參數(shù),則以類似于 C 語言 printf() 命令的格式輸出。第一個參數(shù)是一個字符串,如果沒有參數(shù),只打印一個換行。
  • console.error():與 console.log() 用法相同,只是向標準錯誤流輸出。
  • console.trace():向標準錯誤流輸出當前的調用棧。

2 常用工具 util

util 是一個 Node.js 核心模塊,提供常用函數(shù)的集合,用于彌補核心 JavaScript 的功能過于精簡的不足。

2.1 util.inherits

util.inherits(constructor, superConstructor)是一個實現(xiàn)對象間原型繼承的函數(shù)。 JavaScript 的面向對象特性是基于原型的,與常見的基于類的不同。 JavaScript 沒有提供對象繼承的語言級別特性,而是通過原型復制來實現(xiàn)的,具體細節(jié)我們在附錄A中討論,在這里我們只介紹 util.inherits 的用法,示例如下:

var util = require('util');

function Base() {
    this.name = 'base';
    this.base = 1991;
    this.sayHello = function() {
        console.log('Hello ' + this.name);
    };
}

Base.prototype.showName = function() {
    console.log(this.name);
};

function Sub() {
    this.name = 'sub';
}

util.inherits(Sub, Base);
var objBase = new Base();
objBase.showName();
objBase.sayHello();
console.log(objBase);

var objSub = new Sub();
objSub.showName();
//objSub.sayHello();
console.log(objSub);

我們定義了一個基礎對象 Base 和一個繼承自 Base 的 Sub, Base 有三個在構造函數(shù)內定義的屬性和一個原型中定義的函數(shù),通過 util.inherits 實現(xiàn)繼承。運行結果如下:

base
Hello base
{ name: 'base', base: 1991, sayHello: [Function] }
sub
{ name: 'sub' }

Sub 僅僅繼承了 Base 在原型中定義的函數(shù),而構造函數(shù)內部創(chuàng)造的 base 屬性和 sayHello 函數(shù)都沒有被 Sub 繼承。

2.2 util.inspect

util.inspect(object,[showHidden],[depth],[colors])是一個將任意對象轉換為字符串的方法,通常用于調試和錯誤輸出。它至少接受一個參數(shù) object,即要轉換的對象。

showHidden 是一個可選參數(shù),如果值為 true,將會輸出更多隱藏信息。
depth 表示最大遞歸的層數(shù),如果對象很復雜,你可以指定層數(shù)以控制輸出信息的多少。如果不指定depth,默認會遞歸2層,指定為 null 表示將不限遞歸層數(shù)完整遍歷對象。
如果color 值為 true,輸出格式將會以 ANSI 顏色編碼,通常用于在終端顯示更漂亮的效果。

util.inspect 并不會簡單地直接把對象轉換為字符串,即使該對象定義了 toString 方法也不會調用。

除了以上我們介紹的幾個函數(shù)之外, util還提供了util.isArray()、util.isRegExp()、util.isDate()、 util.isError() 四個類型測試工具,以及 util.format()、 util.debug() 等工具。有興趣的讀者可以訪問 http://nodejs.org/api/util.html 了解詳細內容。

3. 事件驅動 events


event 是Node.js最重要的模塊,沒有“之一”,原因是Node.js本身構架就是事件式的,而它提供了唯一的接口,所以堪稱Node.js事件編程的基石。events模塊不僅用于用戶代碼與Node.js下層事件循環(huán)的交互,還幾乎被所有的模塊依賴。

3.1 事件發(fā)生器

events 模塊只提供了一個對象:events.EventEmitter。EventEmitter的核心就是事件觸發(fā)與事件監(jiān)聽器功能的封裝。EventEmitter的每個事件由一個事件名和若干個參數(shù)組成,事件名是一個字符串,通常表達一定的語義。對于每個事件,EventEmitter支持若干個事件監(jiān)聽器。當事件觸發(fā)時,注冊到這個事件的事件監(jiān)聽器被依次調用,事件參數(shù)作為回調函數(shù)參數(shù)傳遞。

var events = require('events');

var emitter = new events.EventEmitter();

emitter.on('someEvent', function(arg1, arg2) {
    console.log('listener1', arg1, arg2);
});
emitter.on('someEvent', function(arg1, arg2) {
    console.log('listener2', arg1, arg2);
});

emitter.emit('someEvent', 'byvoid', 1991);

運行的結果是:

listener1 byvoid 1991
listener2 byvoid 1991

以上例子中, emitter 為事件 someEvent 注冊了兩個事件監(jiān)聽器,然后觸發(fā)了
someEvent 事件。運行結果中可以看到兩個事件監(jiān)聽器回調函數(shù)被先后調用。

  • EventEmitter.on(event, listener) 為指定事件注冊一個監(jiān)聽器,接受一個字符串 event 和一個回調函數(shù) listener。
  • EventEmitter.emit(event, [arg1], [arg2], [...]) 觸發(fā) event 事件,傳遞若干可選參數(shù)到事件監(jiān)聽器的參數(shù)表。
  • EventEmitter.once(event, listener) 為指定事件注冊一個單次監(jiān)聽器,即監(jiān)聽器最多只會觸發(fā)一次,觸發(fā)后立刻解除該監(jiān)聽器。
  • EventEmitter.removeListener(event, listener) 移除指定事件的某個監(jiān)聽器, listener 必須是該事件已經(jīng)注冊過的監(jiān)聽器。
  • EventEmitter.removeAllListeners([event]) 移除所有事件的所有監(jiān)聽器,
    如果指定 event,則移除指定事件的所有監(jiān)聽器。

更詳細的 API 文檔參見 http://nodejs.org/api/events.html

3.2 error事件

EventEmitter定義了一個特殊的事件error,它包含了“錯誤”的語義,我們在遇到異常的時候通常會觸發(fā)error事件。當error被觸發(fā)時,EventEmitter規(guī)定如果沒有響應的監(jiān)聽器,Node.js就會把它當成異常,退出程序并打印調用棧。我們一般腰圍會觸發(fā)error事件的對象設置監(jiān)聽器,避免遇到錯誤后整個程序奔潰。例如:

var events = require('events');
var emitter = new events.EventEmitter();
emitter.emit('error');

運行時會顯示以下錯誤:

node.js:201
    throw e; // process.nextTick error, or 'error' event on first tick
          ^
Error: Uncaught, unspecified 'error' event.
    at EventEmitter.emit (events.js:50:15)
    at Object.<anonymous> (/home/byvoid/error.js:5:9)
    at Module._compile (module.js:441:26)
    at Object..js (module.js:459:10)
    at Module.load (module.js:348:31)
    at Function._load (module.js:308:12)
    at Array.0 (module.js:479:10)
    at EventEmitter._tickCallback (node.js:192:40)

3.3 繼承EventEmitter

大多數(shù)時候我們不會直接使用EventEmitter,而是在對象中繼承它。包括fs、ner、http在內的,只要是支持事件響應的核心模塊都是EventEmitter的子類
為什么要這么做呢?原因有2點。首先,具有某個實體功能的對象事件符合語義,事件監(jiān)聽和觸發(fā)應該是一個對象的方法。其次JavaScript的對象機制是基于原型的,支持部分多重繼承,繼承EventEmitter不會打亂對象原有的繼承關系。

4 文件系統(tǒng)fs


fs模塊是文件操作的封裝,它提供了文件的讀取、寫入、更名、刪除、遍歷目錄、鏈接等POSIX文件系統(tǒng)操作。與其他模塊不停的是,fs模塊中所有的操作都提供了異步和同步的兩個版本,例如讀取文件內容的函數(shù)有異步的fs.readFile()和同步的fs.readFileSync()。我們以幾個函數(shù)為代表,介紹fs常用的功能,并列出fs所有函數(shù)的定義和功能。

4.1 fs.readFile

fs.readFile(filename,[encoding],[callback(err,data)])是最簡單的讀取文件的函數(shù)。它接收一個必選參數(shù)filename,表示要讀取的文件名。第二個參數(shù)encoding是可選的,表示文件的字符編碼。callback是回調函數(shù),用于接收文件的內容。如果不指定encoding,則callback就是第二個參數(shù)。毀回調函數(shù)提供兩個參數(shù)err和data,err表示有沒有錯誤發(fā)生,data是文件內容。如果指定了encoding,data是一個解析后的字符串,否則data將會是以buffer形式表示的二進制數(shù)據(jù)。

例如一下程序,我們從content.txt中讀取數(shù)據(jù),但不指定編碼:

var fs = require('fs');
fs.readFile('content.txt', function(err, data) {
    if (err) {
        console.error(err);
    } else {
        console.log(data);
    }
});

假設 content.txt 中的內容是 UTF-8 編碼的 Text 文本文件示例,運行結果如下:

<Buffer 54 65 78 74 20 e6 96 87 e6 9c ac e6 96 87 e4 bb b6 e7 a4 ba e4 be 8b>

這個程序以二進制的模式讀取了文件的內容, data 的值是 Buffer 對象。如果我們給fs.readFile 的 encoding 指定編碼:

fs.readFile('content.txt', 'utf-8', function(err, data) {
if (err) {
console.error(err);
} else {
console.log(data);
}
});

那么運行結果則是:

Text 文本文件示例

當讀取文件出現(xiàn)錯誤時, err 將會是 Error 對象。如果 content.txt 不存在,運行前面的代碼則會出現(xiàn)以下結果:

{ [Error: ENOENT, no such file or directory 'content.txt'] errno: 34, code: 'ENOENT',
path: 'content.txt' }

Node.js 的異步編程接口習慣是以函數(shù)的最后一個參數(shù)為回調函數(shù),通常一個函數(shù)只有一個回調函數(shù)。回調函數(shù)的實際參數(shù)中第一個是 err,其余的參數(shù)是其他返回的內容。如果沒有發(fā)生錯誤, err 的值會是 null 或undefined。如果有錯誤發(fā)生, err 通常是 Error 對象的實例。

4.2 fs.readFileSync

fs.readFileSync(filename, [encoding])是 fs.readFile 同步的版本。它接受的參數(shù)和 fs.readFile 相同,而讀取到的文件內容會以函數(shù)返回值的形式返回。如果有錯誤發(fā)生, fs 將會拋出異常,你需要使用 try 和 catch 捕捉并處理異常。

與同步 I/O 函數(shù)不同, Node.js 中異步函數(shù)大多沒有返回值。

4.3 fs.open

fs.open(path, flags, [mode], [callback(err, fd)])是POSIC open函數(shù)的封裝,與C語言標準庫中的fopen函數(shù)類似。它接收兩個必選參數(shù),path為文件的路徑,flags可以是以下值。

  • r :以讀取模式打開文件。
  • r+ :以讀寫模式打開文件。
  • w :以寫入模式打開文件,如果文件不存在則創(chuàng)建。
  • w+ :以讀寫模式打開文件,如果文件不存在則創(chuàng)建。
  • a :以追加模式打開文件,如果文件不存在則創(chuàng)建。
  • a+ :以讀取追加模式打開文件,如果文件不存在則創(chuàng)建。

mode 參數(shù)用于創(chuàng)建文件時給文件指定權限,默認是 0666①。回調函數(shù)將會傳遞一個文
件描述符 fd②。

① 文件權限指的是 POSIX 操作系統(tǒng)中對文件讀取和訪問權限的規(guī)范,通常用一個八進制數(shù)來表示。例如 0754 表示文件所有者的權限是 7 (讀、寫、執(zhí)行),同組的用戶權限是 5 (讀、執(zhí)行),其他用戶的權限是 4 (讀),寫成字符表示就是 -rwxr-xr--。
② 文件描述符是一個非負整數(shù),表示操作系統(tǒng)內核為當前進程所維護的打開文件的記錄表索引。

4.4 fs.read

fs.read(fd, buffer, offset, length, position, [callback(err, bytesRead,buffer)])

是 POSIX read 函數(shù)的封裝,相比 fs.readFile 提供了更底層的接口。fs.read的功能是從指定的文件描述符 fd 中讀取數(shù)據(jù)并寫入 buffer 指向的緩沖區(qū)對象。 offset 是buffer 的寫入偏移量。 length 是要從文件中讀取的字節(jié)數(shù)。 position 是文件讀取的起始位置,如果 position 的值為 null,則會從當前文件指針的位置讀取。回調函數(shù)傳遞bytesRead 和 buffer,分別表示讀取的字節(jié)數(shù)和緩沖區(qū)對象。

以下是一個使用 fs.open 和 fs.read 的示例。

var fs = require('fs');

fs.open('content.txt', 'r', function(err, fd) {
    if (err) {
        console.error(err);
        return;
    }
    var buf = new Buffer(8);
    fs.read(fd, buf, 0, 8, null, function(err, bytesRead, buffer) {
        if (err) {
            console.error(err);
            return;
        }
        console.log('bytesRead: ' + bytesRead);
        console.log(buffer);
    })
});

運行結果是:

bytesRead: 8
<Buffer 54 65 78 74 20 e6 96 87>

一般來說,除非必要,否則不要使用這種方式讀取文件,因為它要求你手動管理緩沖區(qū)和文件指針,尤其是在你不知道文件大小的時候,這將會是一件很麻煩的事情。

fs 模塊函數(shù)表

功能 異步函數(shù) 同步函數(shù)
打開文件 fs.open(path,flags, [mode], [callback(err,fd)]) fs.openSync(path, flags, [mode])
關閉文件 fs.close(fd, [callback(err)]) fs.closeSync(fd)
讀取文件(文件描述符) fs.read(fd,buffer,offset,length,position,[callback(err, bytesRead, buffer)]) fs.readSync(fd, buffer, offset,length, position)
寫入文件(文件描述符) fs.write(fd,buffer,offset,length,position,[callback(err, bytesWritten, buffer)]) fs.writeSync(fd, buffer, offset,length, position)
讀取文件內容 fs.readFile(filename,[encoding],[callback(err,data)]) fs.readFileSync(filename,[encoding])
寫入文件內容 fs.writeFile(filename, data,[encoding],[callback(err)]) fs.writeFileSync(filename, data,[encoding])
刪除文件 fs.unlink(path, [callback(err)]) fs.unlinkSync(path)
創(chuàng)建目錄 fs.mkdir(path, [mode], [callback(err)]) fs.mkdirSync(path, [mode])
刪除目錄 fs.rmdir(path, [callback(err)]) fs.rmdirSync(path)
讀取目錄 fs.readdir(path, [callback(err, files)]) fs.readdirSync(path)
獲取真實路徑 fs.realpath(path, [callback(err,resolvedPath)]) fs.realpathSync(path)
更名 fs.rename(path1, path2, [callback(err)]) fs.renameSync(path1, path2)
截斷 fs.truncate(fd, len, [callback(err)]) fs.truncateSync(fd, len)
更改所有權 fs.chown(path, uid, gid, [callback(err)]) fs.chownSync(path, uid, gid)
更改所有權(文件描述符) fs.fchown(fd, uid, gid, [callback(err)]) fs.fchownSync(fd, uid, gid)
更改所有權(不解析符號鏈接) fs.lchown(path, uid, gid, [callback(err)]) fs.lchownSync(path, uid, gid)
更改權限 fs.chmod(path, mode, [callback(err)]) fs.chmodSync(path, mode)
更改權限(文件描述符) fs.fchmod(fd, mode, [callback(err)]) fs.fchmodSync(fd, mode)
更改權限(不解析符號鏈接) fs.lchmod(path, mode, [callback(err)]) fs.lchmodSync(path, mode)
獲取文件信息 fs.stat(path, [callback(err, stats)]) fs.statSync(path)
獲取文件信息 (文件描述符) fs.fstat(fd, [callback(err, stats)]) fs.fstatSync(fd)
獲取文件信息 (不解析符號鏈接) fs.lstat(path, [callback(err, stats)]) fs.lstatSync(path)
創(chuàng)建硬鏈接 fs.link(srcpath, dstpath, [callback(err)]) fs.linkSync(srcpath, dstpath)
創(chuàng)建符號鏈接 fs.symlink(linkdata, path, [type],[callback(err)]) fs.symlinkSync(linkdata, path,[type])
讀取鏈接 fs.readlink(path, [callback(err,linkString)]) fs.readlinkSync(path)
修改文件時間戳 fs.utimes(path, atime, mtime, [callback(err)]) fs.utimesSync(path, atime, mtime)
修改文件時間戳(文件描述符) fs.futimes(fd, atime, mtime, [callback(err)]) fs.futimesSync(fd, atime, mtime)
同步磁盤緩存 fs.fsync(fd, [callback(err)]) fs.fsyncSync(fd)

5 HTTP服務器與客戶端


Node.js 標準庫提供了 http 模塊,其中封裝了一個高效的 HTTP 服務器和一個簡易的HTTP客戶端。

5.1 HTTP 服務器

http.Server 是 http 模塊中的 HTTP 服務器對象,用 Node.js 做的所有基于 HTTP 協(xié)議的系統(tǒng),如網(wǎng)站、社交應用甚至代理服務器,都是基于 http.Server 實現(xiàn)的。它提供了一套封裝級別很低的 API,僅僅是流控制和簡單的消息解析,所有的高層功能都要通過它的接口來實現(xiàn)。

//app.js
var http = require('http');

http.createServer(function(req, res) {
    res.writeHead(200, {'Content-Type': 'text/html'});
    res.write('<h1>Node.js</h1>');
    res.end('<p>Hello World</p>');
}).listen(3000);

console.log("HTTP server is listening at port 3000.");

這段代碼中, http.createServer 創(chuàng)建了一個 http.Server 的實例,將一個函數(shù)作為 HTTP 請求處理函數(shù)。這個函數(shù)接受兩個參數(shù),分別是請求對象( req )和響應對象( res )。在函數(shù)體內, res 顯式地寫回了響應代碼 200 表示請求成功),指定響應頭為'Content-Type': 'text/html',然后寫入響應體 '<h1>Node.js</h1>',通過 res.end結束并發(fā)送。最后該實例還調用了 listen 函數(shù),啟動服務器并監(jiān)聽 3000 端口。

5.1.1 http.Server 的事件

http.Server 是一個基于時間的HTTP服務器,所有的請求都被封裝為獨立的事件,開發(fā)者只需要對它的事件編寫響應函數(shù)即可實現(xiàn)HTTP服務器的所有功能。它繼承自EventEmitter,提供了以下幾個事件

  • request:當客戶端請求到來時,該事件被觸發(fā),提供兩個參數(shù) req 和res,分別是http.ServerRequest 和 http.ServerResponse 的實例,表示請求和響應信息。
  • connection:當 TCP 連接建立時,該事件被觸發(fā),提供一個參數(shù) socket,為net.Socket 的實例。 connection 事件的粒度要大于 request,因為客戶端在Keep-Alive 模式下可能會在同一個連接內發(fā)送多次請求。
  • close :當服務器關閉時,該事件被觸發(fā)。注意不是在用戶連接斷開時。

除此之外還有 checkContinue、 upgrade、 clientError 事件,通常我們不需要關心,只有在實現(xiàn)復雜的 HTTP 服務器的時候才會用到。

在 這 些 事 件 中 , 最 常 用 的 就 是 request 了 , 因 此 http 提 供 了 一 個 捷 徑 :http.createServer([requestListener]) , 功 能 是 創(chuàng) 建 一 個 HTTP 服 務 器 并 將requestListener 作為 request 事件的監(jiān)聽函數(shù),這也是我們前面例子中使用的方法。事實上它顯式的實現(xiàn)方法是:

//httpserver.js

var http = require('http');
var server = new http.Server();

server.on('request', function(req, res) {
    res.writeHead(200, {'Content-Type': 'text/html'});
    res.write('<h1>Node.js</h1>');
    res.end('<p>Hello World</p>');
});
server.listen(3000);

console.log("HTTP server is listening at port 3000.");

5.1.2

http.ServerRequest 是 HTTP 請求的信息,是后端開發(fā)者最關注的內容。它一般由http.Server 的 request 事件發(fā)送,作為第一個參數(shù)傳遞,通常簡稱 request 或 req。ServerRequest 提供一些屬性,下表中列出了這些屬性。

名稱 含義
complete 客戶端請求是否已經(jīng)發(fā)送完成
httpVersion HTTP 協(xié)議版本,通常是 1.0 或 1.1
method HTTP 請求方法,如 GET、 POST、 PUT、 DELETE 等
url 原始的請求路徑,例如 /static/image/x.jpg 或 /user?name=byvoid
headers HTTP 請求頭
trailers HTTP 請求尾(不常見)
connection 當前 HTTP 連接套接字,為 net.Socket 的實例
socket connection 屬性的別名
client client 屬性的別名

HTTP 請求一般可以分為兩部分: 請求頭(Request Header)和請求體(Requset Body)。以上內容由于長度較短都可以在請求頭解析完成后立即讀取。而請求體可能相對較長,需要一定的時間傳輸,因此 http.ServerRequest 提供了以下3個事件用于控制請求體傳輸。

  • data :當請求體數(shù)據(jù)到來時,該事件被觸發(fā)。該事件提供一個參數(shù) chunk,表示接收到的數(shù)據(jù)。如果該事件沒有被監(jiān)聽,那么請求體將會被拋棄。該事件可能會被調用多次。
  • end :當請求體數(shù)據(jù)傳輸完成時,該事件被觸發(fā),此后將不會再有數(shù)據(jù)到來。
  • close: 用戶當前請求結束時,該事件被觸發(fā)。不同于 end,如果用戶強制終止了傳輸,也還是調用close。

5.1.3 3. 獲取 GET 請求內容

注意, http.ServerRequest 提供的屬性中沒有類似于 PHP 語言中的 $_GET或 $_POST 的屬性,那我們如何接受客戶端的表單請求呢?由于 GET 請求直接被嵌入在路徑中,URL是完整的請求路徑,包括了 ? 后面的部分,因此你可以手動解析后面的內容作為 GET請求的參數(shù)。 Node.js 的 url 模塊中的 parse 函數(shù)提供了這個功能,例如:

//httpserverrequestget.js

var http = require('http');
var url = require('url');
var util = require('util');

http.createServer(function(req, res) {
    res.writeHead(200, {'Content-Type': 'text/plain'});
    res.end(util.inspect(url.parse(req.url, true)));
}).listen(3000);

在瀏覽器中訪問 http://127.0.0.1:3000/user?name=byvoid&email=byvoid@byvoid.com,我們可以看到瀏覽器返回的結果:

{ search: '?name=byvoid&email=byvoid@byvoid.com',
    query: { name: 'byvoid', email: 'byvoid@byvoid.com' },
    pathname: '/user',
    path: '/user?name=byvoid&email=byvoid@byvoid.com',
    href: '/user?name=byvoid&email=byvoid@byvoid.com' }

通過 url.parse,原始的 path 被解析為一個對象,其中 query 就是我們所謂的 GET請求的內容,而路徑則是 pathname。

5.1.4 獲取 POST 請求內容

HTTP協(xié)議 1.1 版本提供了8種標準的請求方法,其中最常見的就是 GET 和 POST。相比GET 請求把所有的內容編碼到訪問路徑中, POST 請求的內容全部都在請求體中。http.ServerRequest 并沒有一個屬性內容為請求體,原因是等待請求體傳輸可能是一件耗時的工作,譬如上傳文件。而很多時候我們可能并不需要理會請求體的內容,惡意的 POST請求會大大消耗服務器的資源。所以 Node.js 默認是不會解析請求體的,當你需要的時候,需要手動來做。讓我們看看實現(xiàn)方法:

//httpserverrequestpost.js

var http = require('http');
var querystring = require('querystring');
var util = require('util');

http.createServer(function(req, res) {
    var post = '';
    req.on('data', function(chunk) {
        post += chunk;
    });
    req.on('end', function() {
        post = querystring.parse(post);
        res.end(util.inspect(post));
    });
}).listen(3000);

上面代碼并沒有在請求響應函數(shù)中向客戶端返回信息,而是定義了一個 post 變量,用于在閉包中暫存請求體的信息。通過 req 的 data 事件監(jiān)聽函數(shù),每當接受到請求體的數(shù)據(jù),就累加到 post 變量中。在 end 事件觸發(fā)后,通過 querystring.parse 將 post 解析為真正的 POST 請求格式,然后向客戶端返回。

5.1.5 http.ServerResponse

http.ServerResponse 是返回給客戶端的信息,決定了用戶最終能看到的結果。它也是由 http.Server 的 request 事件發(fā)送的,作為第二個參數(shù)傳遞,一般簡稱為response 或 res。

http.ServerResponse 有三個重要的成員函數(shù),用于返回響應頭、響應內容以及結束請求。

  • response.writeHead(statusCode, [headers]):向請求的客戶端發(fā)送響應頭。statusCode 是 HTTP 狀態(tài)碼,如 200 (請求成功)、 404 (未找到)等。 headers是一個類似關聯(lián)數(shù)組的對象,表示響應頭的每個屬性。該函數(shù)在一個請求內最多只能調用一次,如果不調用,則會自動生成一個響應頭。
  • response.write(data, [encoding]):向請求的客戶端發(fā)送響應內容。 data 是一個 Buffer 或字符串,表示要發(fā)送的內容。如果 data 是字符串,那么需要指定encoding 來說明它的編碼方式,默認是 utf-8。在 response.end 調用之前,response.write 可以被多次調用。
  • response.end([data], [encoding]):結束響應,告知客戶端所有發(fā)送已經(jīng)完成。當所有要返回的內容發(fā)送完畢的時候,該函數(shù) 必須 被調用一次。它接受兩個可選參數(shù),意義和 response.write 相同。如果不調用該函數(shù),客戶端將永遠處于等待狀態(tài)。

5.2 HTTP客戶端

http 模塊提供了兩個函數(shù) http.request 和 http.get,功能是作為客戶端向 HTTP服務器發(fā)起請求。

  • http.request(options, callback)發(fā)起 HTTP請求。接受兩個參數(shù), option 是一個類似關聯(lián)數(shù)組的對象,表示請求的參數(shù), callback 是請求的回調函數(shù)。 option常用的參數(shù)如下所示。
    • host :請求網(wǎng)站的域名或 IP 地址。
    • port :請求網(wǎng)站的端口,默認 80。
    • method :請求方法,默認是 GET。
    • path :請求的相對于根的路徑,默認是“ /”。 QueryString 應該包含在其中。例如 /search?query=byvoid。
    • headers :一個關聯(lián)數(shù)組對象,為請求頭的內容。
    • callback 傳遞一個參數(shù),為 http.ClientResponse 的實例。
    • http.request 返回一個 http.ClientRequest 的實例。
      下面是一個通過 http.request 發(fā)送 POST 請求的代碼:
//httprequest.js

var http = require('http');
var querystring = require('querystring');
var contents = querystring.stringify({
    name: 'byvoid',
    email: 'byvoid@byvoid.com',
    address: 'Zijing 2#, Tsinghua University',
});

var options = {
    host: 'www.byvoid.com',
    path: '/application/node/post.php',
    method: 'POST',
    headers: {
        'Content-Type': 'application/x-www-form-urlencoded',
        'Content-Length' : contents.length
    }
};
var req = http.request(options, function(res) {
    res.setEncoding('utf8');
    res.on('data', function (data) {
        console.log(data);
    });
});
req.write(contents);
req.end();
  • http.get(options, callback) http 模塊還提供了一個更加簡便的方法用于處理GET請求: http.get。它是 http.request 的簡化版,唯一的區(qū)別在于http.get自動將請求方法設為了 GET 請求,同時不需要手動調用 req.end()。
//httpget.js
var http = require('http');

http.get({host: 'www.byvoid.com'}, function(res) {
    res.setEncoding('utf8');
    res.on('data', function (data) {
        console.log(data);
    });
});

5.2.1 http.ClientRequest

http.ClientRequest 是由 http.request 或 http.get 返回產(chǎn)生的對象,表示一個已經(jīng)產(chǎn)生而且正在進行中的 HTTP請求。它提供一個 response 事件,即 http.request或 http.get 第二個參數(shù)指定的回調函數(shù)的綁定對象。我們也可以顯式地綁定這個事件的監(jiān)聽函數(shù):

//httpresponse.js

var http = require('http');

var req = http.get({host: 'www.byvoid.com'});

req.on('response', function(res) {
    res.setEncoding('utf8');
    res.on('data', function (data) {
        console.log(data);
    });
});

http.ClientRequest 像 http.ServerResponse 一樣也提供了 write 和 end 函數(shù),用于向服務器發(fā)送請求體,通常用于 POST、 PUT 等操作。所以寫結束以后必須調用 end函數(shù)以通知服務器,否則請求無效。 http.ClientRequest 還提供了以下函數(shù)。

  • request.abort():終止正在發(fā)送的請求。
  • request.setTimeout(timeout, [callback]):設置請求超時時間, timeout 為毫秒數(shù)。當請求超時以后, callback 將會被調用。

此外還有request.setNoDelay([noDelay])、 request.setSocketKeepAlive([enable], [initialDelay]) 等函數(shù),具體內容請參見 Node.js 文檔。

5.2.2 http.ClientResponse

http.ClientResponse 與 http.ServerRequest 相似,提供了三個事件 data、 end和 close,分別在數(shù)據(jù)到達、傳輸結束和連接結束時觸發(fā),其中 data 事件傳遞一個參數(shù)chunk,表示接收到的數(shù)據(jù)。

http.ClientResponse 也提供了一些屬性,用于表示請求的結果狀態(tài),參見表

名稱 含義
statusCode HTTP 狀態(tài)碼,如 200、 404、 500
httpVersion HTTP 協(xié)議版本,通常是 1.0 或 1.1
headers HTTP 請求頭
trailers HTTP 請求尾(不常見)

http.ClientResponse 還提供了以下幾個特殊的函數(shù)。

  • response.setEncoding([encoding]):設置默認的編碼,當 data 事件被觸發(fā)時,數(shù)據(jù)將會以 encoding 編碼。默認值是 null,即不編碼,以 Buffer 的形式存儲。常用編碼為 utf8。
  • response.pause():暫停接收數(shù)據(jù)和發(fā)送事件,方便實現(xiàn)下載功能。
  • response.resume():從暫停的狀態(tài)中恢復。
最后編輯于
?著作權歸作者所有,轉載或內容合作請聯(lián)系作者
平臺聲明:文章內容(如有圖片或視頻亦包括在內)由作者上傳并發(fā)布,文章內容僅代表作者本人觀點,簡書系信息發(fā)布平臺,僅提供信息存儲服務。

推薦閱讀更多精彩內容