版權(quán)說(shuō)明:本文檔由用戶提供并上傳,收益歸屬內(nèi)容提供方,若內(nèi)容存在侵權(quán),請(qǐng)進(jìn)行舉報(bào)或認(rèn)領(lǐng)
文檔簡(jiǎn)介
Node入關(guān)本書致力于教會(huì)你如何用Node.js來(lái)開發(fā)應(yīng)用,過(guò)程中會(huì)傳授你所有所需的“高級(jí)Node入關(guān)本書致力于教會(huì)你如何用Node.js來(lái)開發(fā)應(yīng)用,過(guò)程中會(huì)傳授你所有所需的“高級(jí)at知識(shí)。本書絕不是一本“HelloWorld”的教程。作者M(jìn)anuel翻譯goddyzhao&GrayZhang&狀你正在閱讀的已經(jīng)是本書的最終版。因此,只有當(dāng)進(jìn)行錯(cuò)誤更正以及針對(duì)新版本Node.js動(dòng)進(jìn)行對(duì)應(yīng)的修正時(shí),才會(huì)進(jìn)行更新。本書中的代碼案例都在 讀者對(duì)本書最適合與我有相似技術(shù)背景的讀者:至少對(duì)一門諸如Ruby、Python、PHP或者Java這樣這里指的適合對(duì)其他編程語(yǔ)言有一定經(jīng)驗(yàn)的開發(fā)者,意思是說(shuō),本書不會(huì)對(duì)諸如數(shù)據(jù)類型、變量、控制結(jié)構(gòu)等等之類非?;A(chǔ)的概念作介紹。要讀懂本書,這些基礎(chǔ)的概念我都默認(rèn)你已經(jīng)會(huì)了。然而,本書還是會(huì)對(duì)JavaScript中的函數(shù)和對(duì)象作詳細(xì)介紹,因?yàn)樗鼈兣c其他同類編程語(yǔ)言中的函數(shù)和對(duì)象有很大的不同。本書結(jié)讀完本書之后,你將完成一個(gè)完整的web當(dāng)然了,應(yīng)用本身并沒(méi)有什么了不起的,相比為了實(shí)現(xiàn)該功能書寫的代碼本身,我們更關(guān)注的是如何創(chuàng)建一個(gè)框架來(lái)對(duì)我們應(yīng)用的不同模塊進(jìn)行干凈地剝離。是不是很玄乎?稍后你就明白了。本書先從介紹在Node.js環(huán)境中進(jìn)行JavaScript開發(fā)和在瀏覽器環(huán)境中進(jìn)行JavaScript異開始。World”應(yīng)用,這也是最基礎(chǔ)的Node.js可以確保的是,在這過(guò)程中,大家會(huì)學(xué)到JavaScrip可以確保的是,在這過(guò)程中,大家會(huì)學(xué)到JavaScript中一些高級(jí)的概念、如何使用它們以及為什么使用這些概念就可以實(shí)現(xiàn)而其他編程語(yǔ)言中同類的概念就無(wú)法實(shí)現(xiàn)。本書Github目關(guān)狀讀者對(duì)本書結(jié)JavaScript與服務(wù)器端“Hello一個(gè)完整的基于Node.js的web應(yīng)用應(yīng)用不同模塊分構(gòu)建應(yīng)用的模進(jìn)行函數(shù)傳如何來(lái)進(jìn)行請(qǐng)求的“路由行為驅(qū)動(dòng)執(zhí)行為驅(qū)動(dòng)執(zhí)以非阻塞操作進(jìn)行請(qǐng)求響更有用的場(chǎng)處理POST請(qǐng)?zhí)幚砦募峡偨Y(jié)與展JavaScript與JavaScript與拋開技術(shù),我們先來(lái)聊聊你以及你和JavaScript的關(guān)系。本章的主要目的是想讓你看看,對(duì)你而言是否有必要繼續(xù)閱讀后續(xù)章節(jié)的內(nèi)容。而你真正想要的是“干貨”,你想要知道如何構(gòu)建復(fù)雜的web站點(diǎn)——于是,你學(xué)習(xí)了一種與此同時(shí),你還始終關(guān)注著JavaScript,隨著通過(guò)一些對(duì)jQuery,Prototype紹,你慢慢了解到了很多JavaScript中的進(jìn)階技能,同時(shí)也感受到了JavaScript絕非僅僅是window.open()那么簡(jiǎn)單。.不過(guò),這些畢竟都是前端技術(shù),盡管當(dāng)想要增強(qiáng)頁(yè)面的時(shí)候,使用jQuery總讓你覺(jué)得很爽,但到最后,你頂多是個(gè)JavaScript用戶,而非JavaScript開發(fā)者。然后,出現(xiàn)了Node.js,服務(wù)端的JavaScript,于是,你覺(jué)得是時(shí)候該重新拾起既熟悉又陌生的JavaScript了。但是別急,寫Node.js件事情;理解為什么它們要以它們書寫的這種方式來(lái)書寫則意味著——你要懂JavaScript次是玩真的了。問(wèn)題來(lái)了:件事情;理解為什么它們要以它們書寫的這種方式來(lái)書寫則意味著——你要懂JavaScript次是玩真的了。問(wèn)題來(lái)了:由于JavaScript真正意義上以兩種,甚至可以說(shuō)是三種形態(tài)存在(從中世紀(jì)90年代的作為對(duì)DHTML進(jìn)行增強(qiáng)的小玩具,到像jQuery那樣嚴(yán)格意義上的前端技術(shù),一直到現(xiàn)在的服務(wù)端技術(shù)),因此,很難找到一個(gè)“正確”的方式來(lái)學(xué)習(xí)JavaScript,使得讓你書寫Ns應(yīng)用的時(shí)候感覺(jué)自己是在真正開發(fā)它而不僅僅是使用它。因?yàn)檫@就是關(guān)鍵:你本身已經(jīng)是個(gè)有經(jīng)驗(yàn)的開發(fā)者,你不想通過(guò)到處尋找各種解決方案(其中當(dāng)然了,外面不乏很優(yōu)秀的學(xué)習(xí)JavaScript的文章。但是,有的時(shí)候光靠那些文章是遠(yuǎn)遠(yuǎn)不夠的。你需要的是指導(dǎo)。簡(jiǎn)短申業(yè)界有非常優(yōu)秀的JavaScript我就是上一節(jié)中描述的那個(gè)我。我熟悉如何開發(fā)后端web應(yīng)用,但是對(duì)“真正”的JavaScript以及Node.js,我都只是新手。我也只是最近學(xué)習(xí)了一些JavaScript的高級(jí)概念,并沒(méi)有實(shí)踐經(jīng)驗(yàn)。如果成功的話,那么本書就是我當(dāng)初開始學(xué)習(xí)Node.js服務(wù)端JavaScript是一門“完整”的語(yǔ)言:它可以使用在不同的上下文中,其能力與其他同類語(yǔ)言相Node.js事實(shí)上就是另外一種上下文,它允許在后端(脫離瀏覽器環(huán)境)運(yùn)行JavaScript要實(shí)現(xiàn)在后臺(tái)運(yùn)行JavaScript代碼,代碼需要先被解釋然后正確的執(zhí)行。Node.js此,它使用了Google的V8虛擬機(jī)(Google的Chrome瀏覽器使用的JavaScript執(zhí)行環(huán)境),來(lái)解釋和執(zhí)行JavaScript除此之外,伴隨著Node.js的還有許多有用的模塊,它們可以簡(jiǎn)化很多重復(fù)的勞作,比如向終端輸出字符串。因此,Node.js要使用Node.js,首先需要進(jìn)行安裝。關(guān)于如何安裝來(lái)解釋和執(zhí)行JavaScript除此之外,伴隨著Node.js的還有許多有用的模塊,它們可以簡(jiǎn)化很多重復(fù)的勞作,比如向終端輸出字符串。因此,Node.js要使用Node.js,首先需要進(jìn)行安裝。關(guān)于如何安裝Node.js,這里就不贅述了,可以直接參考官方的安裝指南。安裝完成后,繼續(xù)回來(lái)閱讀本書下面的內(nèi)容?!癏ello World”保存該文件,并通過(guò)Node.js正常的話,就會(huì)在終端輸出HelloWorld一個(gè)完整的基于Node.js的web用nodeconsole.log("Hello當(dāng)用戶請(qǐng)求http://domain/start時(shí),可以看到一個(gè)歡迎頁(yè)面,頁(yè)面上有一個(gè)文件上傳的表單。用戶可以選擇一個(gè)圖片并提交表單,隨后文件將被上傳到h當(dāng)用戶請(qǐng)求http://domain/start時(shí),可以看到一個(gè)歡迎頁(yè)面,頁(yè)面上有一個(gè)文件上傳的表單。用戶可以選擇一個(gè)圖片并提交表單,隨后文件將被上傳到http://domain/upload面完成上傳后會(huì)把圖片顯示在頁(yè)面上。差不多了,你現(xiàn)在也可以去Google一下,找點(diǎn)東西亂搞一下來(lái)完成功能。但是我們現(xiàn)在先不做這個(gè)。應(yīng)用不同模塊分我們需要提供Web頁(yè)面,因此需要一個(gè)HTTP個(gè)路由,用于把請(qǐng)求對(duì)應(yīng)到請(qǐng)求處理程序(requesthandler)我們先來(lái)想想,使用PHP的話我們會(huì)怎么構(gòu)建這個(gè)結(jié)構(gòu)。一般來(lái)說(shuō)我們會(huì)用一個(gè)ApacheHTTP不過(guò)對(duì)Node.js來(lái)說(shuō),概念完全不一樣了。使用Node.js時(shí),我們不僅僅在實(shí)現(xiàn)一個(gè)應(yīng)用,同時(shí)還實(shí)現(xiàn)了整個(gè)HTTP服務(wù)器。事實(shí)上,我們的Web應(yīng)用以及對(duì)應(yīng)的Web服務(wù)器基本上是一樣的。聽起來(lái)好像有一大堆活要做,但隨后我們會(huì)逐漸意識(shí)到,對(duì)Node.js來(lái)說(shuō)這并不是什么麻煩的事。構(gòu)建應(yīng)用的模一個(gè)基礎(chǔ)的HTTP構(gòu)建應(yīng)用的模一個(gè)基礎(chǔ)的HTTP服務(wù)當(dāng)我準(zhǔn)備開始寫我的第一個(gè)“真正的”Node.js應(yīng)用的時(shí)候,我不但不知道怎么寫Node.js代碼,也不知道怎么組織這些代碼。我應(yīng)該把所有東西都放進(jìn)一個(gè)文件里嗎?網(wǎng)上有很多教程都會(huì)教你把所有的邏輯都放進(jìn)一個(gè)用Node.js寫的基礎(chǔ)HTTP服務(wù)器里。但是如果我想加入更多的內(nèi)容,同時(shí)還想保持代碼的可讀性呢?這種方法允許你擁有一個(gè)干凈的主文件(mainfile),你可以用Node.js執(zhí)行它;同時(shí)你可以那么,現(xiàn)在我們來(lái)創(chuàng)建一個(gè)用于啟動(dòng)我們的應(yīng)用的主文件,和一個(gè)保存著我們的HTTP碼的模塊。在我的印象里,把主文件叫做inde.js或多或少是個(gè)標(biāo)準(zhǔn)格式。把服務(wù)器模塊放進(jìn)叫server.js的文件里則很好理解。讓我們先從服務(wù)器模塊開始。在你的項(xiàng)目的根目錄下創(chuàng)建一個(gè)叫server.js的文件,并寫入以下代碼:搞定!你剛剛完成了一個(gè)可以工作的HTTP服務(wù)器。為了證明這一點(diǎn),我們來(lái)運(yùn)行并且測(cè)試這段代碼。首先,用Node.js執(zhí)行你的腳本:接下來(lái),打開瀏覽器訪問(wèn)http://localhost:8888/,你會(huì)看到一個(gè)寫著“HelloWorld”的網(wǎng)nodevarhttp=require("http")?response.write("HelloWorld")?這很有趣,不是嗎?讓我們先來(lái)談?wù)凥TTP服務(wù)器的問(wèn)題,把如何組織項(xiàng)目的事情先放一邊吧,你覺(jué)得如何?我保證之后我們會(huì)解決那個(gè)問(wèn)題的。分析HTTP服務(wù)第一行請(qǐng)求(require)Node.js自帶的http這很有趣,不是嗎?讓我們先來(lái)談?wù)凥TTP服務(wù)器的問(wèn)題,把如何組織項(xiàng)目的事情先放一邊吧,你覺(jué)得如何?我保證之后我們會(huì)解決那個(gè)問(wèn)題的。分析HTTP服務(wù)第一行請(qǐng)求(require)Node.js自帶的http模塊,并且把它賦值給http變量。接下來(lái)我們調(diào)用http模塊提供的函數(shù):createServer。這個(gè)函數(shù)會(huì)返回一個(gè)對(duì)象,這個(gè)對(duì)象有一個(gè)叫做listen的方法,這個(gè)方法有一個(gè)數(shù)值參數(shù),指定這個(gè)HTTP服務(wù)器監(jiān)聽的端口號(hào)。咱們暫時(shí)先不管http.createServer的括號(hào)里的那個(gè)函數(shù)定義。這段代碼只會(huì)啟動(dòng)一個(gè)偵聽8888端口的服務(wù)器,它不做任何別的事情,甚至連請(qǐng)求都不會(huì)應(yīng)答。createSever()的第一個(gè)參數(shù),一個(gè)函數(shù)定義。實(shí)際上,這個(gè)函數(shù)定義是createServer()的第一個(gè)也是唯一一個(gè)參數(shù)。因?yàn)樵贘avaScript中,進(jìn)行函數(shù)傳function{}functionexecute(someFunction,{varhttp=請(qǐng)仔細(xì)閱讀這段代碼!在這里,我們把say函數(shù)作為execute函數(shù)的第一個(gè)變量進(jìn)行了傳遞。這里返回的不是say的返回值,而是say本身!sayexecutesomeFunction,execute可以通過(guò)調(diào)用someFunction()(帶括號(hào)的形式)來(lái)使用say函數(shù)。當(dāng)然,因?yàn)檎?qǐng)仔細(xì)閱讀這段代碼!在這里,我們把say函數(shù)作為execute函數(shù)的第一個(gè)變量進(jìn)行了傳遞。這里返回的不是say的返回值,而是say本身!sayexecutesomeFunction,execute可以通過(guò)調(diào)用someFunction()(帶括號(hào)的形式)來(lái)使用say函數(shù)。當(dāng)然,因?yàn)閟ay有一個(gè)變量,execute在調(diào)用someFunction我們?cè)趀xecute接受第一個(gè)參數(shù)的地方直接定義了我們準(zhǔn)備傳遞給execute的函數(shù)。用這種方式,我們甚至不用給這個(gè)函數(shù)起名字,這也是為什么它被叫做匿名函數(shù)這是我們和我所認(rèn)為的“進(jìn)階”JavaScript的第一次親密接觸,不過(guò)我們還是得循序漸進(jìn)?,F(xiàn)在,我們先接受這一點(diǎn):在JavaScript中,一個(gè)函數(shù)可以作為另一個(gè)函數(shù)接收一個(gè)參數(shù)。我們可以先定義一個(gè)函數(shù),然后傳遞,也可以在傳遞參數(shù)的地方直接定義函數(shù)。函數(shù)傳遞是如何讓HTTP服務(wù)器工作varhttp=require("http")?response.write("HelloWorld")?functionexecute(someFunction,{}execute(function(word){console.log(word)},}execute(say,現(xiàn)在它看上去應(yīng)該清晰了很多:我們向createServer函數(shù)傳遞了一個(gè)匿名函數(shù)?;谑录?qū)動(dòng)的現(xiàn)在它看上去應(yīng)該清晰了很多:我們向createServer函數(shù)傳遞了一個(gè)匿名函數(shù)。基于事件驅(qū)動(dòng)的回這個(gè)問(wèn)題可不好回答(至少對(duì)我來(lái)說(shuō)),不過(guò)這是Node.js原生的工作方式。它是事件驅(qū)動(dòng)的,這也是它為什么這么快的原因。你也許會(huì)想花點(diǎn)時(shí)間讀一下FelixGeisend?rfer的大作Understandingnode.js,它介紹了一些這一切都?xì)w結(jié)于“Node.js是事件驅(qū)動(dòng)的”這一事實(shí)。好吧,其實(shí)我也不是特別確切的了解這句話的意思。不過(guò)我會(huì)試著解釋,為什么它對(duì)我們用Node.js寫網(wǎng)絡(luò)應(yīng)用(Webbasedapplication)是有意義的。當(dāng)我們使用http.createServer方法的時(shí)候,我們當(dāng)然不只是想要一個(gè)偵聽某個(gè)端口的服務(wù)寫PHP應(yīng)用的時(shí)候,我們一點(diǎn)也不為此擔(dān)心:任何時(shí)候當(dāng)有請(qǐng)求進(jìn)入的時(shí)候,網(wǎng)頁(yè)服務(wù)器(通常是Apache)就為這一請(qǐng)求新建一個(gè)進(jìn)程,并且開始從頭到尾執(zhí)行相應(yīng)的PHP腳本。那么在我們的Node.js程序中,當(dāng)一個(gè)新的請(qǐng)求到達(dá)8888嗯,這就是Node.js/JavaScript的事件驅(qū)動(dòng)設(shè)計(jì)能夠真正幫上忙的地方了——雖然我們還得學(xué)一些新概念才能掌握它。讓我們來(lái)看看這些概念是怎么應(yīng)用在我們的服務(wù)器代碼里的。varhttp=functiononRequest(request,{response.writeHead(200,{"Content-Type":}這個(gè)就是傳說(shuō)中的回調(diào)。我們給某個(gè)方法傳遞了一個(gè)函數(shù),這個(gè)方法在有相應(yīng)事件發(fā)生時(shí)調(diào)用這個(gè)函數(shù)來(lái)進(jìn)行回調(diào)。至少對(duì)我來(lái)說(shuō),需要一些功夫才能弄懂它。你如果還是不太確定的話就再去讀讀Felix這個(gè)就是傳說(shuō)中的回調(diào)。我們給某個(gè)方法傳遞了一個(gè)函數(shù),這個(gè)方法在有相應(yīng)事件發(fā)生時(shí)調(diào)用這個(gè)函數(shù)來(lái)進(jìn)行回調(diào)。至少對(duì)我來(lái)說(shuō),需要一些功夫才能弄懂它。你如果還是不太確定的話就再去讀讀Felix章。讓我們?cè)賮?lái)琢磨琢磨這個(gè)新概念。我們?cè)趺醋C明,在創(chuàng)建完服務(wù)器之后,即使沒(méi)有HTTP來(lái)、我們的回調(diào)函數(shù)也沒(méi)有被調(diào)用的情況下,我們的代碼還繼續(xù)有效呢?我們?cè)囋囘@個(gè):注意:在onRequest(我們的回調(diào)函數(shù))觸發(fā)的地方,我用console.log輸出了一段文本。當(dāng)我們與往常一樣,運(yùn)行它nodeserver.js時(shí),它會(huì)馬上在命令行上輸出“Serverhasstarted.”。當(dāng)我們向服務(wù)器發(fā)出請(qǐng)求(在瀏覽器訪問(wèn)http://localhost:8888/),“Request這就是事件驅(qū)動(dòng)的異步服務(wù)器端JavaScriptreceived.”http://localhost:8888http://localhost:8888/favicon.ico)varhttp=functiononRequest(request,{console.log("Requestreceived.")?response.write("HelloWorld")?}console.log("Serverhasstarted.")?服務(wù)器是如何處理請(qǐng)求onRequest()的主體部分。服務(wù)器是如何處理請(qǐng)求onRequest()的主體部分。onRequest()requestresponse。它們是對(duì)象,你可以使用它們的方法來(lái)處理HTTP請(qǐng)求的細(xì)節(jié),并且響應(yīng)請(qǐng)求(的瀏覽器發(fā)回一些東西)。所以我們的代碼就是:當(dāng)收到請(qǐng)求時(shí),使用response.writeHead()函數(shù)發(fā)送一個(gè)HTTP狀態(tài)200和HTTP頭的內(nèi)容類型(content-type),使用response.write()函數(shù)在HTTP相應(yīng)主體中發(fā)送文本“HelloWorld"。最后,我們調(diào)用response.end()完成響應(yīng)。目前來(lái)說(shuō),我們對(duì)請(qǐng)求的細(xì)節(jié)并不在意,所以我們沒(méi)有使用request對(duì)象。服務(wù)端的模塊放在哪server.js文件中有一個(gè)非?;A(chǔ)的HTTP服務(wù)器代碼,而且我提到通常我們會(huì)有一個(gè)叫index.js的文件去調(diào)用應(yīng)用的其他模塊(比如server.js中的HTTP服務(wù)器模塊)來(lái)引導(dǎo)和啟動(dòng)應(yīng)用。我們現(xiàn)在就來(lái)談?wù)勗趺窗裺erver.js變成一個(gè)真正的Node.js模塊,使它可以被我們(還沒(méi)動(dòng)工)的index.js主文件使用。Node.js中自帶了一個(gè)叫做“http”的模塊,我們?cè)谖覀兊拇a中請(qǐng)求它并把返回值賦給一個(gè)本地變量。這把我們的本地變量變成了一個(gè)擁有所有http模塊所提供的公共方法的對(duì)象。varhttp=很好,怎么使用Node.js內(nèi)部模塊已經(jīng)很清楚了。我們?cè)趺磩?chuàng)建自己的模塊,又怎么使用它呢?等我們把server.js的部分導(dǎo)出到請(qǐng)求這個(gè)模塊的腳本。很好,怎么使用Node.js內(nèi)部模塊已經(jīng)很清楚了。我們?cè)趺磩?chuàng)建自己的模塊,又怎么使用它呢?等我們把server.js的部分導(dǎo)出到請(qǐng)求這個(gè)模塊的腳本。目前,我們的HTTP服務(wù)器需要導(dǎo)出的功能非常簡(jiǎn)單,因?yàn)檎?qǐng)求服務(wù)器模塊的腳本僅僅是需要啟動(dòng)服務(wù)器而已。這樣,我們現(xiàn)在就可以創(chuàng)建我們的主文件index.js并在其中啟動(dòng)我們的HTTP了,雖然服務(wù)器的代碼還在server.js中。創(chuàng)建index.js文件并寫入以下內(nèi)容:functionstart(){functiononRequest(request,{console.log("Requestreceived.")?response.write("HelloWorld")?}console.log("Serverhasstarted.")?}exports.start=varfoo=正如你所看到的,我們可以像使用任何其他的內(nèi)置模塊一樣使用server模塊:請(qǐng)求這個(gè)文件并把它指向一個(gè)變量,其中已導(dǎo)出的函數(shù)就可以被我們使用了。我們?nèi)匀恢粨碛姓麄€(gè)應(yīng)用的最初部分:我們可以接收HTTP請(qǐng)求。但是我們得做點(diǎn)什么——對(duì)于不同的URL請(qǐng)求,服務(wù)器應(yīng)該有不同的反應(yīng)。對(duì)于一個(gè)非常簡(jiǎn)單的應(yīng)用來(lái)說(shuō),你可以直接在回調(diào)函數(shù)onRequest()中做這件事情。不過(guò)就像處理不同的H正如你所看到的,我們可以像使用任何其他的內(nèi)置模塊一樣使用server模塊:請(qǐng)求這個(gè)文件并把它指向一個(gè)變量,其中已導(dǎo)出的函數(shù)就可以被我們使用了。我們?nèi)匀恢粨碛姓麄€(gè)應(yīng)用的最初部分:我們可以接收HTTP請(qǐng)求。但是我們得做點(diǎn)什么——對(duì)于不同的URL請(qǐng)求,服務(wù)器應(yīng)該有不同的反應(yīng)。對(duì)于一個(gè)非常簡(jiǎn)單的應(yīng)用來(lái)說(shuō),你可以直接在回調(diào)函數(shù)onRequest()中做這件事情。不過(guò)就像處理不同的HTTP請(qǐng)求在我們的代碼中是一個(gè)不同的部分,叫做“路由選擇”——那么,我們接下來(lái)就創(chuàng)造一個(gè)叫做路由的模塊吧。如何來(lái)進(jìn)行請(qǐng)求的“路由我們要為路由提供請(qǐng)求的URL和其他需要的GET及POST參數(shù),隨后路由需要根據(jù)這些數(shù)據(jù)來(lái)執(zhí)行相應(yīng)的代碼(這里“代碼”對(duì)應(yīng)整個(gè)應(yīng)用的第三部分:一系列在接收到請(qǐng)求時(shí)真正工作的處理程序)。因此,我們需要查看HTTP請(qǐng)求,從中提取出請(qǐng)求的URL以及GET/POST參數(shù)。這一功能應(yīng)當(dāng)屬于路由還是服務(wù)器(甚至作為一個(gè)模塊自身的功能)確實(shí)值得探討,但這里暫定其為我們的HTTP服務(wù)器的功能。我們需要的所有數(shù)據(jù)都會(huì)包含在request對(duì)象中,該對(duì)象作為onRequest()回調(diào)函數(shù)的第一個(gè)參數(shù)傳遞。但是為了解析這些數(shù)據(jù),我們需要額外的Node.JS模塊,它們分別是url和querystring模塊。node當(dāng)然我們也可以用querystring模塊來(lái)解析POST現(xiàn)在我們來(lái)給onRequest()函數(shù)加上一些邏輯,用來(lái)找出瀏覽器請(qǐng)求的URL在我們所要構(gòu)建的應(yīng)用中,這意味著來(lái)自/start和當(dāng)然我們也可以用querystring模塊來(lái)解析POST現(xiàn)在我們來(lái)給onRequest()函數(shù)加上一些邏輯,用來(lái)找出瀏覽器請(qǐng)求的URL在我們所要構(gòu)建的應(yīng)用中,這意味著來(lái)自/start和/upload的請(qǐng)求可以使用不同的代碼來(lái)處理。稍后我們將看到這些內(nèi)容是如何整合到一起的?,F(xiàn)在我們可以來(lái)編寫路由了,建立一個(gè)名為router.jsfunctionroute(pathname)console.log("Abouttoroutearequestfor"+}varurl=require("url")?functionstart()functiononRequest(request,response)varpathname=url.parse(request.url).pathname?console.log("Requestfor"+pathname+"received.")?response.write("HelloWorld")?}console.log("Serverhasstarted.")?}exports.start= ------------------------ 我們的服務(wù)器應(yīng)當(dāng)知道路由的存在并加以有效利用。我們當(dāng)然可以通過(guò)硬編碼的方式將這一依賴項(xiàng)綁定到服務(wù)器上,但是其它語(yǔ)言的編程經(jīng)驗(yàn)告訴我們這會(huì)是一件非常痛苦的事,因此我們將使用依賴注入的方式較松散地添加路由模塊(你可以讀讀MartinFow我們的服務(wù)器應(yīng)當(dāng)知道路由的存在并加以有效利用。我們當(dāng)然可以通過(guò)硬編碼的方式將這一依賴項(xiàng)綁定到服務(wù)器上,但是其它語(yǔ)言的編程經(jīng)驗(yàn)告訴我們這會(huì)是一件非常痛苦的事,因此我們將使用依賴注入的方式較松散地添加路由模塊(你可以讀讀MartinFowlers關(guān)于依賴注入的大作來(lái)作為背景知識(shí))。首先,我們來(lái)擴(kuò)展一下服務(wù)器的start()varrouter=varurl=require("url")?functionstart(route)functiononRequest(request,response)varpathname=url.parse(request.url).pathname?response.write("HelloWorld")?}console.log("Serverhasstarted.")?}exports.start=exports.route=如果現(xiàn)在啟動(dòng)應(yīng)用(nodeindex.js如果現(xiàn)在啟動(dòng)應(yīng)用(nodeindex.js,始終記得這個(gè)命令行),隨后請(qǐng)求一個(gè)URL,你將會(huì)看到應(yīng)用輸出相應(yīng)的信息,這表明我們的HTTP服務(wù)器已經(jīng)在使用路由模塊了,并會(huì)將請(qǐng)求的路徑傳遞給路由:(以上輸出已經(jīng)去掉了比較煩人的/favicon.ico請(qǐng)求相關(guān)的部分)行為驅(qū)動(dòng)執(zhí)將函數(shù)作為參數(shù)傳遞并不僅僅出于技術(shù)上的考量。對(duì)軟件設(shè)計(jì)來(lái)說(shuō),這其實(shí)是個(gè)哲學(xué)問(wèn)題。想想這樣的場(chǎng)景:在index文件中,我們可以將router對(duì)象傳遞進(jìn)去,服務(wù)器隨后可以調(diào)用這個(gè)對(duì)象的route函數(shù)。我是在讀了SteveYegge的大作名詞王國(guó)中的死刑之后理解函數(shù)編程。你也去讀一讀這本書路由給真正的請(qǐng)求處理程回到正題,現(xiàn)在我們的HTTP服務(wù)器和請(qǐng)求路由模塊已經(jīng)如我們的期望,可以相互交流了,就像一對(duì)親密無(wú)間的兄弟。當(dāng)然這還遠(yuǎn)遠(yuǎn)不夠,路由,顧名思義,是指我們要針對(duì)不同的URL有不同的處理方式。例如處理/start的“業(yè)務(wù)邏輯”就應(yīng)該和處理/upload的不同。bash$nodeindex.jsAbouttoroutearequestfor應(yīng)用程序需要新的部件,因此加入新的模塊--已經(jīng)無(wú)需為此感到新奇了。我們來(lái)創(chuàng)建一個(gè)叫做reque應(yīng)用程序需要新的部件,因此加入新的模塊--已經(jīng)無(wú)需為此感到新奇了。我們來(lái)創(chuàng)建一個(gè)叫做requestHandlers的模塊,并對(duì)于每一個(gè)請(qǐng)求處理程序,添加一個(gè)占位用函數(shù),隨后將這些函數(shù)作為模塊的方法導(dǎo)出:在這里我們得做個(gè)決定:是將requestHandlers模塊硬編碼到路由里來(lái)使用,還是再添加一點(diǎn)依賴注入?雖然和其他模式一樣,依賴注入不應(yīng)該僅僅為使用而使用,但在現(xiàn)在這個(gè)情況下,使用依賴注入可以讓路由和請(qǐng)求處理程序之間的耦合更加松散,也因此能讓路由的重用性更高。那么我們要怎么傳遞這些請(qǐng)求處理程序呢?別看現(xiàn)在我們只有2個(gè)處理程序,在一個(gè)真實(shí)的應(yīng)用中,請(qǐng)求處理程序的數(shù)量會(huì)不斷增加,我們當(dāng)然不想每次有一個(gè)新的URL或請(qǐng)求處理程序時(shí),都要為了在路由里完成請(qǐng)求到處理程序的映射而反復(fù)折騰。除此之外,在路由里有一大堆ifrequest==xthencallhandlery也使得系統(tǒng)丑陋不堪。(associativearray)不過(guò)結(jié)果有點(diǎn)令人失望,JavaScriptfunctionstart()console.log("Requesthandler'start'was}functionupload()console.log("Requesthandler'upload'was}exports.start=start?在C++或C#中,當(dāng)我們談到對(duì)象,指的是類或者結(jié)構(gòu)體的實(shí)例。對(duì)象根據(jù)他們實(shí)例化的模板(就是所謂的類),會(huì)擁有不同的屬性和方法。但在JavaScript不是這個(gè)概念。在J在C++或C#中,當(dāng)我們談到對(duì)象,指的是類或者結(jié)構(gòu)體的實(shí)例。對(duì)象根據(jù)他們實(shí)例化的模板(就是所謂的類),會(huì)擁有不同的屬性和方法。但在JavaScript不是這個(gè)概念。在JavaScript中,對(duì)象就是一個(gè)鍵/值對(duì)的集合--你可以把JavaScript的對(duì)象想象成一個(gè)鍵為字符串類型的字典。但如果JavaScript的對(duì)象僅僅是鍵/值對(duì)的集合,它又怎么會(huì)擁有方法呢?好吧,這里的值可以是字符串、數(shù)字或者……函數(shù)!雖然handle并不僅僅是一個(gè)“東西”(一些請(qǐng)求處理程序的集合),我還是建議以一個(gè)動(dòng)詞作為其命名,這樣做可以讓我們?cè)诼酚芍惺褂酶鲿车谋磉_(dá)式,稍后會(huì)有說(shuō)明。正如所見,將不同的URL映射到相同的請(qǐng)求處理程序上是很容易的:只要在對(duì)象中添加一個(gè)鍵為"/"的屬性,對(duì)應(yīng)requestHandlers.start即可,這樣我們就可以干凈簡(jiǎn)潔地配置/start都交由start這一處理程序處理。varurl=require("url")?functionstart(route,handle)functiononRequest(request,response)varpathname=url.parse(request.url).pathname?route(handle,varrouter=varrequestHandlers=varhandle=handle["/"]=requestHandlers.start?handle["/start"]=requestHandlers.start?server.start(router.route,然后我們相應(yīng)地在route.js文件中修改route()通過(guò)以上代碼,我們首先檢查給定的路徑對(duì)應(yīng)的請(qǐng)求處理程序是否存在,如果存在的話直接調(diào)用相應(yīng)的函數(shù)。我們可以用從關(guān)聯(lián)數(shù)組中獲取元素一樣的方式從傳遞的對(duì)象中獲取請(qǐng)求處理函數(shù),因此就有了簡(jiǎn)潔流暢的形如handle然后我們相應(yīng)地在route.js文件中修改route()通過(guò)以上代碼,我們首先檢查給定的路徑對(duì)應(yīng)的請(qǐng)求處理程序是否存在,如果存在的話直接調(diào)用相應(yīng)的函數(shù)。我們可以用從關(guān)聯(lián)數(shù)組中獲取元素一樣的方式從傳遞的對(duì)象中獲取請(qǐng)求處理函數(shù),因此就有了簡(jiǎn)潔流暢的形如handle[pathname]();的表達(dá)式,這個(gè)感覺(jué)就像在前方中提到的那樣:“嗨,請(qǐng)幫我處理了這個(gè)路徑”。并且在瀏覽器中打開http://localhost:8888/可以看到這個(gè)請(qǐng)求同樣被start請(qǐng)求處理程序處理了:ServerhasRequestfor/startAbouttoroutearequestfor/startRequesthandler'start'wasfunctionroute(handle,pathname)console.log("Abouttoroutearequestfor"+pathname)?if(typeofhandle[pathname]==='function'){}elseconsole.log("Norequesthandlerfoundfor"+}}exports.route=response.write("HelloWorld")?}console.log("Serverhasstarted.")?}exports.start=讓請(qǐng)求處理程序作出響讓請(qǐng)求處理程序作出響這里要記住的是,瀏覽器發(fā)出請(qǐng)求后獲得并顯示的“HelloWorld”信息仍是來(lái)自于我們不好的實(shí)現(xiàn)方對(duì)于我們這樣擁有PHP或者Ruby技術(shù)背景的開發(fā)者來(lái)說(shuō),最直截了當(dāng)?shù)膶?shí)現(xiàn)方式事實(shí)上并不是非??孔V:看似有效,實(shí)則未必如此。這里我指的“直截了當(dāng)?shù)膶?shí)現(xiàn)方式”意思是:讓請(qǐng)求處理程序通過(guò)onRequest(return())讓我們從讓請(qǐng)求處理程序返回需要在瀏覽器中顯示的信息開始。我們需要將qtHls修改為如下形式:functionstart()return"HelloStart"?}functionupload()return"HelloUpload"?}exports.start=start?Requestfor/Abouttoroutearequestfor/最后,我們需要對(duì)我們的server.j最后,我們需要對(duì)我們的server.js進(jìn)行重構(gòu)以使得它能夠?qū)⒄?qǐng)求處理程序通過(guò)請(qǐng)求路由返回的內(nèi)容響應(yīng)給瀏覽器,如下所示:如果我們運(yùn)行重構(gòu)后的應(yīng)用,一切都會(huì)工作的很好:請(qǐng)求http:/localhost:8888/start,瀏覽器會(huì)輸出“HelloStart”,請(qǐng)求http://localhost:8888/upload會(huì)輸出“HelloUpload”,而請(qǐng)求http://localhost:8888/foo會(huì)輸出“404Notfound”。varurl=require("url")?functionstart(route,handle)functiononRequest(request,response)varpathname=url.parse(request.url).pathname?varcontent=route(handle,pathname)}console.log("Serverhasstarted.")?}exports.start=functionroute(handle,pathname)console.log("Abouttoroutearequestfor"+pathname)?if(typeofhandle[pathname]==='function'){return}elsereturn"404Notfound"?}}exports.route=阻塞與非阻阻塞與非阻這里,我們來(lái)修改下start請(qǐng)求處理程序,我們讓它等待10秒以后再返回“HelloStart”。因(當(dāng)然了,這里只是模擬休眠10秒,實(shí)際場(chǎng)景中,這樣的阻塞操作有很多,比方說(shuō)一些長(zhǎng)時(shí)間的計(jì)算操作等。)如往常一樣,我們先要重啟下服務(wù)器。為了看到效果,我們要進(jìn)行一些相對(duì)復(fù)雜的操作(functionstart()console.log("Requesthandler'start'wasfunctionsleep(milliSeconds)varstartTime=newwhile(newDate().getTime()<startTime+}return"Hello}functionupload()return"HelloUpload"?}exports.start=start?我一起做):首先,打開兩個(gè)瀏覽器窗口或者標(biāo)簽頁(yè)。在第一個(gè)瀏覽器窗口的地址欄中輸入http://localhost:8888/start,但是先不要打開它!在第二個(gè)瀏覽器窗口的地址欄中輸入http://localhost:8888/upload,同樣的,先不要打開接下來(lái),做如下操作:在第一個(gè)窗口中(“/start”)按下回車,然后快速切換到第二個(gè)窗口中(“/upload我一起做):首先,打開兩個(gè)瀏覽器窗口或者標(biāo)簽頁(yè)。在第一個(gè)瀏覽器窗口的地址欄中輸入http://localhost:8888/start,但是先不要打開它!在第二個(gè)瀏覽器窗口的地址欄中輸入http://localhost:8888/upload,同樣的,先不要打開接下來(lái),做如下操作:在第一個(gè)窗口中(“/start”)按下回車,然后快速切換到第二個(gè)窗口中(“/upload”)按下回車。注意,發(fā)生了什么:/startURL加載花了10秒,這和我們預(yù)期的一樣。但是,/uploadURL居這到底是為什么呢?原因就是start()包含了阻塞操作。形象的說(shuō)就是“它阻塞了所有其他的處理工作”。這顯然是個(gè)問(wèn)題,因?yàn)镹ode一向是這樣來(lái)標(biāo)榜自己的:“在node中除了代碼,所有一切都是并行執(zhí)行的”。這句話的意思是說(shuō),Node.js——Node.js是單線程的。它通過(guò)事件輪詢(eventloop)來(lái)實(shí)現(xiàn)并行操作,對(duì)此,我們應(yīng)該要充分利用這一點(diǎn)——盡可能的避免阻塞操作,取而代之,多使用非阻塞操作。對(duì)于Node.js來(lái)說(shuō),它是這樣處理的:“嘿,probablyExpensiveFunction()(的就是需要花時(shí)間處理的函數(shù)),你繼續(xù)處理你的事情,我(Node.js線程)先不等你了,我繼續(xù)去處理你后面的代碼,請(qǐng)你提供一個(gè)callbackFunction(),等你處理完之后我會(huì)去調(diào)用該回調(diào)函數(shù)的,謝謝!”(如果想要了解更多關(guān)于事件輪詢細(xì)節(jié),可以閱讀Mixu的博文——理解node.js詢。)varexec=上述代碼中,我們引入了一個(gè)新的Node.js模塊,child_process。之所以用它,是為了實(shí)現(xiàn)一個(gè)既簡(jiǎn)單又實(shí)用的非阻塞操作:exe()。上述代碼中,我們引入了一個(gè)新的Node.js模塊,child_process。之所以用它,是為了實(shí)現(xiàn)一個(gè)既簡(jiǎn)單又實(shí)用的非阻塞操作:exe()。exe()做了什么呢?它從Node.js來(lái)執(zhí)行一個(gè)shell命令。在上述例子中,我們用它來(lái)獲取當(dāng)前目錄下所有的文件(“l(fā)s-lah”),然后,當(dāng)/startURL請(qǐng)求的時(shí)候?qū)⑽募畔⑤敵龅綖g覽器中。content(初始值為“empty”),執(zhí)行“l(fā)s和往常一樣,我們啟動(dòng)服務(wù)器,然后訪問(wèn)“http://localhost:8888/start”。之后會(huì)載入一個(gè)漂亮的web頁(yè)面,其內(nèi)容為“empty”這個(gè)時(shí)候,你可能大致已經(jīng)猜到了,exec()在非阻塞這塊發(fā)揮了神奇的功效。它其實(shí)是個(gè)很好的東西,有了它,我們可以執(zhí)行非常耗時(shí)的shell操作而無(wú)需迫使我們的應(yīng)用停下來(lái)等待該操作。(如果想要證明這一點(diǎn),可以將“l(fā)s-lah”換成比如“find/”這樣更耗時(shí)的操作來(lái)效果)functionstart()varcontent="empty"?exec("ls-lah",function(error,stdout,{content=return}functionupload()return"HelloUpload"?}exports.start=start?問(wèn)題就在于,為了進(jìn)行非阻塞工作,exec()在我們的例子中,該回調(diào)函數(shù)就是作為第二個(gè)參數(shù)傳遞給exec()Node.jsreturncontent,content仍然是“empty”,因?yàn)閭鬟f給問(wèn)題就在于,為了進(jìn)行非阻塞工作,exec()在我們的例子中,該回調(diào)函數(shù)就是作為第二個(gè)參數(shù)傳遞給exec()Node.jsreturncontent,content仍然是“empty”,因?yàn)閭鬟f給我們這里“l(fā)slah”的操作其實(shí)是非??斓模ǔ钱?dāng)前目錄下有上百萬(wàn)個(gè)文件)。這也是為什么回調(diào)函數(shù)也會(huì)很快的執(zhí)行到——不過(guò),不管怎么說(shuō)它還是異步的。為了讓效果更加明顯,我們想象一個(gè)更耗時(shí)的命令:“find/”,它在我機(jī)器上需要執(zhí)行1分鐘左右的時(shí)間,然而,盡管在請(qǐng)求處理程序中,我把“l(fā)slah”換成“find/”,當(dāng)打開/startURL的時(shí)候,依然能夠立即獲得HTTP響應(yīng)——很明顯,當(dāng)exec()在后臺(tái)執(zhí)行的時(shí)候,在“find以非阻塞操作進(jìn)行請(qǐng)求響我剛剛提到了這樣一個(gè)短語(yǔ)——“正確的方式”。而事實(shí)上通?!罢_的方式”一般都不簡(jiǎn)不過(guò),用Node.js就有這樣一種實(shí)現(xiàn)方案 函數(shù)傳遞。下面就讓我們來(lái)具體看看如何實(shí)現(xiàn)到目前為止,我們的應(yīng)用已經(jīng)可以通過(guò)應(yīng)用各層之間傳遞值的方式(請(qǐng)求處理程序->請(qǐng)求路由->服務(wù)器)將請(qǐng)求處理程序返回的內(nèi)容(請(qǐng)求處理程序最終要顯示給用戶的內(nèi)容)傳遞給服務(wù)器“傳遞”給內(nèi)容的方式。從實(shí)踐角度來(lái)說(shuō),就是將response對(duì)象(從服務(wù)器的回調(diào)函function(error,stdout,{content=}數(shù)onRequest()獲?。┩ㄟ^(guò)請(qǐng)求路由傳遞給請(qǐng)求處理程序。隨后,處理程序就可以采用該對(duì)象相對(duì)此前從route()函數(shù)獲取返回值的做法,這次我們將r數(shù)onRequest()獲?。┩ㄟ^(guò)請(qǐng)求路由傳遞給請(qǐng)求處理程序。隨后,處理程序就可以采用該對(duì)象相對(duì)此前從route()函數(shù)獲取返回值的做法,這次我們將response對(duì)象作為第三個(gè)參數(shù)傳遞給route()函數(shù),并且,我們將onRequest()處理程序中所有有關(guān)response的函數(shù)調(diào)都移除,因?yàn)槲覀兿M@部分工作讓route()函數(shù)來(lái)完成。下面就來(lái)看看我們的同樣的模式:相對(duì)此前從請(qǐng)求處理程序中獲取返回值,這次取而代之的是直接傳遞response對(duì)象。functionroute(handle,pathname,{console.log("Abouttoroutearequestfor"+pathname)?if(typeofhandle[pathname]==='function'){}elseconsole.log("Norequesthandlerfoundfor"+pathname)?response.write("404Notfound")?}}exports.route=varurl=require("url")?functionstart(route,handle)functiononRequest(request,response)varpathname=url.parse(request.url).pathname?route(handle,pathname,}console.log("Serverhasstarted.")?}exports.start=如果沒(méi)有對(duì)應(yīng)的請(qǐng)求處理器處理,我們就直接返回“404”最后,我們將requestHandler.jsstart處理程序在exe如果沒(méi)有對(duì)應(yīng)的請(qǐng)求處理器處理,我們就直接返回“404”最后,我們將requestHandler.jsstart處理程序在exe()的匿名回調(diào)函數(shù)中做請(qǐng)求響應(yīng)的操作,而upload處理程序仍然是簡(jiǎn)單的回復(fù)“HelloWorld”,只是這次是使用response對(duì)象而已。這時(shí)再次我們啟動(dòng)應(yīng)用 如果想要證明/start處理程序中耗時(shí)的操作不會(huì)阻塞對(duì)/upload請(qǐng)求作出立即響應(yīng)的話,可以將requestHandlers.js修改為如下形式:functionstart(response){console.log("Requesthandler'start'wasexec("findfunction(error,stdout,stderr){}functionstart(response){console.log("Requesthandler'start'wasexec("ls-lah",function(error,stdout,"text/plain"})?response.write(stdout)?}functionupload(response)console.log("Requesthandler'upload'wascalled.")?response.write("HelloUpload")?}exports.start=start?更有用的場(chǎng)更有用的場(chǎng)服務(wù)器,請(qǐng)求路由以及請(qǐng)求處理程序都已經(jīng)完成了,下面讓我們按照此前的用例給網(wǎng)站添加交互:用戶選擇一個(gè)文件,上傳該文件,然后在瀏覽器中看到上傳的文件。為了保持簡(jiǎn)單,我們假設(shè)用戶只會(huì)上傳圖片,然后我們應(yīng)用將該圖片顯示到瀏覽器中。好,下面就一步步來(lái)實(shí)現(xiàn),鑒于此前已經(jīng)對(duì)JavaScript原理性技術(shù)性的內(nèi)容做過(guò)大量介紹了,這次我們加快點(diǎn)速度。要實(shí)現(xiàn)該功能,分為如下兩步:首先,讓我們來(lái)看看如何處理POST請(qǐng)求(非文件上傳),之第一,盡管在Node.js中處理基礎(chǔ)的POST請(qǐng)求相對(duì)比較簡(jiǎn)單,但在這過(guò)程中還是能學(xué)到很多。第二,用Node.js來(lái)處理文件上傳(multipartPOST請(qǐng)求)是比較復(fù)雜的,它不在本書的范疇,但,如何使用外部模塊卻是在本書涉獵內(nèi)容之內(nèi)。處理POST請(qǐng)考慮這樣一個(gè)簡(jiǎn)單的例子:我們顯示一個(gè)文本區(qū)(textarea)供用戶輸入內(nèi)容,然后通過(guò)請(qǐng)求提交給服務(wù)器。最后,服務(wù)器接受到請(qǐng)求,通過(guò)處理程序?qū)⑤斎氲膬?nèi)容展示到瀏覽器中。start請(qǐng)求處理程序用于生成帶文本區(qū)的表單,因此,我們將requestHandlers.js式:functionupload(response)console.log("Requesthandler'upload'wascalled.")?response.write("HelloUpload")?}exports.start=start?好了,現(xiàn)在我們的應(yīng)用已經(jīng)很完善了,都可以獲得威比獎(jiǎng)(WebbyAward好了,現(xiàn)在我們的應(yīng)用已經(jīng)很完善了,都可以獲得威比獎(jiǎng)(WebbyAwards)了,哈哈。(譯者注:威比獎(jiǎng)是由國(guó)際數(shù)字藝術(shù)與科學(xué)學(xué)院主辦的評(píng)選全球最佳網(wǎng)站的獎(jiǎng)項(xiàng),具體參見詳細(xì)說(shuō)明)通過(guò)在瀏覽器中訪問(wèn)http://localhost:8888/start就可以看到簡(jiǎn)單的表單了,要記得重啟服務(wù)器哦!你可能會(huì)說(shuō):這種直接將視覺(jué)元素放在請(qǐng)求處理程序中的方式太丑陋了。說(shuō)的沒(méi)錯(cuò),但是,我并不想在本書中介紹諸如MVC之類的模式,因?yàn)檫@對(duì)于你了解JavaScript或者Node.js環(huán)境來(lái)說(shuō)沒(méi)多大關(guān)系。/upload請(qǐng)求處理程序現(xiàn)在,我們已經(jīng)是新手中的專家了,很自然會(huì)想到采用異步回調(diào)來(lái)實(shí)現(xiàn)非阻塞地處理POST求的數(shù)據(jù)。這里采用非阻塞方式處理是明智的,因?yàn)镻OST請(qǐng)求一般都比較“重”——用戶可能會(huì)輸入大functionstart(response)console.log("Requesthandler'start'was'charset=UTF-8"/>'+'<formaction="/upload"'<inputtype="submit"value="Submittext"/>'+}functionupload(response)console.log("Requesthandler'upload'wascalled.")?response.write("HelloUpload")?}exports.start=start?為了使整個(gè)過(guò)程非阻塞,Node.js會(huì)將POST數(shù)據(jù)拆分成很多小的數(shù)據(jù)塊,然后通過(guò)觸發(fā)特定的事件,將這些小數(shù)據(jù)塊傳遞給回調(diào)函數(shù)。這里的特定的事件有da為了使整個(gè)過(guò)程非阻塞,Node.js會(huì)將POST數(shù)據(jù)拆分成很多小的數(shù)據(jù)塊,然后通過(guò)觸發(fā)特定的事件,將這些小數(shù)據(jù)塊傳遞給回調(diào)函數(shù)。這里的特定的事件有data事件(表示新的小數(shù)據(jù)塊到達(dá)了)以及end事件(表示所有的數(shù)據(jù)都已經(jīng)接收完畢)。我們需要告訴Node.js當(dāng)這些事件觸發(fā)的時(shí)候,回調(diào)哪些函數(shù)。怎么告訴呢 我們通在request對(duì)象上注冊(cè)監(jiān)聽器(listener)來(lái)實(shí)現(xiàn)。這里的request對(duì)象是每次接收到HTTP請(qǐng)求問(wèn)題來(lái)了,這部分邏輯寫在哪里呢?我們現(xiàn)在只是在服務(wù)器中獲取到了request對(duì)象——我們并沒(méi)有像之前response對(duì)象那樣,把request對(duì)象傳遞給請(qǐng)求路由和請(qǐng)求處理程序。在我看來(lái),獲取所有來(lái)自請(qǐng)求的數(shù)據(jù),然后將這些數(shù)據(jù)給應(yīng)用層處理,應(yīng)該是HTTP的事情。因此,我建議,我們直接在服務(wù)器中處理POST數(shù)據(jù),然后將最終的數(shù)據(jù)傳遞給請(qǐng)求由和請(qǐng)求處理器,讓他們來(lái)進(jìn)行進(jìn)一步的處理。因此,實(shí)現(xiàn)思路就是:將data和end事件的回調(diào)函數(shù)直接放在服務(wù)器中,在data事件回調(diào)中收集所有的POST數(shù)據(jù),當(dāng)接收到所有數(shù)據(jù),觸發(fā)end事件后,其回調(diào)函數(shù)調(diào)用請(qǐng)求路由,并將數(shù)據(jù)傳遞給它,然后,請(qǐng)求路由再將該數(shù)據(jù)傳遞給請(qǐng)求處理程序。varurl=require("url")?functionstart(route,handle){{varpostData=varpathname=url.parse(request.url).pathname?postData+=postDataChunk?request.addListener("data",function(chunk)//calledwhenanewchunkofdatawasrequest.addListener("end",function()//calledwhenallchunksofdatahavebeen上述代碼做了三件事情:首先,我們?cè)O(shè)置了接收數(shù)據(jù)的編碼格式為UTF-8,了“data”事件的監(jiān)聽器,用于收集每次接收到的新數(shù)據(jù)塊,并將其賦值給postD上述代碼做了三件事情:首先,我們?cè)O(shè)置了接收數(shù)據(jù)的編碼格式為UTF-8,了“data”事件的監(jiān)聽器,用于收集每次接收到的新數(shù)據(jù)塊,并將其賦值給postData變量,最后,我們將請(qǐng)求路由的調(diào)用移到end事件處理程序中,以確保它只會(huì)當(dāng)所有數(shù)據(jù)接收完畢后才觸發(fā),并且只觸發(fā)一次。我們同時(shí)還把POST數(shù)據(jù)傳遞給請(qǐng)求路由,因?yàn)檫@些數(shù)據(jù),請(qǐng)求處理程序會(huì)用到。上述代碼在每個(gè)數(shù)據(jù)塊到達(dá)的時(shí)候輸出了日志,這對(duì)于最終生產(chǎn)環(huán)境來(lái)說(shuō),是很不好的(量可能會(huì)很大,還記得吧?),但是,在開發(fā)階段是很有用的,有助于讓我們看到發(fā)生了什么。再來(lái)點(diǎn)酷的。我們接下來(lái)在/upload頁(yè)面,展示用戶輸入的內(nèi)容。要實(shí)現(xiàn)該功能,我們需要將postData傳遞給請(qǐng)求處理程序,修改router.js為如下形式:functionroute(handle,pathname,response,{console.log("Abouttoroutearequestfor"+pathname)?if(typeofhandle[pathname]==='function'){handle[pathname](response,}elseconsole.log("Norequesthandlerfoundfor"+pathname)?response.write("404Notfound")?}}exports.route=postDataChunk+request.addListener("end",}console.log("Serverhasstarted.")?}exports.start=我們最后要做的是:當(dāng)前我們是把請(qǐng)求的整個(gè)消息體傳遞給了請(qǐng)求路由和請(qǐng)求處理程序。我們應(yīng)該只把POST我們最后要做的是:當(dāng)前我們是把請(qǐng)求的整個(gè)消息體傳遞給了請(qǐng)求路由和請(qǐng)求處理程序。我們應(yīng)該只把POST數(shù)據(jù)中,我們感興趣的部分傳遞給請(qǐng)求路由和請(qǐng)求處理程序。在我們這個(gè)例子中,我們感興趣的其實(shí)只是text字段。我們可以使用此前介紹過(guò)的querystringfunctionstart(response,postData){console.log("Requesthandler'start'was'charset=UTF-8"/>'+'<formaction="/upload"'<textareaname="text"rows="20"functionstart(response,{console.log("Requesthandler'start'was'charset=UTF-8"/>'+'<formaction="/upload"'<inputtype="submit"value="Submittext"/>'+}functionupload(response,{console.log("Requesthandler'upload'wascalled.")?response.write("You'vesent:"+postData)?}exports.start=start?處理文件上回到90年代,這個(gè)用例完全可以滿足用于IPO的商業(yè)模型了,如今,我們通過(guò)它能學(xué)到這樣兩件事情:處理文件上回到90年代,這個(gè)用例完全可以滿足用于IPO的商業(yè)模型了,如今,我們通過(guò)它能學(xué)到這樣兩件事情:如何安裝外部Node.js模塊,以及如何將它們應(yīng)用到我們的應(yīng)用中。這里我們要用到的外部模塊是FelixGeisend?rfer開發(fā)的node-formidable模塊。它對(duì)解析上傳的文件數(shù)據(jù)做了很好的抽象。其實(shí)說(shuō)白了,處理文件上傳“就是”處理POST數(shù)據(jù)——但npmoknpminstall}functionupload(response,{console.log("Requesthandler'upload'wascalled.")?response.write("You'vesentthetext:"+}exports.start=start?現(xiàn)在我們就可以用formidable模塊了——使用外部模塊與內(nèi)部模塊類似,用現(xiàn)在我們就可以用formidable模塊了——使用外部模塊與內(nèi)部模塊類似,用require引入即可:這里該模塊做的就是將通過(guò)HTTPPOST請(qǐng)求提交的表單,在Node.js中可以被解析。我們要做如果我們將上述代碼,保存到一個(gè)文件中,并通過(guò)node來(lái)執(zhí)行,就可以進(jìn)行簡(jiǎn)單的表單提交了,包括文件上傳。然后,可以看到通過(guò)調(diào)用form.parse傳遞給回調(diào)函數(shù)的files如下所示:http=require('http'),sys=if(req.url=='/upload'&&req.method.toLowerCase()=='post')//parseafilevarform=newformidable.IncomingForm()?res.writeHead(200,{'content-type':'text/plain'})?res.write('receivedupload:\n\n')?}//showafileupload'<inputtype="text"'<inputtype="submit"value="Upload">'+varformidable=我們先來(lái)解決后面那個(gè)問(wèn)題 對(duì)于保存在本地硬盤中的文件,如何才能在瀏覽器中看到呢顯然,我們需要將該文件讀取到我們的服務(wù)器中,使用一個(gè)叫fs我們先來(lái)解決后面那個(gè)問(wèn)題 對(duì)于保存在本地硬盤中的文件,如何才能在瀏覽器中看到呢顯然,我們需要將該文件讀取到我們的服務(wù)器中,使用一個(gè)叫fs我們來(lái)添加/showURL的請(qǐng)求處理程序,該處理程序直接硬編碼將文件/tmp/test.png到瀏覽器中。當(dāng)然了,首先需要將該圖片保存到這個(gè)位置才行。將requestHandlers.jsfs=require("fs")?functionstart(response,{console.log("Requesthandler'start'was'<metahttp-equiv="Content-Type"'+'<formaction="/upload"'<inputtype="submit"value="Submittext"/>'+}received{fields:{title:'HelloWorld'},{{size:name:'us-flag.png',type:lastModifiedDate:Tue,21Jun201107:02:41length:[Getter],filename:[Getter],mime:[Getter]}}}重啟服務(wù)器之后,通過(guò)訪問(wèn)http://localhost:88重啟服務(wù)器之后,通過(guò)訪問(wèn)http://localhost:8888/show,就可以看到保存在/tmttg的圖片了。在/start將node-formidable整合到我們的uploadvarrouter=varrequestHandlers=varhandle=handle["/"]=requestHandlers.start?handle["/start"]=requestHandlers.start?handle["/show"]=requestHandlers.show?server.start(router.route,functionupload(response,{console.log("Requesthandler'upload'wascalled.")?response.write("You'vesentthetext:"+}functionshow(response,{console.log("Requesthandler'show'wascalled.")?{if(error)response.write(error+"\n")?}elseresponse.write(file,"binary")?}}exports.start=start?exports.show=show?將上傳的圖片內(nèi)嵌到/uploadURL輸出的HTML第一項(xiàng)很簡(jiǎn)單。只需要在HTML表單中,添加一個(gè)mu將上傳的圖片內(nèi)嵌到/uploadURL輸出的HTML第一項(xiàng)很簡(jiǎn)單。只需要在HTML表單中,添加一個(gè)multipartform-data的編碼類型,移除此前的文本區(qū),添加一個(gè)文件上傳組件,并將提交按鈕的文案改為“Uploadfile”即可。如下requestHandler.jsfs=require("fs")?functionstart(r
溫馨提示
- 1. 本站所有資源如無(wú)特殊說(shuō)明,都需要本地電腦安裝OFFICE2007和PDF閱讀器。圖紙軟件為CAD,CAXA,PROE,UG,SolidWorks等.壓縮文件請(qǐng)下載最新的WinRAR軟件解壓。
- 2. 本站的文檔不包含任何第三方提供的附件圖紙等,如果需要附件,請(qǐng)聯(lián)系上傳者。文件的所有權(quán)益歸上傳用戶所有。
- 3. 本站RAR壓縮包中若帶圖紙,網(wǎng)頁(yè)內(nèi)容里面會(huì)有圖紙預(yù)覽,若沒(méi)有圖紙預(yù)覽就沒(méi)有圖紙。
- 4. 未經(jīng)權(quán)益所有人同意不得將文件中的內(nèi)容挪作商業(yè)或盈利用途。
- 5. 人人文庫(kù)網(wǎng)僅提供信息存儲(chǔ)空間,僅對(duì)用戶上傳內(nèi)容的表現(xiàn)方式做保護(hù)處理,對(duì)用戶上傳分享的文檔內(nèi)容本身不做任何修改或編輯,并不能對(duì)任何下載內(nèi)容負(fù)責(zé)。
- 6. 下載文件中如有侵權(quán)或不適當(dāng)內(nèi)容,請(qǐng)與我們聯(lián)系,我們立即糾正。
- 7. 本站不保證下載資源的準(zhǔn)確性、安全性和完整性, 同時(shí)也不承擔(dān)用戶因使用這些下載資源對(duì)自己和他人造成任何形式的傷害或損失。
最新文檔
- 2025-2030年中國(guó)電子熱管理產(chǎn)品行業(yè)商業(yè)模式創(chuàng)新戰(zhàn)略制定與實(shí)施研究報(bào)告
- 2025-2030年中國(guó)產(chǎn)業(yè)園區(qū)物業(yè)管理行業(yè)商業(yè)模式創(chuàng)新戰(zhàn)略制定與實(shí)施研究報(bào)告
- 2025-2030年中國(guó)金融押運(yùn)行業(yè)商業(yè)模式創(chuàng)新戰(zhàn)略制定與實(shí)施研究報(bào)告
- 2025-2030年中國(guó)掃地機(jī)器人行業(yè)全國(guó)市場(chǎng)開拓戰(zhàn)略制定與實(shí)施研究報(bào)告
- 銷售人員心態(tài)培訓(xùn)課件
- 四川省眉山市2024屆高三下學(xué)期第三次診斷考試英語(yǔ)試題
- 家用壁式電風(fēng)扇行業(yè)行業(yè)發(fā)展趨勢(shì)及投資戰(zhàn)略研究分析報(bào)告
- 中藥提取物項(xiàng)目可行性研究報(bào)告
- 推廣服務(wù)行業(yè)深度研究報(bào)告
- 廣西桂林市灌陽(yáng)縣2021-2022學(xué)年五年級(jí)上學(xué)期英語(yǔ)期末試卷
- 【重慶武隆區(qū)文旅品牌傳播存在的問(wèn)題及優(yōu)化建議分析13000字(論文)】
- 北大荒2023審計(jì)報(bào)告
- 鍋爐安裝竣工報(bào)告
- 水土保持監(jiān)理工作報(bào)告
- 時(shí)間管理學(xué)習(xí)通超星課后章節(jié)答案期末考試題庫(kù)2023年
- 分子影像學(xué)概論課件
- 中國(guó)移動(dòng)呼叫中心的精細(xì)化管理
- (全)2023電氣工程師內(nèi)部考試習(xí)題含答案(繼保)
- 辣椒栽培技術(shù)
- 紀(jì)檢監(jiān)察知識(shí)題庫(kù)-案例分析(20題)
- 《笨狼的故事》讀書會(huì)讀書分享PPT課件(帶內(nèi)容)
評(píng)論
0/150
提交評(píng)論