第一個實例-----我與node.js的第一步接觸 (轉載)

第一個實例-----我與node.js的第一步接觸javascript

      由於最近有東西須要用到node.js,因此我就在linux虛擬機上安裝了node.js,對於javascript,也是第一次接觸。 html

      剛入門,就是一個實用的案例,畢竟這些東西都是實踐出真知。這個案例就是一個web應用,容許用戶上傳圖片並在當前網頁顯示出來。咱們來看看,這個案例是如何從一個簡簡單單的代碼變成具備咱們上面功能的應用。java

      咱們先看看如何在網頁顯示咱們輸出的消息,這條消息天然就是咱們每一個程序員所寫下的第一句話:「Hello Word」。是的,這句話已經成爲咱們不管學習什麼語言或是任何操做系統,都要寫的第一句話,由於這句話說明咱們已經邁出了新世界的第一步!node

      先上代碼:linux

     

var http = require("http");
http.createServer(function(request, response) {     response.writeHead(200, {"Content-Type": "text/plain"});       response.write("Hello World");  
    response.end();}).listen(8888);

這段簡短的代碼就已經告訴咱們如何在網頁中顯示消息。首先,是第一句,注意require()函數,這個函數是表示咱們要申請的模塊,這裏申請的是http模塊並把它的返回值賦值給咱們聲明的變量,這樣咱們的變量就會獲得http模塊所提供的全部公共方法(將該變量命名爲咱們所要申請的模塊是一個好習慣,固然你徹底能夠自定義)。什麼是模塊?咱們能夠將模塊理解爲庫或包,事實上,它也差很少正是這樣,由於這些模塊裏面,封裝着咱們想要使用的函數,接下來咱們還會學到,怎樣建立本身的模塊。好了,如今進入正題。也許有些人會問,什麼是http模塊?好吧,若是真的有人由於這個問題而沒法前進,那麼我就在這裏講一下,可是,我真的不是什麼高手,javascript我只是稍微懂點基礎罷了,你們只要將它簡單認爲是這樣的東西,當咱們要在網頁顯示消息的時候,必定要發出一個請求,告訴服務器,我要在一個網頁前顯示消息,而服務器就會響應咱們的請求顯示出來,因此,你們在接下來的代碼中能夠看到,http模塊中的createServer()的參數是一個響應處理的函數(在javascript中,函數是能夠做爲參數的,由於函數自己是對象),這個函數須要兩個參數,request(請求)和response(響應)。對於createServer()這個函數,它的參數是一個requestListener,就是自動被添加到request事件上的函數,而後返回一個網頁服務對象,就是顯示咱們消息的網頁(它的做用就是創建一個能夠響應請求的服務器)。咱們要想輸出「Hello Word"這個消息,最主要的是在響應這方面下工夫。咱們先要設置一下咱們的消息的顯示格式,好比在writeHead()函數中設置狀態碼和內容類型,這裏是txt,因此就是「text/plain",而後就是用write()設定咱們要輸出的消息,最後就是以end()來結束咱們此次的響應動做。不少人必定對這個函數有個疑問,就是那兩個參數究竟是什麼?好吧,這裏我就稍微講一下,第一個是狀態碼,表示網頁服務器http響應狀態的三位數字,像是咱們上面的200,就表示咱們的請求已成功,請求所但願的響應頭或數據體將隨此響應返回,若是是404,就表示請求失敗,請求所但願獲得的資源未被服務器發現,因此404這個狀態碼被普遍應用於當服務器不想揭示到底爲什麼請求被拒絕或者沒有其餘適合的響應可用的狀況(如今你知道爲何當咱們瀏覽的網頁出現問題時會顯示404了吧)。詳細的狀況請看這條連接:http://baike.baidu.com/view/1790469.htm。第二個是表示MIME類型,所謂的MIME類型,就是設定某種擴展名的文件用一種應用程序來打開的方式類型,當該擴展名文件被訪問的時候,瀏覽器會自動使用指定應用程序來打開,多用於指定一些客戶端自定義的文件名,以及一些媒體文件打開方式,像是圖片,txt等等,這裏的text/plain就表示普通文檔txt,若是是png圖片,就是image/png,更多的狀況看下面這條連接:http://baike.baidu.com/view/160611.htmandroid

      相信你們注意到了,最後的listen()這個監聽器。只要有作過界面的人,像是搞過android控件的人,必定對監聽器很是熟悉。這裏就是監聽咱們的端口號爲8888的網頁響應動做,只要咱們運行上面的代碼,而後在咱們的瀏覽器中輸入http://localhost:8888 /就會在當前的網頁中看到"Hello Word"。程序員

      值得注意的是,咱們這裏有一個匿名函數,這個函數就是咱們處理響應和請求的實際處理函數,咱們能夠將它們提取出來,像是下面這樣:web

      

複製代碼
var http = require("http");
function onRequest(request, response){
       response.writeHead(200, {"Content-Type":"text/plain"});
       response.write("Hellod Word");
       response.end();
}
http.createServer(onRequest).listen(8888);
複製代碼

結果都是同樣的,至於使用哪一種方式,就看我的須要,不過匿名函數的使用方式比較廣泛。shell

      爲何要對咱們的處理函數進行監聽呢?那是由於node.js是基於事件驅動的。好吧,這句話實在是太泛泛而談,就像咱們的大學課堂老師,老是用似是而非的話來忽悠咱們(由於他們本身也是似是而非)。node.js的工做原理是這樣的:除了咱們的代碼,它的全部東西都是並行的!幾乎全部的東西都是同時運行的!爲何是除了咱們的代碼呢?你能夠這樣想象,咱們的代碼就比如是整個應用的國王,它一直都在昏昏欲睡,而後,它有一大幫手下,當一個手下向他詢問任務的時候,它會醒過來,指示任務而後繼續昏昏欲睡,在上一個手下在執行任務的時候,會有其餘手下繼續詢問任務而且執行。問題,爲何咱們的代碼要"昏昏欲睡"呢?只要稍微明白併發的同窗就會明白,若是不這樣作,就會出現同時多個手下詢問任務的狀況。這種狀況很差嗎?是的,很很差,由於這個國王並非一個多麼能幹的國王(請別介意,事實上咱們所寫的代碼真的絕大部分都是不"精明"的),因此,出現這種狀況,它可能會出現錯誤,因此,一對一是最好的,並且這樣的效率更高。而後,每一個手下在完成任務後就會向咱們的國王報告狀況,這也是一對一的。因此,你們明白了沒?(事實上,我也不是很明白,可是接觸過併發,因此多多少少都能想象出來),因此,這裏就有個監聽器,這個監聽器就是當咱們的任務完成時向咱們的國王進行報告。npm

      關於這個例子還沒完,由於這裏有個重要的知識點必須講清楚,就是回調。想象這種狀況,咱們的服務器是跑在一個單線程裏的(是的,node.js是單線程的,由於它已經將除了代碼之外的操做都設置爲並行的),可是咱們的服務器是要處理http請求的,這個請求但是任什麼時候候都會過來,咱們的服務器可以剛好的響應嗎?因而,回調就變得很是重要,就是咱們createServer()裏的函數參數。不管什麼時候,什麼樣的請求過來,只要它做爲參數傳進來,那麼,咱們的服務器都能根據這個參數自動調用合適的函數,恰當的處理這個請求,這就是回調(不熟悉回調的話,你只要這麼想,回調的英文就是callback,咱們的函數已經寫好了,就像一個選秀選手在後臺等着咱們的主持人叫他出來表演,當主持人拿到它的號碼(參數),就會叫他,這就是回調)。因此,咱們的服務器是異步的。        就算咱們的回調函數沒有被調動,其餘代碼仍是能夠被執行的。就像這樣:

function onRequest(request, response){
       console.log("server has started");
       ....
}

這裏只是在回調函數裏添加一句:console.log("server has started")(若是不清楚這一句的話,就將它當成咱們javascript中的alert()),原本應該是不會執行的,若是是咱們平時的串行代碼(就算是咱們之前寫的併發也是不行的,除非是特殊的方法),可是,神奇的海螺小姐又出現了!它可以被顯示出來!咱們要分清楚一個問題,就是咱們的請求真正想要觸發的,是我添加的語句的下面,前面並非回調函數真正想要執行的部分(我用了「真正」這個詞,由於這裏必須跟你們說明一下,咱們這裏處理的只是response的部分,我並無對request進行任何細節上的處理,因此,理論上,當任何請求過來的時候它都能被執行,至因而否可以被響應,則是看請求的類型),因此,當有請求過來的時候,就算它不能觸發咱們的回調函數,咱們的代碼只要可以執行,仍是會執行的,由於這時任何請求都能使它運行。      好了,咱們繼續往下講,前面的廢話實在是太多了(請原諒我,由於我也是一隻初學雞,因此有些問題很你們同樣,也是一頭霧水,只能將本身知道的全部東西都倒出來了)。 

     咱們上面講的,只是實現一個很是基礎的http服務器,它還並非一個真正意義上的node.js模塊,由於它不能被其餘模塊調用。那麼,咱們接下來怎樣讓它變成真正的模塊呢?就是抓住我上面的關鍵點,可以被其餘模塊調用。一般,像是上面的建立服務器的代碼,咱們通常命名爲server.js,並且咱們都會有一個index.js做爲驅使其餘模塊的國王。接下來咱們就是要讓這個手下可以聽話了:

    

複製代碼
var http = require("http");
function start(){
     function onRequest(request, response){
            ...
     }
     http.createServer(onRequest).listen(8888);
 }
exports.start = start;
複製代碼

咱們這段代碼惟一的不一樣點就是咱們在結尾多了這一句:exports.start = start。這段代碼就是將咱們的start()函數導出去,而後讓咱們的index.js可以啓動這個服務器,由於通常咱們對服務器的操做就僅是啓動它而已。       咱們在index.js中只需這麼寫:

     

var server = require("./server");
server.start();

一樣的道理,咱們請求server模塊並將其賦給一個變量,而後調用這個模塊中的start()函數。這裏與咱們上面請求內置模塊是同樣的操做,可是,自定義的模塊的請求是須要在前面加上"./"。

      經過上面的例子,咱們已經能夠初步掌握如何建立模塊以及如何在另外一個模塊中調用其餘模塊的方法,接下來,仍是繼續探討其餘模塊的建立,就是有關於處理不一樣的url請求。固然,咱們徹底能夠將代碼放在咱們已經建好的server模塊中,可是咱們通常都會新建一個模塊,由於處理url請求並非服務器要作的,它是要交給路由器的(路由器這個詞你們必定很是熟悉吧,路由這個名字真的是很是形象,我對於譯者的崇拜之情油然而生)。咱們仍是立刻創建一個路由器模塊router.js吧:

     

function route(pathname){
       console.log("About to route:" + pathname);
}
exports.route = route;

而後咱們的服務器仍是要進行修改:

     

複製代碼
var http = require("http");
    url = require("url");
function start(route){
     function onRequest(requset, response){
           var  pathname = url.parse(request.url).pathname;
            console.log(pathname + "has received");
            route(pathname);       }
...
}
exports.start = start;
複製代碼

接着就是index.js:

     

var server = require("./server");
    router = require("./router");
server.start(router.route);

相信你們必定仍是會注意到,就是導出來的函數在除了index.js以外的其餘模塊中,根本就不須要申請router模塊就能直接使用,像是做爲參數傳遞。我曾經將index.js的server.start()直接改成start(),結果它顯示的是start()沒有被定義這個錯誤,而後繼續手賤,在server.js中添加一個route的函數而且導出,可是什麼狀況都沒有發生!route函數並沒顯示任何錯誤!按道理來講,像是咱們的java,若是你的兩個類中的方法名是同樣的,你必須指點對象,可是這裏彷佛並非這樣的。關於這個問題,我想放在後面再講,由於後面的代碼中會出現新的start函數。

     咱們這裏要先講一下路由器的工做原理。路由器會根據請求的url和其餘須要的get,post參數來執行相應的處理程序,因而咱們須要從http請求中提取出url和get,post參數。這一部分的功能究竟是路由器仍是服務器或者做爲一個新的功能模塊,咱們這裏就不討論,由於這也太難爲我這個初學者了,這是個人第一個應用,我對web編程並無更多的經驗,因此,這裏就先放在server模塊裏。好了,那麼,咱們所須要的參數都是來自於請求,就是request,那麼,咱們怎樣提取出來呢?解析request,咱們須要其餘模塊,像是我上面用到的url模塊,事實上,咱們還與querystring模塊,至於哪一個更好,咱們先表下不談,就着url模塊來說如何提取,下面就是這兩個模塊的提取原理:

     

                               url.parse(string).query
                                           |
           url.parse(string).pathname      |
                       |                   |
                       |                   |
                     ------ -------------------
http://localhost:8888/start?http=bar&hello=world
                                ---       -----
                                 |          |
                                 |          |
              querystring(string)["http"]    |
                                            |
                         querystring(string)["hello"]

對於上面的原理,咱們只須要知道/start?http=bar&hello=world這一部分。相信你們看圖就能明白這裏面的組成。/start是咱們在index.js中調用的server中的start函數,根就是咱們的路徑名pathname,後面就是咱們的顯示消息,「Hello Word",可是這裏倒是"hell0=word",爲何會是這樣呢?若是接觸過javascript的同窗就不難明白,由於咱們的url是被處理過的,全部的字母都已經被轉化爲小寫,而且空格對應於=。這部分我就再也不講解了,由於我以爲個人javascript知識也實在是太匱乏了,因此你們感興趣的話,仍是本身看看相關的書吧。

       咱們如今已經能夠經過不一樣的url路徑來區別不一樣的請求了,也把路由器和服務器整合起來,如今咱們來看看這裏面的一些亮點吧。首先,就是咱們的服務器要想知道路由器的存在而且加以利用,就像我前面講過的,咱們爲何不能夠直接將這個路由器硬編碼進咱們服務器代碼中呢?咱們能夠這麼幹:

      

function start(router.route){
      ...
}

可是,咱們都知道,這樣作的壞處就是咱們的代碼太"硬"了!編寫這樣的代碼,會使得咱們的複用性大大降低,因此,node.js採用的是依賴注入的方式來實現鬆散的注入路由模塊。依賴注入,這是一個新字眼,相信我,這個字眼所表明的意思咱們在java這樣的面相對象編程語言中已是接觸過了,可是它的實際意義倒是很是龐大的,像我這樣的新手固然是不敢有任何奢望來向你們講解這個話題,可是我會結合咱們上面的例子稍微講一下。依賴注入,說白一點,就是咱們的對象的建立的時候若是須要獲得另外一個對象的引用(由於咱們的對象的建立可能須要另外一個對象中的方法),那麼咱們固然是會將那個對象的引用做爲參數傳進來。這就是所謂的"依賴",傳對象的引用就是所謂的"注入"。這樣的行爲咱們在java中是習覺得常的,可是,這種行爲會形成咱們的代碼的耦合性較大,由於咱們的對象的建立是須要其餘代碼的,這樣,當這部分的代碼發生變化的時候,咱們的新對象就要發生變化。哈,這樣好像我是在將依賴注入是一種很差的行爲,可是,我必須強調,這裏的狀況是指咱們傳進的對象是特定的對象。好比說,像是上面的例子,咱們能夠在新建一個js文件,這個js文件裏也有一個導出的route函數,那麼,若是是咱們之前的思惟,那麼,在咱們的server.js中傳進的route函數必定要制定是哪個,可是,即便你真這麼作了,也不須要這麼作,由於咱們的解釋器可以找到正確的執行函數(我不知道這裏面是怎麼發生,這個問題我想,我後面會專門寫篇文章來研究一下,這裏就先跳過)。因此,咱們的代碼就不會發生於特定的對象耦合的狀況,這樣之後若是要修改的話,就會相對容易,也不會有反作用。其實,我感受這點可能與javascript中解釋器尋找函數的機制是同樣的,由於javascript的函數的調用是能夠只寫函數名的!另外,稍微說點題外話,其實上面的話題是與函數式編程有點,因爲這方面我根本沒有接觸,因此上面講的天然是小生亂彈了。

      上面的例子咱們的路由模塊並無針對不一樣的url執行不一樣的行爲,只是咱們的服務器可以與路由溝通了而已。因此,咱們的腳步仍要繼續。

複製代碼
function start() {  
       console.log("Request handler 'start' was called.");
}
function upload() {  
       console.log("Request handler 'upload' was called.");
}
exports.start = start;
exports.upload = upload;
複製代碼

這裏的requestHandler模塊中的start()和upload()函數也並無什麼真正的處理,它們只是佔位用的函數,就是咱們爲咱們的start和upload這樣的處理佔個位置。而後咱們的問題又來了,就是咱們到底要將這段代碼放在哪裏?這裏是將處理動做和路由聯繫起來,可是咱們須要將這段代碼硬編碼進咱們的服務器裏嗎?而後這樣寫:      if(request == x){

        handler y;

}

這樣咱們豈不是要隨着咱們所要處理的請求的增長而不斷擴充if!事實證實,當你的代碼中有一大串if,那說明你的代碼必定有問題!因此。正確的作法就是咱們再從新寫一個新的模塊,這一次就是requestHandlers模塊,而後再將這個對象傳進來。

      咱們的代碼以下:

      首先,是index.js模塊:

     

複製代碼
var server = require("./server");
var router = require("./router");
var requestHandlers = require("./requestHandlers"); var handle = {}
handle["/"] = requestHandlers.start;
handle["/start"] = requestHandlers.start;
handle["/upload"] = requestHandlers.upload;
server.start(router.route, handle);
複製代碼

咱們能夠看到,這裏有一個handle對象,並且接下來的語句就讓人很頭疼:handle["/"] = requestHandlers.start.怎麼回事?爲何會這樣寫啊?其實,javascritp的對象只是鍵/值對的集合,咱們能夠想象成是一個鍵位字符串類型的字典,而且值能夠是函數。因此,咱們這裏就徹底看到了這種特性,咱們的對象甚至能夠這樣寫:handle["/"],"/"就是一個鍵,而後它的值就是requestHandlers.start。用這樣的方式就能將咱們requestHandlers中的處理行爲放進一個對象裏。      接下來就是server.js:

    

複製代碼
var http = require("http");
var url = require("url");
function start(route, handle) {  
      function onRequest(request, response) {   
             var pathname = url.parse(request.url).pathname;    
             console.log("Request for " + pathname + "received.");    
             route(handle, pathname);    
             response.writeHead(200, {"Content-Type": "text/plain"});    
             response.write("Hello World");    
             response.end();  
      }  
http.createServer(onRequest).listen(8888);  
console.log("Server has started.");
}
exports.start = start;
複製代碼

router.js也要相應的作出改變:     

複製代碼
function route(handle, pathname) {  
      console.log("About to route a request for " +  pathname);  
      if (typeof handle[pathname] === 'function') {    
               handle[pathname]();  
      } else {    
          console.log("No request handler found for " + pathname);  
      }
}
exports.route = route;
複製代碼

這裏咱們要先判斷咱們的handle對象相應的值是不是處理函數,若是是,咱們就調用。       如今咱們的服務器,路由和處理程序已經聯繫在一塊兒了。

      好了,咱們如今要作的就是i,咱們不想要咱們的瀏覽器就只是返回"Hello Word"這麼沒有養分的信息,咱們想要咱們的處理程序可以返回有用的相關信息,固然咱們徹底能夠這樣作,就是在相關的處理程序的最後return相關信息。是的,這樣作的確沒有錯,可是,會出現問題,就是咱們的代碼會掛!是的,這裏就是阻塞問題了。

     上面的操做是阻塞的,咱們會想要這樣子作,就是當程序還在處理一個請求的同時,咱們會想要咱們的程序繼續處理新的請求。不要感到難以想象,這是web編程,想一想咱們平時使用瀏覽器的時候,老是喜歡同時打開多個頁面是吧,若是像是上面那樣作,咱們的代碼就會出現阻塞,就會出現一個頁面必須等待另外一個頁面打開完成。那麼,該怎麼才能使用所謂的非阻塞操做呢?

     咱們先從server.js下手:

    

複製代碼
var http = require("http");
var url = require("url");
function start(route, handle) {  
     function onRequest(request, response) {    
           var pathname = url.parse(request.url).pathname;   
           console.log("Request for " + pathname + " received.");    
           route(handle, pathname, response);        }       http.createServer(onRequest).listen(8888);       console.log("Server has started."); } exports.start = start;
複製代碼

這裏的區別就是咱們將response做爲參數傳給route函數,而後咱們繼續看route函數:      

複製代碼
function route(handle, pathname, response) {       console.log("About to route a request for " + pathname);       if (typeof handle[pathname] === 'function') {             handle[pathname](response);       } else {             console.log("No request handler found for " + pathname);             response.writeHead(404, {"Content-Type": "text/plain"});             response.write("404 Not found");             response.end();      } } exports.route = route;
複製代碼

接着咱們再將requestHandlers.js進行修改:      

複製代碼
var exec = require("child_process").exec;
function start(response) {       console.log("Request handler 'start' was called.");       exec("ls -lah", function (error, stdout, stderr) {             response.writeHead(200, {"Content-Type": "text/plain"});             response.write(stdout);             response.end();       }); } function upload(response) {       console.log("Request handler 'upload' was called.");       response.writeHead(200, {"Content-Type": "text/plain"});       response.write("Hello Upload");       response.end(); } exports.start = start; exports.upload = upload;
複製代碼

這裏咱們注意一下這個新的模塊"child_process」,這個模塊有一個exec函數,這是一個非阻塞的函數,它真的是很可怕的東西!爲何這麼說呢?咱們來舉個例子:     

複製代碼
var exec = require("child_process").exec;
function start(){
     var content = "empty";
     exec("ls-lah", function(error, stdout, stderr){
             content = stdout;
     }
     return content;
}
複製代碼

咱們這裏的exec執行的是一個shell命令(若是你有過linux編程,必定知道這是什麼東西),將當前目錄下的文件信息輸出到瀏覽器,可是咱們一旦真正運行,就會發現,輸出是empty。爲何呢?由於咱們的exec爲何是非阻塞的呢?由於它回調了一個函數stdout(這個函數的做用就是返回輸出的結果),而後賦給content,可是由於咱們的exec是非阻塞的,就算咱們的exec的回調還沒結束,可是咱們已經執行到下面的return content了,並且這時,content依然是"empty"。因此,明白非阻塞是很重要的,可是咱們也要注意非阻塞有可能給咱們形成的麻煩。

     繼續回到咱們上面的例子。爲何咱們要將response做爲參數而且將這部分的工做移到requestHandlers裏呢?由於咱們想要的是對response的直接響應。以前咱們的代碼是這樣的:咱們的服務器將response傳給咱們的route,咱們的route進行處理好,咱們的服務器再對resopnse進行響應,如今是這樣:咱們的服務器依然是將response傳給route,可是如今咱們的route就已經對response進行響應,這裏就少了一層,可是咱們的代碼的邏輯更加清晰,由於咱們處理response的程序和響應response的程序理應放在一塊。

      進行到這一步,咱們發現,咱們的應用根本沒有什麼實際意義!咱們仍是隻是顯示一些信息而已!不行,咱們必須作些更有用的東西出來!!此次的目標就是容許用戶上傳文件,而後咱們將這個文件顯示出來,好比說圖片。好了,咱們先來看看如何處理POST請求。

      咱們的requestHandlers.js再度修改:

     

複製代碼
function start(response) {      console.log("Request handler 'start' was called.");      var body = '<html>' + '<head>' + '<meta http-equiv="Content-Type" content="text/html; ' + 'charset=UTF-8" />' + '</head>' + '<body>' + '<form action="/upload" method="post">' + '<textarea name="text" rows="20" cols="60">    </textarea>' + '<input type="submit" value="Submit text" />' + '</form>' + '</body>' + '</html>';       response.writeHead(200, {"Content-Type": "text/html"});        response.write(body);        response.end(); } function upload(response) {      console.log("Request handler 'upload' was called.");      response.writeHead(200, {"Content-Type": "text/plain"});      response.write("Hello Upload");      response.end(); } exports.start = start; exports.upload = upload;
複製代碼

咱們這裏的修改就是多了body,是的,咱們只是顯示一個文本區供用戶輸入,而後經過POST請求提交給服務器,接着服務器經過處理程序將內容顯示到瀏覽器。因此,這裏須要生成帶文本區的表單。

      而後咱們就來處理upload。這部分固然是採用非阻塞,由於POST請求通常都是很是重的,所謂的重,就是指用戶通常都會上傳大量的內容,若是採用阻塞的操做,就會使用戶的執行動做徹底卡死。爲了使整個過程都是非阻塞的,node.js會將POST數據分紅許多小的數據塊,而後經過觸發特定的事件,將這些小的數據塊傳遞給回調函數,這裏特定的事件有data事件(表示有新的小數據塊到了),end事件(表示全部的數據塊已經接受完畢)。因此,咱們天然就要告訴解釋器,當這些事件觸發時應該回調哪些函數。咱們能夠在request上註冊監聽器,如:

     

request.addListener("data", function(chunk){
    ...
});
request.addListener("end", function(){
    ...
});

問題來了,就是這部分代碼應該放在哪裏?這種問題當咱們在不斷擴展本身程序的功能時很是常見,由於咱們必須處理好咱們每一個模塊應該要處理的功能。這裏的話,應該是放在服務器裏,由於服務器應該是獲取POST請求的數據並進行處理而後交給應用層處理。因此咱們的server.js又要進一步改造:     

複製代碼
var http = require("http"); var url = require("url"); function start(route, handle) {      function onRequest(request, response) {            var postData = "";           var pathname = url.parse(request.url).pathname;            console.log("Request for " + pathname + " received.");            request.setEncoding("utf8");            request.addListener("data", function(postDataChunk) {                 postData += postDataChunk;                 console.log("Received POST data chunk '" +  postDataChunk + "'.");            });            request.addListener("end", function() {                  route(handle, pathname, response, postData);            });       }     http.createServer(onRequest).listen(8888);      console.log("Server has started."); } exports.start = start;
複製代碼

 

這裏咱們設置數據的接收格式是UTF-8,而後咱們的data事件會不斷收集數據,最後將全部的數據塊傳遞給end事件,end事件中調用的是route函數,由於這是在全部的數據塊接收完畢後才執行的動做。這裏還有一個地方,就是每次數據到達的時候都會有輸出日誌,這對於最終的生產環境來講是很差的,可是對於開發階段來講卻能讓咱們更加直觀的追蹤咱們收到的數據。        接着咱們要進行的是upload功能,因而咱們處理數據的router.js進行如下的修改:

      

複製代碼
function route(handle, pathname, response, postData) {       console.log("About to route a request for " + pathname);       if (typeof handle[pathname] === 'function') {             handle[pathname](response, postData);       } else {             console.log("No request handler found for " + pathname);             response.writeHead(404, {"Content-Type": "text/plain"});             response.write("404 Not found");             response.end();       } } exports.route = route;
複製代碼

而後就是咱們的requestHandlers.js:

     

複製代碼
複製代碼
function start(response, postData) {  
    console.log("Request handler 'start' was called.");  
    var body = '<html>' + '<head>' + '<meta http-equiv="Content-Type" content="text/html; ' + 'charset=UTF-8" />' + '</head>' + '<body>' + 
    '<form action="/upload" method="post">' + '<textarea name="text" rows="20" cols="60"></textarea>' + '<input type="submit" value="Submit text" />'
+    '</form>' + '</body>' + '</html>';    
    response.writeHead(200, {"Content-Type": "text/html"});   
    response.write(body);    
    response.end();
}
function upload(response, postData) {  
    console.log("Request handler 'upload' was called.");  
    response.writeHead(200, {"Content-Type": "text/plain"}); 
    response.write("You've sent: " + postData); 
    response.end();
}
exports.start = start;
exports.upload = upload;
複製代碼

 

複製代碼

這個例子到這裏沒有結束,由於咱們隊POST請求中的數據感興趣的只是裏面的text數據,而不是全部的數據,因此咱們必須將這些感興趣的數據提取出來傳遞給路由和處理程序。 因而咱們就要利用以前出現過的querystring模塊:

    

複製代碼
var querystring = require("querystring"); function start(response, postData) {  
    console.log("Request handler 'start' was called.");  
    var body = '<html>' + '<head>' + '<meta http-equiv="Content-Type" content="text/html; ' + 'charset=UTF-8" />' + '</head>' + '<body>' + 
    '<form action="/upload" method="post">' + '<textarea name="text" rows="20" cols="60"></textarea>' + '<input type="submit" value="Submit text" />'
+    '</form>' + '</body>' + '</html>';    
    response.writeHead(200, {"Content-Type": "text/html"});   
    response.write(body);    
    response.end();
}
function upload(response, postData) {  
    console.log("Request handler 'upload' was called.");  
    response.writeHead(200, {"Content-Type": "text/plain"}); 
    response.write("You've sent: " + postData); 
    querystring.parse((postData)text);     response.end();
}
exports.start = start;
exports.upload = upload;
複製代碼

以上的例子只是說明咱們該處理文件(text數據),可是咱們最終的目標是要處理圖片,因此,咱們須要用到formidable模塊,可是這些模塊並非咱們的node.js默認的模塊,咱們須要使用外部模塊,咱們須要下載並安裝,因而咱們能夠經過這樣的命令:      npm install formidable;

這樣咱們的node.js就會自動下載而且安裝了。

       顯示用戶上傳的圖片,其實原理就是讀取用戶指定位置的圖片,那麼,咱們先來實現如何讀取咱們的本地圖片。

      

複製代碼
var querystring = require("querystring"),         fs = require("fs"); function start(response, postData) {       console.log("Request handler 'start' was called.");       var body = '<html>' + '<head>' + '<meta http-equiv="Content-Type" ' + 'content="text/html; charset=UTF-8" />' + '</head>' + '<body>' + '<form action="/upload" method="post">' + '<textarea name="text" rows="20" cols="60"></textarea>' + '<input type="submit" value="Submit text" />' + '</form>' + '</body>' + '</html>';         response.writeHead(200, {"Content-Type": "text/html"});         response.write(body);         response.end(); } function upload(response, postData) {      console.log("Request handler 'upload' was called.");      response.writeHead(200, {"Content-Type": "text/plain"});      response.write("You've sent the text: " + querystring.parse(postData).text);      response.end(); } function show(response, postData) {       console.log("Request handler 'show' was called.");       fs.readFile("/tmp/test.png", "binary", function(error, file) {             if(error) {                   response.writeHead(500, {"Content-Type": "text/plain"});                   response.write(error + "\n");                   response.end();             } else {                   response.writeHead(200, {"Content-Type": "image/png"});                   response.write(file, "binary");                   response.end();             }       }); } exports.start = start; exports.upload = upload; exports.show = show;
複製代碼

這裏咱們的requestHandlers.js只需作上面同樣的修改就行,添加一個專門處理文件讀取的fs模塊就行。       接下里就是實現咱們的最終要求了。咱們要作的第一步就是在/start表單中添加一個上傳元素,這個的實現很簡單,就是將咱們的表單進行修改就行,添加一個文件上傳組件(若是是對html的表單上的組件不熟悉的話,抱歉,這裏實在是沒有多少篇幅能夠講這部分的內容,還請自行百度),如:

     

複製代碼
var querystring = require("querystring"),         fs = require("fs"); function start(response, postData) {       console.log("Request handler 'start' was called.");       var body = '<html>' + '<head>' + '<meta http-equiv="Content-Type" ' + 'content="text/html; charset=UTF-8" />' + '</head>' + '<body>' + '<form action="/upload" enctype="multipart/form-data" ' + 'method="post">' + '<input type="file" name="upload">' + '<input type="submit" value="Upload file" />' + '</form>' + '</body>' + '</html>';         response.writeHead(200, {"Content-Type": "text/html"});         response.write(body);         response.end(); } function upload(response, postData) {       console.log("Request handler 'upload' was called.");       response.writeHead(200, {"Content-Type": "text/plain"});      response.write("You've sent the text: "+  querystring.parse(postData).text);      response.end(); } function show(response, postData) {       console.log("Request handler 'show' was called.");       fs.readFile("/tmp/test.png", "binary", function(error, file) {            if(error) {                 response.writeHead(500, {"Content-Type": "text/plain"});                 response.write(error + "\n");                 response.end();            } else {                 response.writeHead(200, {"Content-Type": "image/png"});                  response.write(file, "binary");                  response.end();            }       }); } exports.start = start; exports.upload = upload; exports.show = show;
複製代碼

接下來咱們須要在upload中對於上傳的圖片進行處理,可是,這裏有個問題必須知道,就是咱們須要將request傳遞給formidable的form.parse函數,可是咱們有的只是response和postData數組,因此咱們只能將request一路從服務器經過路由接着傳遞給咱們的處理程序。如今的咱們已經能夠將全部有關於postData的部分從咱們的服務器和處理程序中移除了,由於它們對於咱們的上傳文件已經沒有什麼做用了。顯示server.js:

     

複製代碼
var http = require("http"); var url = require("url"); function start(route, handle) {      function onRequest(request, response) {            var pathname = url.parse(request.url).pathname;            console.log("Request for " + pathname + " received.");            route(handle, pathname, response, request);      }      http.createServer(onRequest).listen(8888);      console.log("Server has started."); } exports.start = start;
複製代碼

而後是router.js:      

複製代碼
function route(handle, pathname, response, request) {       console.log("About to route a request for " + pathname);       if (typeof handle[pathname] === 'function') {             handle[pathname](response, request);       } else {            console.log("No request handler found for " + pathname);            response.writeHead(404, {"Content-Type": "text/html"});            response.write("404 Not found");            response.end();       } } exports.route = route;
複製代碼

formidable會將咱們的上傳文件自動保存到/tmp目錄中,咱們須要作的就是保證保存成/tmp/test.png,因此這裏咱們須要使用的是fs.renameSync(path1, path2)函數,它能使一個文件從一個路徑保存到另外一個路徑中,而且它是同步的。requestHandlers.js以下:      

複製代碼
var querystring = require("querystring"),         fs = require("fs"),         formidable = require("formidable"); function start(response) {      console.log("Request handler 'start' was called.");     var body = '<html>' + '<head>' + '<meta http-equiv="Content-Type" content="text/html; ' + 'charset=UTF-8" />' + '</head>' + '<body>' + '<form action="/upload" enctype="multipart/form-data" ' + 'method="post">' + '<input type="file" name="upload" multiple="multiple">' + '<input type="submit" value="Upload file" />' + '</form>' + '</body>' + '</html>';        response.writeHead(200, {"Content-Type": "text/html"});        response.write(body);        response.end(); } function upload(response, request) {       console.log("Request handler 'upload' was called.");       var form = new formidable.IncomingForm();      console.log("about to parse");       form.parse(request, function(error, fields, files) {             console.log("parsing done");             fs.renameSync(files.upload.path, "/tmp/test.png");            response.writeHead(200, {"Content-Type": "text/html"});             response.write("received image:<br/>");             response.write("<img src='/show' />");            response.end();   });} function show(response) {       console.log("Request handler 'show' was called.");       fs.readFile("/tmp/test.png", "binary", function(error, file) {             if(error) {                  response.writeHead(500, {"Content-Type": "text/plain"});                  response.write(error + "\n");                 response.end();             } else {                   response.writeHead(200, {"Content-Type": "image/png"});                   response.write(file, "binary");                   response.end();             }       }); } exports.start = start; exports.upload = upload; exports.show = show;
複製代碼

如今上圖:

好了,到了這裏,咱們所要實現的目標已經達成了。相信你們必定對於node.js有了必定的初步瞭解了。

相關文章
相關標籤/搜索