- 新時(shí)期的Node.js入門
- 李鍇
- 3095字
- 2019-12-12 17:05:42
2.4 HTTP服務(wù)
HTTP模塊是Node的核心模塊,主要提供了一系列用于網(wǎng)絡(luò)傳輸?shù)腁PI,這些API大都位于比較底層的位置,可以讓開(kāi)發(fā)者自由地控制整個(gè)HTTP傳輸過(guò)程。
在HTTP模塊中,Node定義了一些頂級(jí)的類、屬性以及方法,如下所示:

每個(gè)類下面又定義了一些方法和事件,接下來(lái)我們會(huì)就其中常用的部分加以說(shuō)明。
2.4.1 創(chuàng)建HTTP服務(wù)器
通常使用createServer方法創(chuàng)建HTTP服務(wù)器,下面是一個(gè)最簡(jiǎn)單的例子。
代碼2.8 創(chuàng)建一個(gè)簡(jiǎn)單的HTTP服務(wù)器

上面的代碼中,使用createServer方法創(chuàng)建了一個(gè)簡(jiǎn)單的HTTP服務(wù)器,該方法返回一個(gè)http.server類的實(shí)例,createServer方法包含了一個(gè)匿名的回調(diào)函數(shù),該函數(shù)有兩個(gè)參數(shù)req和res,它們是InComingMessage和ServerResponse的實(shí)例。分別表示HTTP的request和response對(duì)象,服務(wù)器創(chuàng)建完成后,Node進(jìn)程開(kāi)始循環(huán)監(jiān)聽(tīng)3000端口(由listen方法實(shí)現(xiàn))。
當(dāng)瀏覽器訪問(wèn)localhost:3000時(shí),Node返回“Hello Node”字符串。
http.server類定義了一系列的事件,在上面的代碼中,HTTP請(qǐng)求會(huì)觸發(fā)connection和request事件,將上面的代碼稍微改造。
代碼2.9 監(jiān)聽(tīng)來(lái)自客戶端的事件

當(dāng)訪問(wèn)http://localhost:3000/時(shí),控制臺(tái)輸出如下:

程序打印出兩個(gè)request,代表觸發(fā)了兩次request事件(其中一個(gè)是favicon.ico的請(qǐng)求)。
下面的代碼是一個(gè)簡(jiǎn)單的靜態(tài)文件服務(wù)器,只支持文本文件,可以通過(guò)瀏覽器來(lái)查看服務(wù)器端的文件內(nèi)容。
代碼2.10 一個(gè)簡(jiǎn)單的靜態(tài)文件服務(wù)器

2.4.2 處理HTTP請(qǐng)求
如圖2-3所示,這是一個(gè)標(biāo)準(zhǔn)的HTTP報(bào)文格式。

圖2-3
1.method,URL和header
當(dāng)處理HTTP請(qǐng)求時(shí),最先做的事就是獲取請(qǐng)求的URL、method等信息。
Node將相關(guān)的信息都封裝在一個(gè)對(duì)象(前面代碼中的req)中,該對(duì)象是IncomingMessage的實(shí)例。
以獲取method和URL為例:

對(duì)于HTTP請(qǐng)求來(lái)說(shuō),method的值通常是get、post、put、delete、update 5個(gè)關(guān)鍵字之一,以get和post最為常見(jiàn),URL的值為除去網(wǎng)站服務(wù)器地址之外的完整值。
例如請(qǐng)求:

那么URL的值即為index.html?name=Lear。
2.header
http header通常為以下的形式:

可以使用Chrome控制臺(tái)來(lái)查看具體信息,以localhost:3000的請(qǐng)求為例,HTTP header形式如下:

Node獲取HTTP header信息也很簡(jiǎn)單,header是一個(gè)JSON對(duì)象,可以對(duì)屬性名進(jìn)行單獨(dú)索引:

3.request body
Node使用stream來(lái)處理HTTP的請(qǐng)求體,這個(gè)stream注冊(cè)了data和end兩個(gè)事件。
下面的這段代碼通常獲取完整的HTTP內(nèi)容體,在Buffer的一節(jié)我們已經(jīng)提到過(guò)了。

目前我們還沒(méi)有提到response對(duì)象,該對(duì)象是ServerResponse的一個(gè)實(shí)例,并且實(shí)現(xiàn)了一個(gè)writableStream,我們接下來(lái)會(huì)對(duì)其進(jìn)行介紹。
2.4.3 Response對(duì)象
1.設(shè)置statusCode
狀態(tài)碼的設(shè)置在Web開(kāi)發(fā)中常常被忽略,在Node中如果開(kāi)發(fā)者不手動(dòng)設(shè)置,那么狀態(tài)碼的值會(huì)默認(rèn)為200。
但200并不適用所有場(chǎng)景,另一個(gè)常用的狀態(tài)碼是404,表示服務(wù)器沒(méi)有對(duì)應(yīng)的資源。
Web開(kāi)發(fā)中如果遇到非法的路徑訪問(wèn),通常會(huì)返回一個(gè)404 not found的頁(yè)面,但實(shí)際上,即使開(kāi)發(fā)者返回一個(gè)200的狀態(tài)碼,也能將對(duì)應(yīng)的頁(yè)面返回,因此狀態(tài)碼的設(shè)置通常是一種最佳實(shí)踐,而非強(qiáng)制的編碼規(guī)范。
2.設(shè)置response header
通過(guò)setHeader方法可以設(shè)置response的頭部信息。
代碼2.11 設(shè)置響應(yīng)頭

setHeader方法只能設(shè)置response header單個(gè)屬性的內(nèi)容,如果想要一次性設(shè)置所有的響應(yīng)頭和狀態(tài)碼,可以使用writeHead方法。
response.writeHead
writeHead方法用于定義HTTP相應(yīng)頭,包括狀態(tài)碼等一系列屬性,下面的例子我們會(huì)同時(shí)設(shè)置狀態(tài)碼和多個(gè)header字段。

調(diào)用該方法后,服務(wù)器向客戶端發(fā)送HTTP響應(yīng)頭,后面通常會(huì)跟著調(diào)用res.write等方法,響應(yīng)頭不可重復(fù)發(fā)送。
有時(shí)開(kāi)發(fā)者并不會(huì)顯式調(diào)用該方法,當(dāng)調(diào)用end方法時(shí)也會(huì)調(diào)用writeHead方法,此時(shí)statusCode會(huì)自動(dòng)設(shè)置成200。
3.response body
response對(duì)象是一個(gè)writableStream實(shí)例,可以直接調(diào)用write方法進(jìn)行寫(xiě)入,寫(xiě)入完成后,再調(diào)用end方法將該stream發(fā)送到客戶端。

不過(guò)這樣會(huì)顯得有些煩瑣,也可以直接將response body作為end方法的參數(shù)進(jìn)行返回。

4.response.end
end方法在每個(gè)HTTP請(qǐng)求的最后都會(huì)被調(diào)用,當(dāng)客戶端的請(qǐng)求完成之后,開(kāi)發(fā)者應(yīng)該調(diào)用該方法來(lái)結(jié)束HTTP請(qǐng)求。通常情況下,如果不調(diào)用end方法,用戶最直觀的感受通常是瀏覽器(以Chrome為例)位于地址欄左邊的叉號(hào)會(huì)一直存在,表示該請(qǐng)求尚未完成。
同樣的,end方法支持一個(gè)字符串或者buffer作為參數(shù),可以指定在HTTP請(qǐng)求的最后返回的數(shù)據(jù),該數(shù)據(jù)會(huì)在瀏覽器頁(yè)面上顯示出來(lái);如果定義了回調(diào)方法,那么會(huì)在end返回后調(diào)用。

2.4.4 上傳數(shù)據(jù)
從概念上來(lái)說(shuō),本節(jié)的內(nèi)容和上一節(jié)有重合之處,但數(shù)據(jù)上傳相關(guān)的操作比較復(fù)雜,因此單獨(dú)抽出為一節(jié)內(nèi)容進(jìn)行介紹。
在實(shí)際的業(yè)務(wù)開(kāi)發(fā)中,用戶除了接收數(shù)據(jù)外,往往還有上傳數(shù)據(jù)的需求,例如提交表單、上傳文件等。
在上面的代碼中我們只處理了頭部信息,頭部信息之外的內(nèi)容(body部分)需要開(kāi)發(fā)者自行解析,否則這部分內(nèi)容就會(huì)被Node程序丟棄。
在傳統(tǒng)的Web開(kāi)發(fā)中,最常用的HTTP請(qǐng)求只有g(shù)et和post兩種。get請(qǐng)求的報(bào)文內(nèi)容很簡(jiǎn)單,只有請(qǐng)求行和請(qǐng)求頭部;post請(qǐng)求由于要上傳數(shù)據(jù),因此需要包含請(qǐng)求體的內(nèi)容,有兩個(gè)相關(guān)的屬性經(jīng)常被用到,分別是content-type和content-length。
對(duì)于Node而言,可以通過(guò)req.method屬性來(lái)判斷請(qǐng)求方法的類型。

1.提交表單
表單的提交是post請(qǐng)求最常用的情景之一。

上面的HTML代碼定義了一個(gè)簡(jiǎn)單的form表單,單擊submit按鈕后會(huì)將整個(gè)表單提交到“/login”路徑下。
下面是Node的服務(wù)端代碼:
代碼2.12 server端的代碼

如果不使用Express之類的Web框架,Node實(shí)現(xiàn)的服務(wù)器代碼通常都是上面這種結(jié)構(gòu),獲取請(qǐng)求的URL之后,再針對(duì)不同的HTTP method進(jìn)行處理,缺點(diǎn)就是要寫(xiě)很多條件控制語(yǔ)句。
當(dāng)用戶在瀏覽器輸入用戶名、密碼并提交后,瀏覽器向localhost:3000/login發(fā)起post請(qǐng)求,我們可以將頭部信息打印出來(lái)。

可以看出,如果是以表單形式提交數(shù)據(jù),請(qǐng)求頭中的content-type為application/x-www(-?)form-urlencoded。
報(bào)文主體中的內(nèi)容是通過(guò)數(shù)據(jù)流的形式來(lái)傳輸?shù)模梢酝ㄟ^(guò)監(jiān)聽(tīng)流事件的方式來(lái)獲取數(shù)據(jù),這一點(diǎn)在buffer一節(jié)已經(jīng)介紹過(guò)了,讀者可以參考代碼2.4。
將表單中的body內(nèi)容打印出來(lái)如下所示:

解析這樣的字符串十分容易,讀者可以自行實(shí)現(xiàn)這樣的方法,也可以使用一些第三方模塊來(lái)實(shí)現(xiàn)。
2.使用post上傳文件
首先要構(gòu)造一個(gè)用于上傳文件的表單。

和只有字段值的表單不同的是,上傳文件的表單要設(shè)置enctype="multipart/form-data"屬性,同樣地,文件上傳時(shí)的header信息也有所不同:

服務(wù)器處理上傳文件通常基于stream來(lái)實(shí)現(xiàn),這里使用的是比較流行的第三方庫(kù)formidable。formidable模塊已經(jīng)有些年頭了,由于社區(qū)喜新厭舊的天性,模塊版本更新可能不夠及時(shí),我們?cè)诘?章會(huì)進(jìn)行進(jìn)一步介紹。
下面是封裝的一個(gè)處理上傳文件的方法。
代碼2.13 服務(wù)器處理上傳文件

在回調(diào)方法中的files字段,將其打印出來(lái):

如果想要獲取files對(duì)象中一些屬性,例如name,type的值,可以通過(guò):

來(lái)獲取,上面表達(dá)式的file字段即為form表單的name屬性。
可以看出formidable是調(diào)用writeStream進(jìn)行文件寫(xiě)入的,同樣的,該模塊還支持多個(gè)文件同時(shí)上傳,讀者可以自行實(shí)現(xiàn)。
2.4.5 HTTP客戶端服務(wù)
HTTP模塊除了能在服務(wù)端處理客戶端請(qǐng)求之外,還可以作為客戶端向服務(wù)器發(fā)起請(qǐng)求,例如通過(guò)http.get發(fā)起get請(qǐng)求,通過(guò)post方法上傳文件等。這也是Node也能做出像electron那樣的桌面軟件的基礎(chǔ)。
http.get的聲明如下:

代碼2.14 發(fā)起一個(gè)get請(qǐng)求

上面這段代碼向http://blockchain.info/ticker發(fā)起了一個(gè)get請(qǐng)求。用來(lái)獲得比特幣當(dāng)前的價(jià)格信息,該請(qǐng)求返回的結(jié)果如下:

2.4.6 創(chuàng)建代理服務(wù)器
代理服務(wù)器相當(dāng)于在客戶端和目標(biāo)服務(wù)器之間建立了一個(gè)中轉(zhuǎn),所有的訪問(wèn)和流量都經(jīng)過(guò)這個(gè)服務(wù)器進(jìn)行中轉(zhuǎn),代理服務(wù)器在實(shí)際中運(yùn)用十分廣泛,例如,如果本地機(jī)器不能直接訪問(wèn)目標(biāo)服務(wù)器,那么就在可以連通兩端的機(jī)器上搭建一個(gè)代理服務(wù)器,就能通過(guò)間接的方式訪問(wèn)目標(biāo)服務(wù)器了。
在本節(jié)中,我們會(huì)在本地搭建一個(gè)簡(jiǎn)單的代理服務(wù)器。
代碼2.15 代理服務(wù)器的例子

在上面的例子中,我們?cè)诒镜貏?chuàng)建了一個(gè)HTTP服務(wù)器,請(qǐng)求經(jīng)由localhost:8080進(jìn)行轉(zhuǎn)發(fā),請(qǐng)求的URL也要改成形如localhost:8080/google.com的格式,也可以用一些其他配置省略掉開(kāi)頭的localhost:8080。
代理服務(wù)器可以有很多應(yīng)用領(lǐng)域,例如使用它來(lái)緩存文件或者,很多企業(yè)都會(huì)使用代理服務(wù)器來(lái)過(guò)濾掉一些廣告和垃圾網(wǎng)站的URL,或者限制員工使用公司網(wǎng)絡(luò)訪問(wèn)社交網(wǎng)站。有的企業(yè)訪問(wèn)npm下載第三方模塊也需要配置代理。
一些常用的屏蔽廣告的瀏覽器插件大都也是依靠本地啟動(dòng)代理服務(wù)器來(lái)實(shí)現(xiàn)廣告過(guò)濾的。
關(guān)于反向代理
如果一個(gè)代理服務(wù)器可以代理外部的訪問(wèn)來(lái)訪問(wèn)內(nèi)部網(wǎng)絡(luò)時(shí),這種代理方式就被稱為反向代理。
CDN就是一個(gè)反向代理的例子,如果一個(gè)網(wǎng)站購(gòu)買了CDN服務(wù),那么當(dāng)有來(lái)自外部(客戶端)的請(qǐng)求時(shí),并沒(méi)有直接訪問(wèn)服務(wù)器的內(nèi)容,而是訪問(wèn)距離用戶最近的CDN節(jié)點(diǎn)。對(duì)于服務(wù)器來(lái)說(shuō),CDN就起到了反向代理的功能。
- 區(qū)塊鏈架構(gòu)與實(shí)現(xiàn):Cosmos詳解
- ADI DSP應(yīng)用技術(shù)集錦
- Learning Python by Building Games
- 自然語(yǔ)言處理Python進(jìn)階
- Python時(shí)間序列預(yù)測(cè)
- Learning OpenCV 3 Computer Vision with Python(Second Edition)
- Python深度學(xué)習(xí):模型、方法與實(shí)現(xiàn)
- Java SE實(shí)踐教程
- 零基礎(chǔ)學(xué)C語(yǔ)言(升級(jí)版)
- Scratch從入門到精通
- Arduino電子設(shè)計(jì)實(shí)戰(zhàn)指南:零基礎(chǔ)篇
- Mastering Leap Motion
- C Primer Plus(第6版)中文版【最新修訂版】
- Clojure for Finance
- Python高性能編程(第2版)