目錄:
Node.js核心入門(一)
- 全局對(duì)象
- 常用工具
- 事件機(jī)制
Node.js核心入門(二)
- 文件系統(tǒng)訪問
- HTTP服務(wù)器與客戶端
文件系統(tǒng) fs
fs 模塊是文件操作的封裝,它提供了文件的讀取、寫入、更名、刪除、遍歷目錄、鏈接等 POSIX 文件系統(tǒng)操作,且所有的方法都有異步和同步的形式。異步方法的最后一個(gè)參數(shù)都是一個(gè)回調(diào)函數(shù)。 傳給回調(diào)函數(shù)的參數(shù)取決于具體方法,但回調(diào)函數(shù)的第一個(gè)參數(shù)都會(huì)保留給異常。 如果操作成功完成,則第一個(gè)參數(shù)會(huì)是 null 或 undefined。
const fs = require('fs');
fs.unlink('/tmp/hello', (err) => {
if (err) throw err;
console.log('成功刪除 /tmp/hello');
});
當(dāng)使用同步方法時(shí),任何異常都會(huì)被立即拋出。 可以使用 try/catch 來處理異常,或讓異常向上冒泡。
const fs = require('fs');
fs.unlinkSync('/tmp/hello');
console.log('成功刪除 /tmp/hello');
1.fs.readFile(path,[options], callback)
fs.readFile(path,[options], callback) 是最簡(jiǎn)單的讀取。它接受一個(gè)必選參數(shù)filename,表示要讀取的文件名。第二個(gè)參數(shù)options是可選的,表示文件的字符編碼。 callback 是回調(diào)函數(shù),用于接收文件的內(nèi)容。如果不指定 options ,則 callback 就是第二個(gè)參數(shù)。回調(diào)函數(shù)提供兩個(gè)參數(shù) err 和 data , err 表示有沒有錯(cuò)誤發(fā)生,data 是文件內(nèi)容。如果指定了 options, data 是一個(gè)解析后的字符串,否則 data 將會(huì)是以 Buffer形式表示的二進(jìn)制數(shù)據(jù)。例如:
fs.readFile('/etc/passwd', 'utf8', callback);
需要注意的是,當(dāng) path 是一個(gè)目錄時(shí),fs.readFile() 與 fs.readFileSync() 的行為與平臺(tái)有關(guān)。在 macOS、Linux 與 Windows 上,會(huì)返回一個(gè)錯(cuò)誤。在 FreeBSD 上,會(huì)返回目錄內(nèi)容的表示。
// 在 macOS、Linux 與 Windows 上:
fs.readFile('<directory>', (err, data) => {
// => [Error: EISDIR: illegal operation on a directory, read <directory>]
});
// 在 FreeBSD 上:
fs.readFile('<directory>', (err, data) => {
// => null, <data>
});
2.fs.readFileSync(path[, options])
fs.readFileSync(filename, [encoding]) 是 fs.readFile 同步的版本。它接受的參數(shù)和 fs.readFile 相同,但讀取到的文件內(nèi)容會(huì)以函數(shù)返回值的形式返回。如果有錯(cuò)
誤發(fā)生, fs 將會(huì)拋出異常,這時(shí)候我們就需要使用 try 和 catch 捕捉并處理異常。
3.fs.open(path, flags[, mode], callback)
fs.open(path, flags[, mode], callback)是 POSIX open 函數(shù)的
封裝,與 C 語言標(biāo)準(zhǔn)庫中的 fopen 函數(shù)類似。它接受兩個(gè)必選參數(shù), path 為文件的路徑,
而flags 可以是以下值:
'r' - 以讀取模式打開文件。如果文件不存在則發(fā)生異常。
'r+' - 以讀寫模式打開文件。如果文件不存在則發(fā)生異常。
'rs+' - 以同步讀寫模式打開文件。命令操作系統(tǒng)繞過本地文件系統(tǒng)緩存。
(這對(duì) NFS 掛載模式下打開文件很有用,因?yàn)樗梢宰屇闾^潛在的舊本地緩存。 它對(duì) I/O 的性能有明顯的影響,所以除非需要,否則不要使用此標(biāo)志。
注意,這不會(huì)使 fs.open() 進(jìn)入同步阻塞調(diào)用。 如果那是你想要的,則應(yīng)該使用 fs.openSync()。)
'w' - 以寫入模式打開文件。文件會(huì)被創(chuàng)建(如果文件不存在)或截?cái)啵ㄈ绻募嬖冢?
'wx' - 類似 'w',但如果 path 存在,則失敗。
'w+' - 以讀寫模式打開文件。文件會(huì)被創(chuàng)建(如果文件不存在)或截?cái)啵ㄈ绻募嬖冢?
'wx+' - 類似 'w+',但如果 path 存在,則失敗。
'a' - 以追加模式打開文件。如果文件不存在,則會(huì)被創(chuàng)建。
'ax' - 類似于 'a',但如果 path 存在,則失敗。
'a+' - 以讀取和追加模式打開文件。如果文件不存在,則會(huì)被創(chuàng)建。
'ax+' - 類似于 'a+',但如果 path 存在,則失敗。
mode 可設(shè)置文件模式(權(quán)限和 sticky 位),但只有當(dāng)文件被創(chuàng)建時(shí)才有效。默認(rèn)為 0o666,可讀寫。
4.fs.read(fd, buffer, offset, length, position, callback)
fs.read(fd, buffer, offset, length, position, callback) 是 POSIX read 函數(shù)的封裝,相比 fs.readFile 提供了更底層的接口。從 fd 指定的文件中讀取數(shù)據(jù)。buffer 是數(shù)據(jù)將被寫入到的 buffer。offset 是 buffer 中開始寫入的偏移量。length是一個(gè)整數(shù),指定要讀取的字節(jié)數(shù)。position指定從文件中開始讀取的位置。如果position為null,則數(shù)據(jù)從當(dāng)前文件讀取位置開始讀取,且文件讀取位置會(huì)被更新。 如果 position 為一個(gè)整數(shù),則文件讀取位置保持不變。回調(diào)有三個(gè)參數(shù) (err, bytesRead, buffer)。
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>
HTTP服務(wù)器與客戶端
Node.js 標(biāo)準(zhǔn)庫提供了http模塊,其中封裝了一個(gè)高效的 HTTP 服務(wù)器和一個(gè)簡(jiǎn)易的HTTP 客戶端。 http.Server 是一個(gè)基于事件的 HTTP 服務(wù)器,它的核心由 Node.js 下層 C++
部分實(shí)現(xiàn),而接口由 JavaScript 封裝,兼顧了高性能與簡(jiǎn)易性。 http.request則是一個(gè)HTTP 客戶端工具,用于向 HTTP 服務(wù)器發(fā)起請(qǐng)求,例如實(shí)現(xiàn) Pingback或者內(nèi)容抓取。
Node.js 中的HTTP接口被設(shè)計(jì)成支持協(xié)議的許多特性。比如,大塊編碼的消息。這些接口不緩沖完整的請(qǐng)求或響應(yīng),用戶能夠以流的形式處理數(shù)據(jù)。HTTP消息頭由一個(gè)對(duì)象表示,其中鍵名是小寫的,鍵值不能修改:
{ 'content-length': '123',
'content-type': 'text/plain',
'connection': 'keep-alive',
'host': 'mysite.com',
'accept': '*/*' }
為了支持各種可能的 HTTP 應(yīng)用,Node.js的 HTTP API是非常底層的。它只涉及流處理與消息解析。它把一個(gè)消息解析成消息頭和消息主體,但不解析具體的消息頭或消息主體。鍵名是小寫的,鍵值不能修改。為了支持各種可能的 HTTP 應(yīng)用,Node.js 的 HTTP API 是非常底層的。 它只涉及流處理與消息解析。 它把一個(gè)消息解析成消息頭和消息主體,但不解析具體的消息頭或消息主體。
HTTP服務(wù)器
http.Server 是 http 模塊中的 HTTP 服務(wù)器對(duì)象,用 Node.js 做的所有基于 HTTP 協(xié)議的系統(tǒng),如網(wǎng)站、社交應(yīng)用甚至代理服務(wù)器,都是基于http.Server實(shí)現(xiàn)的。它提供了一套封裝級(jí)別很低的API,僅僅是流控制和簡(jiǎn)單的學(xué)習(xí)解析,而所有的高級(jí)功能都是通過它的接口來實(shí)現(xiàn)的。比如官網(wǎng)上的這個(gè)例子:
const http = require('http');
const hostname = '127.0.0.1';
const port = 3000;
const server = http.createServer((req, res) => {
res.statusCode = 200;
res.setHeader('Content-Type', 'text/plain');
res.end('Hello World\n');
});
server.listen(port, hostname, () => {
console.log(`Server running at http://${hostname}:${port}/`);
})
在這段代碼中,就使用了http.createServer([requestListener])來新建一個(gè)的 http.Server 實(shí)例。現(xiàn)在就先讓我們來看看http.createServer([requestListener])吧。
1. http.Server 的事件
http.Server 是一個(gè)基于事件的 HTTP 服務(wù)器,所有的請(qǐng)求都被封裝為獨(dú)立的事件,開發(fā)者只需要對(duì)它的事件編寫響應(yīng)函數(shù)即可實(shí)現(xiàn) HTTP 服務(wù)器的所有功能。它繼承自
EventEmitter ,提供了以下幾個(gè)事件:
- request:每次接收到一個(gè)請(qǐng)求時(shí)觸發(fā)。 注意,每個(gè)連接可能有多個(gè)請(qǐng)求(在 HTTP keep-alive 連接的情況下)。
- connection :當(dāng)一個(gè)新的 TCP 流被建立時(shí)觸發(fā)。socket 是一個(gè) net.Socket 類型的對(duì)象。 通常用戶無需訪問該事件。 注意,因?yàn)閰f(xié)議解析器綁定到 socket 的方式,socket 不會(huì)觸發(fā) 'readable' 事件。socket 也可以通過 request.connection 訪問。
- connect:每當(dāng)客戶端發(fā)送 HTTP CONNECT 請(qǐng)求時(shí)觸發(fā)。 如果該事件未被監(jiān)聽,則發(fā)送 CONNECT 請(qǐng)求的客戶端會(huì)關(guān)閉連接。當(dāng)該事件被觸發(fā)后,請(qǐng)求的 socket 上沒有 'data' 事件監(jiān)聽器,這意味著需要綁定 'data' 事件監(jiān)聽器,用來處理 socket 上被發(fā)送到服務(wù)器的數(shù)據(jù)。
- close:當(dāng)服務(wù)器關(guān)閉時(shí),該事件被觸發(fā)。注意不是在用戶連接斷開時(shí),而是服務(wù)器關(guān)閉時(shí)。
在這些事件最常用的是request是最常用的,因此 http 提供了一個(gè)捷徑:
http.createServer([requestListener]) ,功能是創(chuàng)建一個(gè) HTTP 服務(wù)器并將requestListener 作為 request 事件的監(jiān)聽函數(shù)。我們上面那個(gè)官網(wǎng)的例子就是如此,其實(shí)它顯式的實(shí)現(xiàn)方法是這樣的:
//httpserver.js
const http = require('http');
const hostname = '127.0.0.1';
const port = 3000;
const server = new http.Server();
server.on('request', (req, res) => {
res.statusCode = 200;
res.setHeader('Content-Type', 'text/plain');
res.end('Hello World\n');
});
server.listen(port, hostname, () => {
console.log(`Server running at http://${hostname}:${port}/`);
})
2. http.ServerRequest
http.ServerRequest 是 HTTP 請(qǐng)求的信息,是后端開發(fā)者最關(guān)注的內(nèi)容。它一般由http.Server 的 request 事件發(fā)送,作為第一個(gè)參數(shù)傳遞,通常簡(jiǎn)稱 request 或 HTTP 請(qǐng)求一般可以分為兩部分:請(qǐng)求頭(Request Header)和請(qǐng)求體(Requset Body)。以上內(nèi)容由于長(zhǎng)度較短都可以在請(qǐng)求頭解析完成后立即讀取。而請(qǐng)求體可能相對(duì)較長(zhǎng),需要一定的時(shí)間傳輸,因此 http.ServerRequest 提供了以下3個(gè)事件用于控制請(qǐng)求體傳輸。req。HTTP請(qǐng)求一般可以分為兩部分:請(qǐng)求頭(RequestHeader)和請(qǐng)求體(RequsetBody)。以上內(nèi)容由于長(zhǎng)度較短都可以在請(qǐng)求頭解析完成后立即讀取。而請(qǐng)求體可能相對(duì)較長(zhǎng),需要一定的時(shí)間傳輸,因此http.ServerRequest提供了以下3個(gè)事件用于控制請(qǐng)求體傳輸。http.ServerRequest提供了3個(gè)事件用于控制請(qǐng)求體傳輸:
data:當(dāng)請(qǐng)求體數(shù)據(jù)到來時(shí),該事件被觸發(fā),提供一個(gè)參數(shù)給回調(diào)函數(shù),是接受到的數(shù)據(jù),該事件可能被多次調(diào)用(所有data按順序的集合,是請(qǐng)求體數(shù)據(jù))。如果該事件沒有被監(jiān)聽,請(qǐng)求體將被拋棄;
end:當(dāng)請(qǐng)求體數(shù)據(jù)完成時(shí)該事件觸發(fā)。此后不再觸發(fā)data事件;
-
close:用戶當(dāng)前請(qǐng)求結(jié)束時(shí),該事件被觸發(fā)。不同于end,如果用戶強(qiáng)制終止了傳輸,也還是調(diào)用close。
表4-2 ServerRequest 的屬性 名 稱 含 義 complete 客戶端請(qǐng)求是否已經(jīng)發(fā)送完成 httpVersion HTTP 協(xié)議版本,通常是 1.0 或 1.1 method HTTP 請(qǐng)求方法,如 GET、POST、PUT、DELETE 等 url 原始的請(qǐng)求路徑,例如 /static/image/x.jpg 或 /user?name=byvoid headers HTTP 請(qǐng)求頭 trailers HTTP 請(qǐng)求尾(不常見) connection 當(dāng)前 HTTP 連接套接字,為 net.Socket 的實(shí)例 socket connection 屬性的別名 client client 屬性的別名
3. 獲取 GET 請(qǐng)求內(nèi)容
注意, http.ServerRequest 提供的屬性中沒有類似于 PHP 語言中的 $_GET 或 $_POST 的屬性,GET請(qǐng)求被直接內(nèi)嵌在路徑中。URL是完整的請(qǐng)求路徑(包括?后面的部分),因此手動(dòng)解析后面的內(nèi)容作為GET請(qǐng)求的參數(shù)。Node.js的url模塊中的parse函數(shù)提供了這個(gè)功能。
以u(píng)rl:http://127.0.0.1/user?name=byvoid&email=byvoid@byvoid.com為例:
var http = require("http");
var url = require("url");
var server = new http.Server();
server.on("request", function (req, res) {
if (req.url == "/favicon.ico") {
return;
}
var m = url.parse(req.url, true);
console.log(m)
res.writeHead(200, {'Content-type': 'text/html;charset = utf8'});
res.end();
})
server.listen(80);
console.log("The server begin");
console.log輸出內(nèi)容:
Url {
protocol: null,
slashes: null,
auth: null,
host: null,
port: null,
hostname: null,
hash: null,
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'
}
4. 獲取 POST 請(qǐng)求內(nèi)容
HTTP 協(xié)議1.1版本提供了8種標(biāo)準(zhǔn)的請(qǐng)求方法,而其中最常見的就是 GET 和 POST。相比GET請(qǐng)求把所有的內(nèi)容編碼到訪問路徑中,POST 請(qǐng)求的內(nèi)容全部都在請(qǐng)求體中。http.ServerRequest 并沒有一個(gè)屬性內(nèi)容是在請(qǐng)求體中,原因是等待請(qǐng)求體傳輸可能是一件耗時(shí)的工作,譬如上傳文件。而很多時(shí)候我們可能并不需要理會(huì)請(qǐng)求體的內(nèi)容,且惡意的 POST
請(qǐng)求會(huì)大大消耗服務(wù)器的資源。所以 Node.js 默認(rèn)是不會(huì)解析請(qǐng)求體的,因此當(dāng)我們需要的時(shí)候,我們就要手寫一個(gè),具體實(shí)現(xiàn)方法如下:
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);
5.http.ServerResponse
http.ServerResponse 是返回給客戶端的信息,決定了用戶最終能看到的結(jié)果。它也是由 http.Server 的 request 事件發(fā)送的,作為第二個(gè)參數(shù)傳遞,一般簡(jiǎn)稱為
response 或 res 。http.ServerResponse 有三個(gè)重要的成員函數(shù),用于返回響應(yīng)頭、響應(yīng)內(nèi)容以及結(jié)束請(qǐng)求:
- response.writeHead(statusCode, [headers]) :向請(qǐng)求的客戶端發(fā)送響應(yīng)頭。statusCode是HTTP狀態(tài)碼,如200(請(qǐng)求成功)、404(未找到)等。headers是一個(gè)類似關(guān)聯(lián)數(shù)組的對(duì)象,表示響應(yīng)頭的每個(gè)屬性。該函數(shù)在一個(gè)請(qǐng)求內(nèi)最多只能調(diào)用一次,如果不調(diào)用,則會(huì)自動(dòng)生成一個(gè)響應(yīng)頭。
- response.write(data, [encoding]) :向請(qǐng)求的客戶端發(fā)送響應(yīng)內(nèi)容。 data 是一個(gè) Buffer 或字符串,表示要發(fā)送的內(nèi)容。如果 data 是字符串,那么需要指定
encoding 來說明它的編碼方式,默認(rèn)是 utf-8 。在 response.end 調(diào)用之前,response.write 可以被多次調(diào)用。 - response.end([data], [encoding]) :結(jié)束響應(yīng),告知客戶端所有發(fā)送已經(jīng)完成。當(dāng)所有要返回的內(nèi)容發(fā)送完畢的時(shí)候,該函數(shù) 必須 被調(diào)用一次。它接受兩個(gè)可選參數(shù),意義和 response.write 相同。如果不調(diào)用該函數(shù),客戶端將永遠(yuǎn)處于等待狀態(tài)。
HTTP 客戶端
http 模塊提供了兩個(gè)函數(shù) http.request和http.get,功能是作為客戶端向HTTP服務(wù)器發(fā)起請(qǐng)求。
1.http.request(options,callback)
http.request(options,callback)發(fā)起HTTP請(qǐng)求,它接受兩個(gè)參數(shù),option是一個(gè)類似關(guān)聯(lián)數(shù)組的對(duì)象,表示請(qǐng)求的參數(shù),callback是請(qǐng)求的回調(diào)函數(shù)。option常用的參數(shù)如下所示:
- host :請(qǐng)求網(wǎng)站的域名或 IP 地址。
- port :請(qǐng)求網(wǎng)站的端口,默認(rèn) 80。
- method :請(qǐng)求方法,默認(rèn)是 GET。
- path :請(qǐng)求的相對(duì)于根的路徑,默認(rèn)是“ / ”。 QueryString 應(yīng)該包含在其中。例如 /search?query=byvoid 。
- headers :一個(gè)關(guān)聯(lián)數(shù)組對(duì)象,為請(qǐng)求頭的內(nèi)容。
而callback 則傳遞一個(gè)參數(shù),為 http.ClientResponse 的實(shí)例。http.request 返回一個(gè)http.ClientRequest 的實(shí)例,下面是一個(gè)通過 http.request 發(fā)送 POST 請(qǐng)求的代碼:
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();
運(yùn)行結(jié)果如下:
array(3) {
["name"]=>
string(6) "byvoid"
["email"]=>
string(17) "byvoid@byvoid.com"
["address"]=>
string(30) "Zijing 2#, Tsinghua University"
}
2.http.get(options, callback)
http 模塊還提供了一個(gè)更加簡(jiǎn)便的方法用于處理GET請(qǐng)求:http.get(options, callback)。它是http.request的簡(jiǎn)化版,唯一的區(qū)別在于http.get自動(dòng)將請(qǐng)求方法設(shè)為了 GET 請(qǐng)求,同時(shí)不需要手動(dòng)調(diào)用 req.end() :
var http = require('http');
http.get({host: 'www.byvoid.com'}, function(res) {
res.setEncoding('utf8');
res.on('data', function (data) {
console.log(data);
});
});
http.ClientRequest
該對(duì)象在 http.request() 內(nèi)部被創(chuàng)建并返回。它表示著一個(gè)正在處理的請(qǐng)求,其請(qǐng)求頭已進(jìn)入隊(duì)列。它提供一個(gè)response事件,即http.request或http.get第二個(gè)參數(shù)指定的回調(diào)函數(shù)的綁定對(duì)象。
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ù),用于向服務(wù)器發(fā)送請(qǐng)求體,通常用于 POST、PUT 等操作。所有寫結(jié)束以后必須調(diào)用 end
函數(shù)以通知服務(wù)器,否則請(qǐng)求無效。http.ClientRequest 還提供了以下常用的函數(shù):
- request.abort() :標(biāo)記請(qǐng)求為終止。 調(diào)用該方法將使響應(yīng)中剩余的數(shù)據(jù)被丟棄且 socket 被銷毀。
- request.setTimeout(timeout,[callback]):設(shè)置請(qǐng)求超時(shí)時(shí)間, timeout為毫秒數(shù)。一旦socket被分配給請(qǐng)求且已連接,socket.setTimeout() 會(huì)被調(diào)用。
- request.end([data[, encoding]][, callback])結(jié)束發(fā)送請(qǐng)求。如果部分請(qǐng)求主體還未被發(fā)送,則會(huì)刷新它們到流中。 如果請(qǐng)求是分塊的,則會(huì)發(fā)送終止字符 '0\r\n\r\n'。
http.ClientResponse
http.ClientResponse 與 http.ServerRequest 相似,提供了三個(gè)事件 data 、end和 close,分別在數(shù)據(jù)到達(dá)、傳輸結(jié)束和連接結(jié)束時(shí)觸發(fā),其中 data 事件傳遞一個(gè)參數(shù)chunk ,表示接收到的數(shù)據(jù)。
http.ClientResponse 也提供了一些屬性,用于表示請(qǐng)求的結(jié)果狀態(tài):
statusCode HTTP 狀態(tài)碼,如 200、404、500
httpVersion HTTP 協(xié)議版本,通常是 1.0 或 1.1
headers HTTP 請(qǐng)求頭
trailers HTTP 請(qǐng)求尾(不常見)
http.ClientResponse 還提供了以下幾個(gè)特殊的函數(shù):
- response.setEncoding([encoding]) :設(shè)置默認(rèn)的編碼,當(dāng) data 事件被觸發(fā)時(shí),數(shù)據(jù)將會(huì)以encoding編碼。默認(rèn)值是null,即不編碼,以 Buffer 的形式存儲(chǔ)。常用編碼為 utf8。
- response.pause():暫停接收數(shù)據(jù)和發(fā)送事件,方便實(shí)現(xiàn)下載功能。
- response.resume() :從暫停的狀態(tài)中恢復(fù)。