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