nodejs wiki

91
Node.js Taiwan 社群強力推薦 實戰 程式設計 e Node.js Node.js Little Book 中文版 適用最新版 Node.js 0.6.7 本書由 Node.js Taiwan 社群程式設計師編寫 最快樂的 Node.js 入門學習指南 ContPub Press Node.js Taiwan

Upload: kurt-nan

Post on 17-Jan-2016

59 views

Category:

Documents


0 download

DESCRIPTION

Nodejs Wiki

TRANSCRIPT

Page 1: Nodejs Wiki

Nodejs Taiwan社群強力推薦實戰程

式設計

e

NodejsNodejsLittle Book

中文版

適用最新版 Nodejs 067

本書由 Nodejs Taiwan社群程式設計師編寫

最快樂的 Nodejs入門學習指南

ContPub Press Nodejs Taiwan

Nodejs中文電子書

Nodejs Taiwan

2012年 05月 02日

目錄

關於本書 1

授權 1作者 2下載電子書 2原始碼 201 精選文章收錄流程 3

前言 5

1 Nodejs簡介 7

2 JavaScript與 NodeJS 11

21 Event Loop 1122 Scope與 Closure 1223 Callback 1324 CPS(Continuation-Passing Style) 1625 函數返回函數與 Currying 1726 流程控制 18

i

3 Nodejs安裝與設定 25

31 Ubuntu Linux 2532 Other Linux 2633 Windows 26

4 Nodejs基礎 29

41 nodejs http伺服器建立 2942 nodejs http路徑建立 3143 nodejs檔案讀取 3444 nodejs http靜態檔案輸出 3645 nodejs http GET資料擷取 3846 本章結語 40

5 NPM套件管理工具 41

51 安裝 NPM 4152 使用 NPM安裝套件 4553 套件的更新及維護 4854 使用 packagejson 49

6 Express介紹 51

61 Express安裝 5162 Express基本操作 5263 Express路由處理 5264 Express middleware 5565 Express路由應用 5666 Express GET應用範例 5767 Express POST應用範例 6068 Express AJAX應用範例 6369 原始資料提供 67

7 CoffeeScript 69

8 製作一個 Hubot的 Plurk Adapter 71

81 應用事項提醒 7182 建立 Adapter 71

ii

83 建立 Robot跟 API 7284 原始資料提供 78

9 Nodejs好用工具介紹 79

91 logio 79

10 精選文章 81

101 Express 81

11 參考資源 83

111 Nodejs書籍 83112 Nodejs影音教學 84113 Nodejs教學網站 84114 Nodejs課程 84

iii

iv

關於本書

這是一本關於 Nodejs技術的開放源碼電子書我們使用 GitHub維護電子書內容並交由 ContPub(Continuous Publishing)系統自動線上發佈本書提供 PDFEPUBMOBI及 HTML等格式您除了可以在網站檢視本書所有內容也可以將電子書下載至閱讀器保存

本書的線上閱讀網址與 GitHub資料同步更新

httpbooknodejstw

如果您想要取得本書的其他格式可以從 ContPub的電子書專頁下載最新版本

httpcontpuborgreadnodejs-wiki-book

本書適合 Nodejs初學者至進階開發者也歡迎您在學習時一起參與本書內容撰寫

授權

Nodejs台灣社群協作電子書採用創用 CC姓名標示 -非商業性授權您不必為本書付費

Nodejs Wiki Book book is licensed under the Attribution-NonCommercial 30Unported license You should not have paid for this book

您可以複製散佈及修改本書內容但請勿將本書用於商業用途

您可以在以下網址取得授權條款全文

1

Nodejs中文電子書

httpcreativecommonsorglicensesby-nc30legalcode

作者

本書由 Nodejs Taiwan社群成員協作以下名單依照字母排序

bull Caesar Chi (clonn)

bull Fillano Feng ( llano)

bull Kyle Lin (lyhcode)

Nodejs Taiwan是一個自由開放的技術學習社群我們歡迎您加入一起學習研究及分享

下載電子書

線上閱讀本書

httpbooknodejstw

PDF格式適合一般電腦及 7吋以上平板電腦閱讀

httpcontpuborgdownloadnodejs-wiki-bookpdf

EPUB格式適合 iPadiPhone行動裝置閱讀

httpcontpuborgdownloadnodejs-wiki-bookepub

MOBI格式適合 Kindle電子書閱讀器

httpcontpuborgdownloadnodejs-wiki-bookmobi

原始碼

本書最新的原始碼(中文版)網址如下

httpgithubcomnodejs-twnodejs-wiki-book

2 關於本書

Nodejs中文電子書

01 精選文章收錄流程

精選文章的用意是鼓勵作者在自已的網誌發表 Nodejs 教學再由Nodejs Taiwan社群挑選系列文章列入電子書的精選文集

1 將文章標題及連結貼到「精選文章」分類

2 社群工作小組以 E-Mail通知作者文章列入精選並邀請將內文授權給Nodejs Taiwan電子書分享

3 作者同意後由工作小組負責整理圖文發佈至電子書

4 以 E-Mail寄出感謝函通知原作者文章已收錄依作者意願調整文章內容

5 於 Nodejs Taiwan首頁及粉絲專頁推薦作者的文章

01 精選文章收錄流程 3

Nodejs中文電子書

4 關於本書

前言

Nodejs是 JavaScript程式語言的開發框架由 04x至目前 v060版本核心上已經有許多變更當然目的只有一個就是讓開發變得更簡單速

度能夠更快這是所有人奮鬥的目標而 nodeJS 華文維基平台主力由nodeJStw一群喜好 javascript開發者共同主筆內文以中文為主希望能夠降低開發者學習門檻藉由我們拋磚引玉讓更多華人開發者共同學習

討論

CoffeeScript好好玩

5

Nodejs中文電子書

6 前言

1

Nodejs簡介

Nodejs是一個高效能易擴充的網站應用程式開發框架 (Web Applica-tion Framework)它誕生的原因是為了讓開發者能夠更容易開發高延展性的網路服務不需要經過太多複雜的調校效能調整及程式修改就能

滿足網路服務在不同發展階段對效能的要求

Ryan Dahl是NodeJS的催生者目前任職於 Joyent (主機託管服務公司)他開發 NodeJS的目的就是希望能解決 Apache在連線數量過高時緩衝區 (buffer)和系統資源會很快被耗盡的問題希望能建立一個新的開發框架以解決這個問題因此嘗試使用效能十分優秀的 V8 JavaScript Engine讓網站開發人員熟悉的 JavaScript程式語言也能應用於後端服務程式的開發並且具有出色的執行效能

JavaScript是功能強大的物件導向程式語言但是在 JavaScript的官方規格中主要是定義網頁 (以瀏覽器為基礎) 應用程式需要的應用程式介面(API)對 JavaScript程式的應用範圍有所侷限為使 JavaScript能夠在更多用途發展CommonJS規範一組標準函式庫 (standard library)使 JavaScript的應用範圍能夠和 RubyPython及 Java等語言同樣豐富撰寫 NodeJS的JavaScript程式碼符合 CommonJS規範可以使用 CommonJS API為基礎開發程式並且在不同的 CommonJS兼容 (compliant) JavaScript執行環境中程式碼具有可攜性

7

Nodejs中文電子書

瀏覽器的 JavaScript與實現 CommonJS規範的 NodeJS有何不同呢瀏覽器的 JavaScript 提供 XMLHttpRequest 讓程式可以和網頁伺服器建立資料傳輸連線但這通常只能適用於網站開發的需求因為我們只能用

XMLHttpRequest與網頁伺服器通訊卻無法利用它建立其他類型如 Telnet FTP NTP的伺服器通訊如果我們想開發網路服務程式例如 SMTP電子郵件伺服器就必須使用 Sockets建立 TCP (某些服務則用 UDP)監聽及連線其他程式語言如 PHPJavaPythonPerl及 Ruby等在標準開發環境中皆有提供 Sockets API而瀏覽器的 JavaScript基於安全及貼近網站設計需求的考量下並未將 Sockets列入標準函式庫之中CommonJS的規範填補這種基礎函式庫功能的空缺遵循 CommonJS規範的 NodeJS可以直接使用 Sockets API建立各種網路服務程式

JavaScript語言本身支援 Lambda的特性因此一個匿名函式 (anonymousfunction)可以被儲存成一個變數並當作參數傳遞給另一個函式

var proc1 = function(op x) return op(x)

var op1 = function(x) return x+1 var op2 = function(x) return xx

proc1(op1 3) result is 4 proc1(op2 5) result is 25

另一個 JavaScript開發者必須掌握的語言特性稱為 Closure

NodeJS符合 CommonJS的規範使得 Callback方式易於實現也能夠讓更多同好基於 JavaScript開發符合 NodeJS的外掛模組 (Module)

回想以前要寫一個能夠同時容納上百人的上線的網路服務需要花費

多大的苦工可能 10人多就需要經過一次程式調整而 NodeJS就是為了解決這個困境NodeJS因此誕生它是一種利用 V8 Javascript編譯器所開發的產品利用 V8編譯器的高效能與 Javascript的程式開發特性所產生的網路程式

開發人員所編寫出來的 Javascript腳本程式怎麼可能會比其他語言寫出來的網路程式還要快上許多呢以前的網路程式原理是將使用者每次的

連線 (connection)都開啟一個執行緒 (thread)當連線爆增的時候將會快速耗盡系統效能並且容易產生阻塞 (block)的發生

8 1 Nodejs簡介

Nodejs中文電子書

NodeJS對於資源的調校有所不同當程式接收到一筆連線 (connection)會通知作業系統透過 epoll kqueue devpoll或 select將連線保留並且放入heap中配置先讓連線進入休眠 (Sleep)狀態當系統通知時才會觸發連線的 callback這種處理連線方式只會佔用掉記憶體並不會使用到 CPU資源另外因為採用 Javascript語言的特性每個 request都會有一個 callback如此可以避免發生 Block的狀況發生

基於 Callback特性目前NodeJS大多應用於 Comet(long pulling) RequestServer或者是高連線數量的網路服務上目前也有許多公司將 NodeJS設為內部核心網路服務之一在 NodeJS也提供了外掛管理 (Node packagemanagement)讓愛好 NodeJS輕易開發更多有趣的服務外掛並且提供到 npm讓全世界使用者快速安裝使用

本書最後執行測試版本為 nodejs v068相關 API文件可查詢 lsquohttpnodejsorg lthttpnodejsorggtlsquo本書所有範例均可於 linux windows上執行如遇到任何問題歡迎至 lsquohttpnodejstw lthttpnodejstwgtlsquo詢問對於nodejs相關問題

9

Nodejs中文電子書

10 1 Nodejs簡介

2

JavaScript與 NodeJS

其實使用 JavaScript 在網頁端與伺服器端的差距並不大但是為了使NodeJS可以發揮他最強大的能力有一些知識還是必要的所以還是針對這些主題介紹一下其中 Event LoopScope以及 Callback其實是比較需要了解的基本知識cpscurrying ow control是更進階的技巧與應用

21 Event Loop

可能很多人在寫 Javascript 時並不知道他是怎麼被執行的這個時候可以參考一下 jQuery作者 John Resig一篇好文章介紹事件及 timer怎麼在瀏覽器中執行How JavaScript Timers Work通常在網頁中所有的Javascript執行完畢後(這部份全部都在 global scope跑除非執行函數)接下來就是如 John Resig解釋的這樣所有的事件處理函數以及 timer執行的函數會排在一個 queue結構中利用一個無窮迴圈不斷從 queue中取出函數來執行這個就是 event loop

(除了 John Resig的那篇文章Nicholas C Zakas的 ldquoProfessional Javascriptfor Web Developer 2nd editionrdquo 有一個試閱本httpyuiblogcomassetspdfzakas-projs-2ed-ch18pdf598頁剛好也有簡短的說明)

所以在 Javascript中雖然有非同步但是他並不是使用執行緒所有

11

Nodejs中文電子書

的事件或是非同步執行的函數都是在同一個執行緒中利用 event loop的方式在執行至於一些比較慢的動作例如 IO網頁 render re ow等實際動作會在其他執行緒跑等到有結果時才利用事件來觸發處理函數來處理

這樣的模型有幾個好處沒有執行緒的額外成本所以反應速度很快不會

有任何程式同時用到同一個變數不必考慮 lock也不會產生 dead lock所以程式撰寫很簡單但是也有一些潛在問題任一個函數執行時間較長都

會讓其他函數更慢執行(因為一個跑完才會跑另一個)在多核心硬體普遍

的現在無法用單一的應用程式 instance發揮所有的硬體能力用 NodeJS撰寫伺服器程式碰到的也是一樣的狀況要讓系統發揮 event loop的效能就要盡量利用事件的方式來組織程式架構另外對於一些有可能較為耗

時的操作可以考慮使用 processnextTick函數來讓他以非同步的方式執行避免在同一個函數中執行太久擋住所有函數的執行

如果想要測試 event loop怎樣在「瀏覽器」中運行可以在函數中呼叫alert()這樣會讓所有 Javascript的執行停下來尤其會干擾所有使用 timer的函數執行有一個簡單的例子這是一個會依照設定的時間間隔嚴格執

行動作的動畫如果時間過了就會跳過要執行的動作點按圖片以後人

物會快速旋轉但是在旋轉執行完畢前按下「delay」按鈕讓 alert訊息等久一點接下來的動畫就完全不會出現了

22 Scope與 Closure

要快速理解 JavaScript的 Scope(變數作用範圍)原理只要記住他是Lexical Scope就差不多了簡單地說變數作用範圍是依照程式定義時(或者叫做程式文本)的上下文決定而不是執行時的上下文決定

為了維護程式執行時所依賴的變數即使執行時程式運行在原本的

scope之外他的變數作用範圍仍然維持不變這時程式依賴的自由變數(定義時不是 local的而是在上一層 scope定義的變數)一樣可以使用就好像被關閉起來所以叫做 Closure用程式看比較好懂

function outter(arg1)

arg1 及 free_variable1 對 inner 函數來說都是自由變數

var free_variable1 = 3

return function inner(arg2)

12 2 JavaScript與 NodeJS

Nodejs中文電子書

var local_variable1 =2arg2 及 local_variable1 對 inner 函數來說都是本地變數

return arg1 + arg2 + free_variable + local_variable1

var a = outter(1)變數 a就是 outter函數執行後返回的 inner函數 var b =a(4)執行 inner函數執行時上下文已經在 outter函數之外但是仍然能正常執行而且可以使用定義在 outter函數裡面的 arg1及 free variable1變數 consolelog(b)結果 10

在 Javascript中scope最主要的單位是函數(另外有 global及 eval)所以有可能製造出 closure的狀況通常在形式上都是有巢狀的函數定義而且內側的函數使用到定義在外側函數裡面的變數

Closure有可能會造成記憶體洩漏主要是因為被參考的變數無法被垃圾收集機制處理造成佔用的資源無法釋放所以使用上必須考慮清楚

不要造成意外的記憶體洩漏(在上面的例子中如果 a一直未執行使用到的記憶體就不會被釋放)

跟透過函數的參數把變數傳給函數比較起來Javascript Engine會比較難對 Closure進行最佳化如果有效能上的考量這一點也需要注意

23 Callback

要介紹 Callback之前要先提到 JavaScript的特色

JavaScript是一種函數式語言(functional language)所有 Javascript語言內的函數都是高階函數 (higher order function這是數學名詞計算機用語好像是 rst class function意指函數使用沒有任何限制與其他物件一樣)也就是說函數可以作為函數的參數傳給函數也可以當作函數的返回值這個特性讓 Javascript的函數使用上非常有彈性而且功能強大

callback在形式上其實就是把函數傳給函數然後在適當的時機呼叫傳入的函數Javascript使用的事件系統通常就是使用這種形式NodeJS中有一個物件叫做 EventEmitter這是 NodeJS事件處理的核心物件所有會使用事件處理的函數都會「繼承」這個物件(這裡說的繼承實

作上應該像是 mixin)他的使用很簡單可以使用物件on(事件名稱callback

23 Callback 13

Nodejs中文電子書

函數) 或是物件addListener(事件名稱callback 函數) 把你想要處理事件的函數傳入在物件中可以使用物件emit(事件名稱 參數) 呼叫傳入的callback函數這是 Observer Pattern的簡單實作而且跟在網頁中使用 DOM的 addEventListener使用上很類似也很容易上手不過 NodeJS是大量使用非同步方式執行的應用所以程式邏輯幾乎都是寫在 callback函數中當邏輯比較複雜時大量的 callback會讓程式看起來很複雜也比較難單元測試舉例來說

var p client = new Db(lsquointegration tests 20rsquo new Server(ldquo127001rdquo 27017) lsquopkrsquoCustomPKFactory) p clientopen(function(err p client)

p clientdropDatabase(function(err done)

p clientcreateCollection(lsquotest custom keyrsquo function(err collection)

collectioninsert(lsquoarsquo1 function(err docs)

collection nd(lsquo idrsquonew ObjectID(ldquoaaaaaaaaaaaardquo) function(err cursor)

cursortoArray(function(err items) testassertEquals(1 itemslength) p clientclose()

)

)

)

)

)

)

這是在網路上看到的一段操作 mongodb的程式碼為了循序操作所以必須在一個 callback裡面呼叫下一個動作要使用的函數這個函數裡面還是會使用 callback最後就形成一個非常深的巢狀

這樣的程式碼會比較難進行單元測試有一個簡單的解決方式是

盡量不要使用匿名函數來當作 callback或是 event handler透過這樣的方式

14 2 JavaScript與 NodeJS

Nodejs中文電子書

就可以對各個 handler做單元測試了例如

var http = require(lsquohttprsquo) var tools =

cookieParser function(request response) if(requestheaders[rsquoCookiersquo]) do parsing

var server = httpcreateServer(function(request response)

thisemit(lsquoinitrsquo request response)

) serveron(lsquoinitrsquo toolscookieParser) serverlisten(8080 lsquo127001rsquo)

更進一步可以把 tools改成外部 module例如叫做 toolsjs

moduleexports = cookieParser function(request response) if(requestheaders[rsquoCookiersquo]) do parsing

然後把程式改成

var http = require(lsquohttprsquo)

var server = httpcreateServer(function(request response) thisemit(lsquoinitrsquo re-quest response)

) serveron(lsquoinitrsquo require(lsquo toolsrsquo)cookieParser) serverlisten(8080lsquo127001rsquo)

這樣就可以單元測試 cookieParser了例如使用 nodeunit時可以這樣寫

var testCase = require(lsquonodeunitrsquo)testCase moduleexports = testCase(

ldquosetUprdquo function(cb) thisrequest = headers Cookielsquoname1val1 name2val2rsquo thisresponse = thisresult= name1rsquoval1rsquoname2rsquoval2rsquo

cb()

ldquotearDownrdquo function(cb)

cb()

23 Callback 15

Nodejs中文電子書

ldquonormal caserdquo function(test)

testexpect(1) var obj = require(lsquotoolsrsquo)cookieParser(thisrequest thisresponse)testdeepEqual(obj thisresult) testdone()

)

善於利用模組可以讓程式更好維護與測試

24 CPS(Continuation-Passing Style)

cps 是 callback 使用上的特例形式上就是在函數最後呼叫 callback這樣就好像把函數執行後把結果交給 callback 繼續運行所以稱作continuation-passing style利用 cps可以在非同步執行的情況下透過傳給 callback的這個 cps callback來獲知 callback執行完畢或是取得執行結果例如

lthtmlgt ltbodygt ltdiv id=rdquopanelrdquo style=rdquovisibilityhiddenrdquogtlt divgtlt bodygt lt htmlgt ltscriptgt var request = new XMLHttpRequest() re-questopen(lsquoGETrsquo lsquotest749txt timestamp=rsquo+new Date()getTime() true) re-questaddEventListener(lsquoreadystatechangersquo function(next)

return function() if(thisreadyState===4ampampthisstatus===200) next(thisresponseText)lt== 傳入的 cps callback 在動作完成時執行並取得結果進一步處理

(function(str)lt==這個匿名函數就是 cps callback

documentgetElementById(lsquopanelrsquo)innerHTML=str docu-mentgetElementById(lsquopanelrsquo)stylevisibility = lsquovisiblersquo

) false) requestsend() ltscriptgt

進一步的應用也可以參考 2-6流程控制

16 2 JavaScript與 NodeJS

Nodejs中文電子書

25 函數返回函數與 Currying

前面的 cps範例裡面使用了函數返回函數這是為了把 cps callback傳遞給 onreadystatechange事件處理函數的方法(因為這個事件處理函數並沒有設計好會傳送接收這樣的參數)實際會執行的事件處理函數其實是內層返回的那個函數之外包覆的這個函數主要是為了利用 Closure把 next傳給內層的事件處理函數這個方法更常使用的地方是為了解決一些

scope問題例如

ltscriptgt var accu=0count=10 for(var i=0 iltcount i++)

setTimeout(

function() countndash accu+=i if(countlt=0)

consolelog(accu)

50)

ltscriptgt

最後得出的結果會是 100而不是想像中的 45這是因為等到 setTime-out指定的函數執行時變數 i已經變成 10而離開迴圈了要解決這個問題就需要透過 Closure來保存變數 i

ltscriptgt var accu=0count=10 for(var i=0 iltcount i++)

setTimeout(

function(i) return function() countndash

accu+=i if(countlt=0)

consolelog(accu)

(i)

50)

25 函數返回函數與 Currying 17

Nodejs中文電子書

淺藍色底色的部份是跟上面例子不一樣的地方 ltscriptgt

函數返回函數的另外一個用途是可以暫緩函數執行例如

function add(m n) return m+n

var a = add(20 10) consolelog(a)

add這個函數必須同時輸入兩個參數才有辦法執行如果我希望這個函數可以先給它一個參數等一些處理過後再給一個參數然後得到結

果就必須用函數返回函數的方式做修改

function add(m)

return function(n) return m+n

var wait another arg = add(20)先給一個參數 var a = function(arr)

var ret=0 for(var i=0iltarrlengthi++) ret+=arr[i] return ret

([1234])計算一下另一個參數 var b = wait another arg(a)然後再繼續執行 consolelog(b)

像這樣利用函數返回函數使得原本接受多個參數的函數可以一次

接受一個參數直到參數接收完成才執行得到結果的方式有一個學名就

叫做Currying

綜合以上許多奇技淫巧就可以透過用函數來處理函數的方式調整

程式流程接下來看看

26 流程控制

(以 sync方式使用 async函數避開巢狀 callback循序呼叫 async callback等奇技淫巧)

建議參考

bull httphowtonodeorgcontrol- ow

bull httphowtonodeorgcontrol- ow-part-ii

18 2 JavaScript與 NodeJS

Nodejs中文電子書

bull httphowtonodeorgcontrol- ow-part-iii

bull httpblogmixunet20110202essential-node-js-patterns-and-snippets

這幾篇都是非常經典的 NodeJSJavascript流程控制好文章(阿mixu是在介紹一些 pattern時提到這方面的主題)不過我還是用幾個簡單的程式介紹一下做法跟概念

並發與等待

下面的程式參考了 mixu文章中的做法

var wait = function(callbacks done) consolelog(lsquowait startrsquo) var counter = call-backslength var results = [] var next = function(result) 接收函數執行結果並判斷是否結束執行 resultspush(result) if(ndashcounter == 0) done(results)如果結束執行就把所有執行結果傳給指定的 callback處理 for(var i = 0 i lt callbackslength i++) 依次呼叫所有要執行的函數 callbacks[i](next) consolelog(lsquowait endrsquo)

wait( [ function(next) setTimeout(function() consolelog(lsquodone arsquo) var result =500 next(result) 500) function(next) setTimeout(function() con-solelog(lsquodone brsquo) var result = 1000 next(result) 1000) function(next)setTimeout(function() consolelog(lsquodone crsquo) var result = 1500 next(1500) 1500) ] function(results) var ret = 0 i=0 for( iltresultslength i++) ret+= results[i] consolelog(lsquodone all result lsquo+ret)

)

執行結果wait start wait end done a done b done c done all result 3000

可以看出來其實 wait並不是真的等到所有函數執行完才結束執行而是在所有傳給他的函數執行完畢後(不論同步非同步)才執行處理結

果的函數(也就是 done())

不過這樣的寫法還不夠實用因為沒辦法實際讓函數可以等待執行

完畢又能當作事件處理函數來實際使用上面參考到的 Tim Caswell的文

26 流程控制 19

Nodejs中文電子書

章裡面有一種解法不過還需要額外包裝(在他的例子中)NodeJS核心的 fs物件把一些函數(例如 readFile)用 Currying處理類似像這樣

var fs = require(lsquofsrsquo) var readFile = function(path)

return function(callback errback)

fsreadFile(path function(err data)

if(err) errback()

else callback(data)

)

其他部份可以參考 Tim Caswell的文章他的 Doparallel跟上面的 wait差不多意思這裡只提示一下他沒說到的地方

另外一種做法是去修飾一下 callback當他作為事件處理函數執行後再用 cps的方式取得結果

ltscriptgt function Wait(fns done)

var count = 0 var results = [] thisgetCallback = function(index)

count++ return (function(waitback)

return function() var i=0args=[]for(iltargumentslengthi++)

argspush(arguments[i])

argspush(waitback) fns[index]apply(thisargs)

)(function(result) resultspush(result) if(ndashcount == 0)

20 2 JavaScript與 NodeJS

Nodejs中文電子書

done(results)

)

var a = new Wait(

[ function(waitback) consolelog(lsquodone arsquo) var result = 500 wait-back(result) function(waitback) consolelog(lsquodone brsquo) var result =1000 waitback(result) function(waitback) consolelog(lsquodone crsquo) varresult = 1500 waitback(result) ] function(results) var ret = 0 i=0for( iltresultslength i++) ret += results[i] consolelog(lsquodone allresult lsquo+ret)

) var callbacks = [agetCallback(0)agetCallback(1)agetCallback(0)agetCallback(2)]一次取出要使用的 callbacks避免結果提早送出 setTimeout(callbacks[0]500) setTimeout(callbacks[1] 1000) setTimeout(callbacks[2] 1500) setTime-out(callbacks[3] 2000) 當所有取出的 callbacks執行完畢就呼叫 done()來處理結果 ltscriptgt

執行結果

done a done b done a done c done all result 3500

上面只是一些小實驗更成熟的作品是 Tim Caswell 的 stephttpsgithubcomcreationixstep

如果希望真正使用同步的方式寫非同步則需要使用 Promisejs這一類的 library來轉換非同步函數不過他結構比較複雜 XD(見仁見智不過有些人認為 Promise有點過頭了)httpblogsmsdncombrbucktonarchive20110815promise-js-2-0-promise-framework-for-javascriptaspx

如果想不透過其他 Library做轉換又能直接用同步方式執行非同步函數大概就要使用一些需要額外 compile原始程式碼的方法了例如 BrunoJouhier的 streamlinejshttpsgithubcomSagestreamlinejs

26 流程控制 21

Nodejs中文電子書

循序執行

循序執行可以協助把非常深的巢狀 callback結構攤平例如用這樣的簡單模組來做(serialjs)

moduleexports = function(funs) var c = 0 if(isArrayOfFunctions(funs))

throw(lsquoArgument type was not matched Should be array of func-tionsrsquo)

return function()

var args = Arrayprototypeslicecall(arguments 0) if((cgt=funslength))

c++ return funs[c-1]apply(this args)

function isArrayOfFunctions(f ) if(typeof f == lsquoobjectrsquo) return false if(flength)return false if(fconcat) return false if(fsplice) return false var i = 0 for(iltflength i++)

if(typeof f[i] == lsquofunctionrsquo) return false

return true

簡單的測試範例(testSerialjs)使用 fs 模組確定某個 path 是檔案然後讀取印出檔案內容這樣會用到兩層的 callback所以測試中有使用serial的版本與 nested callbacks的版本做對照

var serial = require(lsquoserialrsquo) fs = require(lsquofsrsquo) path = lsquodclientjsrsquo cb = serial([ func-tion(err data)

if(err)

if(dataisFile) fsreadFile(path cb)

22 2 JavaScript與 NodeJS

Nodejs中文電子書

else consolelog(err)

function(err data)

if(err) consolelog(lsquo[ attened by searial]rsquo) con-solelog(datatoString(lsquoutf8rsquo))

else consolelog(err)

]) fsstat(path cb)

fsstat(path function(err data) 第一層 callback if(err)

if(dataisFile)

fsreadFile(path function(err data) 第二層 callback if(err)

consolelog(lsquo[nested callbacks]rsquo) con-solelog(datatoString(lsquoutf8rsquo))

else consolelog(err)

)

else consolelog(err)

)

關鍵在於這些 callback的執行是有順序性的所以利用 serial返回的一個函數 cb來取代這些 callback然後在 cb中控制每次會循序呼叫的函數

26 流程控制 23

Nodejs中文電子書

就可以把巢狀的 callback攤平成循序的 function陣列(就是傳給 serial函數的參數)

測試中的dclientjs 是一個簡單的 dnode 測試程式放在跟 testSerialjs同一個目錄

var dnode = require(lsquodnodersquo)

dnodeconnect(8000 lsquolocalhostrsquo function(remote)

remoterestart(function(str) consolelog(str) processexit()

)

)

執行測試程式後出現結果

[ attened by searial] var dnode = require(lsquodnodersquo)

dnodeconnect(8000 lsquolocalhostrsquo function(remote)

remoterestart(function(str) consolelog(str) processexit()

)

)

[nested callbacks] var dnode = require(lsquodnodersquo)

dnodeconnect(8000 lsquolocalhostrsquo function(remote)

remoterestart(function(str) consolelog(str) processexit()

)

)

對照起來看兩種寫法的結果其實是一樣的但是利用 serialjs巢狀的 callback結構就會消失

不過這樣也只限於順序單純的狀況如果函數執行的順序比較複雜

(不只是一直線)還是需要用功能更完整的流程控制模組比較好例如

httpsgithubcomcaolanasync

24 2 JavaScript與 NodeJS

3

Nodejs安裝與設定

本篇將講解如何在各個不同 OS建立 NodeJS環境目前 NodeJS 048版本環境架設方式需依賴 Linux指令才可編譯完成當然在不同作業系統中也已經有 NodeJS package可以直接使用指令快速架設以下各不同作業系統解說如何安裝 NodeJS

31 Ubuntu Linux

更新推薦使用 nvm

git clone gitgithubcomcreationixnvmgit ~nvm

echo rdquo ~nvmnvmshrdquo gtgt ~bashrc

nvm install v0614

nvm alias default v0614

以 上 可 參 考 http dreamerslabcom blog tw how-to-setup-a-node-js-development-environment-on-ubuntu-11-04

使用 APT套件管理工具是常見的方法以下是使用社群提供的 PPA安裝方式

sudo apt-get install python-software-properties

sudo add-apt-repository ppachris-leanodejs-devel

25

Nodejs中文電子書

sudo apt-get update

sudo apt-get install nodejs

32 Other Linux

Linux很適合作為 NodeJS的伺服器作業系統及開發環境安裝前請先確認以下套件已正確安裝

bull curl (wget)用來下載檔案的工具

bull git先進的版本控制工具

bull g++ GNU C++軟體編譯工具

bull make GNU軟體專案建置工具

安裝指令如下如設有權限問題請在指令前面加上 sudo

git clone httpsgithubcomjoyentnodegit

cd node

git checkout v067

configure

make

sudo make install

接著測試 nodeJS是否正常執行

node --version

出現版本訊息即表示安裝成功

33 Windows

nodeJS 在 v060 版本之後開始正式支援 windows native直接使用nodeexe 就可以執行程式支援性完全與 linux 相同更棒的部份就是不需經過編譯經過下載之後簡單設定完成立即開發 node程式

26 3 Nodejs安裝與設定

Nodejs中文電子書

下載 nodejs安裝檔案 lthttpnodejsorgdownloadgt

如此完成 windows native nodeexe安裝接著可以進入 command line執行測試在 command line輸入rdquonoderdquo會出現 nodejs版本訊息畫面表示安裝完成

33 Windows 27

Nodejs中文電子書

28 3 Nodejs安裝與設定

4

Nodejs基礎

前篇文章已經由介紹安裝至設定都有完整介紹nodeJS 內部除了javascript常用的函式 (function)物件 (object)之外也有許多不同的自訂物件nodeJS預設建立這些物件為核心物件是為了要讓開發流程更為這些資料在官方文件已經具有許多具體說明接下來將會介紹在開發 nodeJS程式時常見的物件特性與使用方法

41 nodejs http伺服器建立

在 lsquonodejs官方網站 lthttpnodejsorggtlsquo裡面有舉一個最簡單的 HTTP伺服器建立一開始初步就是建立一個伺服器平台讓 nodejs可以與瀏覽器互相行為每種語言一開始的程式建立都是以 Hello world開始最初也從 Hello world帶各位進入 nodejs的世界

輸入以下程式碼儲存檔案為 node basic http hello worldjs

var server

ip = rdquo127001rdquo

port = 1337

http = require(rsquohttprsquo)

server = httpcreateServer(function (req res)

29

Nodejs中文電子書

reswriteHead(200 rsquoContent-Typersquo rsquotextplainrsquo)

resend(rsquoHello Worldnrsquo)

)

serverlisten(port ip)

consolelog(rdquoServer running at httprdquo + ip + rdquordquo + port)

程式碼解講一開始需要有幾個基本的變數

bull ip 機器本身的 ip位置因為使用本地端因此設定為 127001

bull port 需要開通的阜號通常設定為 http port 80因範例不希望與基本 port相衝隨意設定為 1337

在 nodejs 的程式中有許多預設的模組可以使用因此需要使用require方法將模組引入在這邊我們需要使用 http這個模組因此將 http載入Http模組裡面內建有許多方法可以使用這邊採用 createServer創建一個基本的 http伺服器再將 http伺服器給予一個 server變數裡面的回呼函式 (call back function)可以載入 http伺服器的資料與回應方法 (requestresponse)在程式裡面就可以看到我們直接回應給瀏覽器端所需的 Header回應內容

reswriteHead(200 rsquoContent-Typersquo rsquotextplainrsquo)

resend(rsquoHello Worldnrsquo)

Http伺服器需要設定 port ip在最後需要設定 Http監聽需要使用到listen事件監聽所有 Http伺服器行為

httplisten(port ip)

所有事情都完成之後需要確認伺服器正確執行因此使用 console在javascript裡就有這個原生物件console所印出的資料都會顯示於 nodejs伺服器頁面這邊印出的資料並不會傳送到使用者頁面上之後許多除壞

(debug)都會用到 console物件

consolelog(rdquoServer running at httprdquo + ip + rdquordquo + port)

30 4 Nodejs基礎

Nodejs中文電子書

42 nodejs http路徑建立

前面已經介紹如何建立一個簡單的 http伺服器接下來本章節將會介紹如何處理伺服器路徑 (rout)問題在 http的協定下所有從瀏覽器發出的要求 (request)都需要經過處理路徑上的建立也是如此

路徑就是指伺服器 ip位置或者是網域名稱之後對於伺服器給予的要求修改剛才的 hello world檔案修改如下

server = httpcreateServer(function (req res)

consolelog(requrl)

reswriteHead(200 rsquoContent-Typersquo rsquotextplainrsquo)

resend(rsquohello worldnrsquo)

)

重新啟動 nodejs程式後在瀏覽器端測試一下路徑行為結果如下圖

當在瀏覽器輸入 http1270011337test 在伺服器端會收到兩個要求一個是我們輸入的test要求另外一個則是faviconicotest的路徑要求http伺服器本身需要經過程式設定才有辦法回應給瀏覽器端所需要的回應在伺服器中所有的路徑要求都是需要被解析才有辦法取得資料從

42 nodejs http路徑建立 31

Nodejs中文電子書

上面解說可以了解到在 nodejs當中所有的路徑都需要經過設定未經過設定的路由會讓瀏覽器無法取得任何資料導致錯誤頁面的發生底下將會解

說如何設定路由同時避免發生錯誤情形先前 nodejs程式需要增加一些修改才能讓使用者透過瀏覽器在不同路徑時有不同的結果根據剛才

的程式做如下的修改

var server

ip = rdquo127001rdquo

port = 1337

http = require(rsquohttprsquo)

url = require(rsquourlrsquo)

path

server = httpcreateServer(function (req res)

path = urlparse(requrl)

reswriteHead(200 rsquoContent-Typersquo rsquotextplainrsquo)

switch (pathpathname)

case rdquoindexrdquo

resend(rsquoI am indexnrsquo)

break

case rdquotestrdquo

resend(rsquothis is test pagenrsquo)

break

default

resend(rsquodefault pagenrsquo)

break

)

serverlisten(port ip)

consolelog(rdquoServer running at httprdquo + ip + rdquordquo + port)

程式做了片段的修改首先載入 url模組另外增加一個 path變數url模組就跟如同他的命名一般專門處理 url字串處理裡面提供了許多方法來解決路徑上的問題因為從瀏覽器發出的要求路徑可能會帶有多種需求

或者 GET參數組合等因此我們需要將路徑單純化取用路徑部分的資料即可例如使用者可能會送出 http1270011337testsend=1如果直接

32 4 Nodejs基礎

Nodejs中文電子書

信任 requrl就會收到結果為testsend=1所以需要透過 url模組的方法將路徑資料過濾

在這邊使用 urlparse的方法裡面帶入網址格式資料會回傳路徑資料為了後需方便使用將回傳的資料設定到 path變數當中在回傳的路徑資料裡面包含資訊如下圖

這邊只需要使用單純的路徑要求直接取用 pathpathname就可以達到我們的目的

最後要做路徑的判別在不同的路徑可以指定不同的輸出在範例中

有三個可能結果第一個從瀏覽器輸入index就會顯示 index結果test 就會呈現出 test頁面最後如果都不符合預期的輸入會直接顯示 default的頁面最後的預防可以讓瀏覽器不會出現非預期結果讓程式的可靠性提昇

底下為測試結果

42 nodejs http路徑建立 33

Nodejs中文電子書

43 nodejs檔案讀取

前面已經介紹如何使用路由(rount)做出不同的回應實際應用只有在瀏覽器只有輸出幾個文字資料總是不夠的在本章節中將介紹如何使

用檔案讀取輸出檔案資料讓使用者在前端瀏覽器也可以讀取到完整的

html css javascript檔案輸出

檔案管理最重要的部分就是 lsquoFile system lthttpnodejsorgdocslatestapifshtmlgtlsquo這個模組此模組可以針對檔案做管理監控讀取等行為裡面有許多預設的方法底下是檔案輸出的基本範例底下會有兩個檔案

第一個是靜態 html檔案

ltDOCTYPE htmlgt

lthtml xmlns=rdquohttpwwww3org1999xhtmlrdquo xmllang=rdquozh-CNrdquo lang=rdquozh-CNrdquogt

ltheadgt

ltmeta http-equiv=rdquoContent-Typerdquo content=rdquotexthtml charset=UTF-7rdquo gt

lttitlegtnodejs index html filelttitlegt

ltheadgt

ltbodygt

34 4 Nodejs基礎

Nodejs中文電子書

lth1gtnodejs index html filelth1gt

ltbodygt

lthtmlgt

另一個為 nodejs程式

var fs = require(rdquofsrdquo)

filename = rdquostaticindexhtmlrdquo

encode = rdquoutf8rdquo

fsreadFile(filename encode function(err file)

consolelog(file)

)

一開始直接載入 le system模組 載入名稱為 fs讀取檔案主要使

用的方法為 readFile裡面以三個參數路徑 ( le path) 編碼方式 (encoding)

回應函式 (callback)路徑必須要設定為靜態 html所在位置才能指定到正確的檔案靜態檔案的編碼方式也必須正確這邊使用靜態檔案的編

碼為 utf8如果編碼設定錯誤nodejs讀取出來檔案結果會使用 byte raw格式輸出如果錯誤編碼格式會導致輸出資料為 byte raw

回應函式中裡面會使用兩個變數error為錯誤資訊如果讀取的檔案不存在或者發生錯誤error數值會是 true如果成功讀取資料 error將會是 falsecontent則是檔案內容資料讀取後將會把資料全數丟到 content這個變數當中

最後程式的輸出結果畫面如下

43 nodejs檔案讀取 35

Nodejs中文電子書

44 nodejs http靜態檔案輸出

前面已經了解如何讀取本地端檔案接下來將配合 http伺服器路由讓每個路由都能夠輸出相對應的靜態 html檔案首先新增加幾個靜態 html檔案

ltDOCTYPE htmlgt

lthtml xmlns=rdquohttpwwww3org1999xhtmlrdquo xmllang=rdquozh-CNrdquo lang=rdquozh-CNrdquogt

ltheadgt

ltmeta http-equiv=rdquoContent-Typerdquo content=rdquotexthtml charset=UTF-7rdquo gt

lttitlegtnodejs index html filelttitlegt

ltheadgt

ltbodygt

lth1gtnodejs index html filelth1gt

ltbodygt

lthtmlgt

ltDOCTYPE htmlgt

lthtml xmlns=rdquohttpwwww3org1999xhtmlrdquo xmllang=rdquozh-CNrdquo lang=rdquozh-CNrdquogt

ltheadgt

ltmeta http-equiv=rdquoContent-Typerdquo content=rdquotexthtml charset=UTF-7rdquo gt

lttitlegtnodejs test html filelttitlegt

ltheadgt

ltbodygt

lth1gtnodejs test html filelth1gt

ltbodygt

lthtmlgt

ltDOCTYPE htmlgt

lthtml xmlns=rdquohttpwwww3org1999xhtmlrdquo xmllang=rdquozh-CNrdquo lang=rdquozh-CNrdquogt

ltheadgt

ltmeta http-equiv=rdquoContent-Typerdquo content=rdquotexthtml charset=UTF-7rdquo gt

lttitlegtnodejs static html filelttitlegt

ltheadgt

ltbodygt

lth1gtnodejs static html filelth1gt

ltbodygt

lthtmlgt

36 4 Nodejs基礎

Nodejs中文電子書

準備一個包含基本路由功能的 http伺服器

var server

ip = rdquo127001rdquo

port = 1337

http = require(rsquohttprsquo)

url = require(rsquourlrsquo)

server = httpcreateServer(function (req res)

var path = urlparse(requrl)

)

serverlisten(port ip)

consolelog(rdquoServer running at httprdquo + ip + rdquordquo + port)

加入 le system 模組使用 readFile 的功能將這一段程式放置於

createServer的回應函式中

fsreadFile(filePath encode function(err file)

)

readFile的回應函式裡面加入頁面輸出讓瀏覽器可以正確讀到檔案在這邊我們設定讀取的檔案為 html 靜態檔案所以 Content-type 設定為texthtml讀取到檔案的內容將會正確輸出成 html靜態檔案

fsreadFile(filePath encode function(err file)

reswriteHead(200 rsquoContent-Typersquo rsquotexthtmlrsquo)

reswrite(file)

resend()

)

到這邊為止基本的程式內容都已經完成剩下一些細節的調整首先

路徑上必須做調整目前的靜態檔案全部都放置於 static資料夾底下設定一個變數來記住資料夾位置

接著將瀏覽器發出要求路徑與資料夾組合讀取正確 html靜態檔案使用者有可能會輸入錯誤路徑所以在讀取檔案的時候要加入錯誤處理

同時回應 404伺服器無法正確回應的 http header格式

加入這些細節的修改一個基本的 http 靜態 html輸出伺服器就完成了完整程式碼如下

44 nodejs http靜態檔案輸出 37

Nodejs中文電子書

var server

ip = rdquo127001rdquo

port = 1337

http = require(rsquohttprsquo)

fs = require(rdquofsrdquo)

folderPath = rdquostaticrdquo

url = require(rsquourlrsquo)

path

filePath

encode = rdquoutf8rdquo

server = httpcreateServer(function (req res)

path = urlparse(requrl)

filePath = folderPath + pathpathname

fsreadFile(filePath encode function(err file)

if (err)

reswriteHead(404 rsquoContent-Typersquo rsquotextplainrsquo)

resend()

return

reswriteHead(200 rsquoContent-Typersquo rsquotextapplicationrsquo)

reswrite(file)

resend()

)

)

serverlisten(port ip)

consolelog(rdquoServer running at httprdquo + ip + rdquordquo + port)

45 nodejs http GET資料擷取

http伺服器中除了路由之外另一個最常使用的方法就是擷取 GET資料本單元將會介紹如何透過基本 http伺服器擷取瀏覽器傳來的要求擷取 GET資料

在 http協定中GET參數都是藉由 URL從瀏覽器發出要求送至伺服器

38 4 Nodejs基礎

Nodejs中文電子書

端基本的傳送網址格式可能如下

http127001testsend=1amptest=2

上面這段網址裡面的 GET參數就是 send而這個變數的數值就為 1如果想要在 http伺服器取得 GET資料需要在瀏覽器給予的要求 (request)做處理

首先需要載入 query string這個模組這個模組主要是用來將字串資料

過濾後轉換成javascript物件

qs = require(rsquoquerystringrsquo)

接著在第一階段利用 url模組過濾瀏覽器發出的 URL資料後將回應的物件裡面的 query這個變數是一個字串值資料過濾後如下

send=1amptest=2

透過 query string使用 parse這個方法將資料轉換成 javascript物件就表示 GET的資料已經被伺服器端正式擷取下來

path = urlparse(requrl)

parameter = qsparse(pathquery)

整個 nodejs http GET參數完整擷取程式碼如下

var server

ip = rdquo127001rdquo

port = 1337

http = require(rsquohttprsquo)

qs = require(rsquoquerystringrsquo)

url = require(rsquourlrsquo)

server = httpcreateServer(function (req res)

var path = urlparse(requrl)

parameter = qsparse(pathquery)

consoledir(parameter)

reswriteHead(200 rsquoContent-Typersquo rsquotextplainrsquo)

reswrite(rsquoBrowser test GET parameternrsquo)

resend()

)

45 nodejs http GET資料擷取 39

Nodejs中文電子書

serverlisten(port ip)

consolelog(rdquoServer running at httprdquo + ip + rdquordquo + port)

程式運作之後由瀏覽器輸入要求網址之後nodejs伺服器端回應資料為

Server running at http1270011337

send rsquo1rsquo test rsquo1rsquo

46 本章結語

前面所解說的部份一大部分主要是處理 http伺服器基本問題雖然在某些部分有牽扯到 http 伺服器基本運作原理主要還是希望可以藉由這些基本範例練習 nodejs練習回應函式與語法串接的特點習慣編寫javascript風格程式當然直接這樣開發 nodejs是非常辛苦的接下來在模組實戰開發的部份將會介紹特定的模組一步一步帶領各位從無到有進行

nodejs應用程式開發

40 4 Nodejs基礎

5

NPM套件管理工具

npm全名為 Node Package Manager是 Nodejs的套件(package)管理工具類似 Perl的 ppm或 PHP的 PEAR等安裝 npm後使用npm install

module name指令即可安裝新套件維護管理套件的工作會更加輕鬆

npm可以讓Nodejs的開發者直接利用擴充線上的套件庫(packagesregistry)加速軟體專案的開發npm提供很友善的搜尋功能可以快速找到安裝需要的套件當這些套件發行新版本時npm也可以協助開發者自動更新這些套件

npm不僅可用於安裝新的套件它也支援搜尋列出已安裝模組及更新的功能

51 安裝 NPM

Nodejs在 063版本開始內建 npm讀者安裝的版本若是此版本或更新的版本就可以略過以下安裝說明

若要檢查 npm是否正確安裝可以使用以下的指令

npm -v

41

Nodejs中文電子書

執行結果說明

若 npm正確安裝執行 npm -v將會看到類似 110-2的版本訊息

若讀者安裝的 Nodejs版本比較舊或是有興趣嘗試自己動手安裝 npm工具則可以參考以下的說明

安裝於Windows系統

Nodejs for Windows 於 062 版開始內建 npm使用 nodejsorg 官方提供的安裝程式不需要進一步的設定就可以立即使用 npm指令對於Windows的開發者來說大幅降低環境設定的問題與門檻

除了使用 Nodejs內建的 npm讀者也可以從 npm官方提供的以下網址

httpnpmjsorgdist

這是由 npm提供的 Fancy Windows Install版本請下載壓縮檔(例如npm-110-3zip)並將壓縮檔內容解壓縮至 Nodejs的安裝路徑(例如CProgram Filesnodejs)

解壓縮後在 Nodejs的安裝路徑下應該有以下的檔案及資料夾

bull npmcmd(檔案)

bull node modules(資料夾)

安裝於 Linux系統

Ubuntu Linux的使用者可以加入 NPM Unoffcial PPA這個 repository即可使用 apt-get完成 npm安裝

42 5 NPM套件管理工具

Nodejs中文電子書

Ubuntu Linux使用 apt-get安裝 npm

sudo apt-get install python-software-properties

sudo add-apt-repository ppagias-kay-leenpm

sudo apt-get update

sudo apt-get install npm

npm官方提供的安裝程式 installsh可以適用於大多數的 Linux系統使用這個安裝程式請先確認

1 系統已安裝 curl工具(請使用 curl --version查看版本訊息)

2 已安裝 Nodejs並且 PATH正確設置

3 Nodejs的版本必須大於 04x

以下為 npm提供的安裝指令

curl httpnpmjsorginstallsh | sh

安裝成功會看到如下訊息

installsh安裝成功的訊息

npm10105 homeUSERNAMElocalnodelibnode_modulesnpm

It worked

安裝於Mac OS X

建議採用與 Nodejs相同的方式進行 npm的安裝例如使用MacPorts安裝 Nodejs就同樣使用MacPorts安裝 npm這樣對日後的維護才會更方便容易

使用 MacPorts安裝 npm是本書比較建議的方式它可以讓 npm的安裝移除及更新工作自動化將會幫助開發者節省寶貴時間

51 安裝 NPM 43

Nodejs中文電子書

安裝MacPorts的提示

在MacPorts網站可以取得 OS X系統版本對應的安裝程式(例如 106或 107)httpwwwmacportsorg安裝過程會詢問系統管理者密碼使用預設的選項完成安裝即可安

裝MacPorts之後在終端機執行 port -v將會看到MacPorts的版本訊息

安裝 npm之前先更新MacPorts的套件清單以確保安裝的 npm是最新版本

sudo port -d selfupdate

接著安裝 npm

sudo port install npm

若讀者的 Nodejs並非使用MacPorts安裝則不建議使用MacPorts安裝npm因為 MacPorts會自動檢查並安裝相依套件而 npm相依 nodejs所以MacPorts也會一併將 nodejs套件安裝造成先前讀者使用其它方式安裝的 nodejs被覆蓋

讀者可以先使用 MacPorts安裝 curl(sudo port install curl)

再參考 Linux的 installsh安裝方式即可使用 npm官方提供的安裝程式

NPM安裝後測試

npm是指令列工具(command-line tool)使用時請先打開系統的文字終端機工具

測試 npm安裝與設定是否正確請輸入指令如下

npm -v

或是

npm --version

如果 npm已經正確安裝設定就會顯示版本訊息

44 5 NPM套件管理工具

Nodejs中文電子書

執行結果(範例)

110-2

52 使用 NPM安裝套件

npm目前擁有超過 6000種套件(packages)可以在 npm registry使用關鍵字搜尋套件

httpsearchnpmjsorg

舉例來說在關鍵字欄位輸入「coffee-script」下方的清單就會自動列出包含 coffee-script關鍵字的套件

接著我們回到終端機模式的操作npm的指令工具本身就可以完成套

件搜尋的任務

例如以下的指令同樣可以找出 coffee-script相關套件

npm search coffee-script

以下是搜尋結果的參考畫面

52 使用 NPM安裝套件 45

Nodejs中文電子書

找到需要的套件後(例如 express)即可使用以下指令安裝

npm install coffee-script

值得注意的一點是使用 npm install會將指定的套件安裝在工

作目錄(Working Directory)的 node modules資料夾下

以Windows為例如果執行 npm install的目錄位於

Cproject1

那麼 npm將會自動建立一個 node modules的子目錄(如果不存在)

Cproject1node modules

並且將下載的套件放置於這個子目錄例如

Cproject1node modulescoffee-script

這個設計讓專案可以個別管理相依的套件並且可以在專案佈署或發

行時將這些套件(位於 node modules)一併打包方便其它專案的使用者不必再重新下載套件

這個 npm install的預設安裝模式為 local(本地)只會變更當前專案的資料夾不會影響系統

另一種安裝模式稱為 global(全域)這種模式會將套件安裝到系統資

料夾也就是 npm安裝路徑的 node modules資料夾例如

CProgram Filesnodejsnode modules

46 5 NPM套件管理工具

Nodejs中文電子書

是否要使用全域安裝可以依照套件是否提供新指令來判斷舉例來說express 套件提供 express 這個指令而 coffee-script 則提供 coffee

指令

在 local 安 裝 模 式 中這 些 指 令 的 程 式 檔 案會 被 安 裝 到node modules的 bin這個隱藏資料夾下除非將bin的路徑加入 PATH環境變數否則要執行這些指令將會相當不便

為了方便指令的執行我們可以在 npm install 加上 -g 或 --

global參數啟用 global安裝模式例如

npm install -g coffee-script

npm install -g express

使用 global安裝模式需要注意執行權限的問題若權限不足可能會出現類似以下的錯誤訊息

npm ERR Error EACCES permission denied rsquorsquo

npm ERR

npm ERR Please try running this command again as rootAdministrator

要獲得足夠得執行權限請參考以下說明

bull Windows 7 或 2008 以上在「命令提示字元」的捷徑按右鍵選擇「以系統管理員身分執行」執行 npm指令時就會具有 Administrator身分

bull Mac OS X或 Linux系統可以使用 sudo指令例如

sudo npm install -g express

bull Linux系統可以使用 root權限登入或是以「sudo su -」切換成 root身分(使用 root權限操作系統相當危險因此並不建議使用這種方式)

若加上 -g參數使用 npm install -g coffee-script完成安裝

後就可以在終端機執行 coffee指令例如

coffee -v

52 使用 NPM安裝套件 47

Nodejs中文電子書

執行結果(範例)

CoffeeScript version 120

53 套件的更新及維護

除了前一節說明的 search 及 install 用法npm 還提供其他許多指令(commands)

使用 npm help可以查詢可用的指令

npm help

執行結果(部分)

where ltcommandgt is one of

adduser apihelp author bin bugs c cache completion

config deprecate docs edit explore faq find get

help help-search home i info init install la link

list ll ln login ls outdated owner pack prefix

prune publish r rb rebuild remove restart rm root

run-script s se search set show star start stop

submodule tag test un uninstall unlink unpublish

unstar up update version view whoami

使用 npm help command可以查詢指令的詳細用法例如

npm help list

接下來本節要介紹開發過程常用的 npm指令

使用 list可以列出已安裝套件

npm list

48 5 NPM套件管理工具

Nodejs中文電子書

執行結果(範例)

-- coffee-script120

- express256

- connect185

| -- formidable108

-- mime124

-- mkdirp007

-- qs041

檢視某個套件的詳細資訊例如

npm show express

升級所有套件(如果該套件已發佈更新版本)

npm update

升級指定的套件

npm update express

移除指定的套件

npm uninstall express

54 使用 packagejson

對於正式的 Nodejs專案可以建立一個命名為 packagejson的設

定檔(純文字格式)檔案內容參考範例如下

54 使用 packagejson 49

Nodejs中文電子書

packagejson(範例)

rdquonamerdquo rdquoapplication-namerdquo

rdquoversionrdquo rdquo001rdquo

rdquoprivaterdquo true

rdquodependenciesrdquo

rdquoexpressrdquo rdquo255rdquo

rdquocoffee-scriptrdquo rdquolatestrdquo

rdquomongooserdquo rdquogt= 253rdquo

其中 name與 version依照專案的需求設置

需要注意的是 dependencies的設定它用於指定專案相依的套件名

稱及版本

bull rdquoexpressrdquo rdquo255rdquo

代表此專案相依版本 255的 express套件

bull rdquocoffee-scriptrdquo rdquolatestrdquo

使用最新版的 coffee-script套件(每次更新都會檢查新版)

bull rdquomongooserdquo rdquogt= 253rdquo

使用版本大於 253的 mongoose套件

假設某個套件的新版可能造成專案無法正常運作就必須指定套件的

版本避免專案的程式碼來不及更新以相容新版套件通常在開發初期的

專案需要盡可能維持新套件的相容性(以取得套件的更新或修正)可以

用「gt=」設定最低相容的版本或是使用「latest」設定永遠保持最新

套件

50 5 NPM套件管理工具

6

Express介紹

在前面的 nodejs基礎當中介紹許多許多開設 http的使用方法及介紹以及許多基本的 nodejs基本應用

接下來要介紹一個套件稱為 express [Express](httpexpressjscom)這個套件主要幫忙解決許多 nodejs http server所需要的基本服務讓開發 httpservice變得更為容易不需要像之前需要透過層層模組(module)才有辦法開始編寫自己的程式

這個套件是由 TJ Holowaychuk 製作而成的套件裡面包含基本的路由處理 (route)http資料處理(GETPOSTPUT)另外還與樣板套件(jshtml template engine)搭配同時也可以處理許多複雜化的問題

61 Express安裝

安裝方式十分簡單只要透過之前介紹的 NPM就可以使用簡單的指令安裝指令如下

這邊建議需要將此套件安裝成為全域模組方便日後使用

51

Nodejs中文電子書

62 Express基本操作

express的使用也十分簡單先來建立一個基本的 hello world

var app = require(rsquoexpressrsquo)createServer()

port = 1337

applisten(port)

appget(rsquorsquo function(req res)

ressend(rsquohello worldrsquo)

)

consolelog(rsquostart express servernrsquo)

可以從上面的程式碼發現基本操作與 nodejs http的建立方式沒有太大差異主要差在當我們設定路由時可以直接透過 appget方式設定回應與接受方式

63 Express路由處理

Express對於 http服務上有許多包裝讓開發者使用及設定上更為方便例如有幾個路由設定那我們就統一藉由 appget來處理

Create http server

appget(rsquorsquo function(req res)

ressend(rsquohello worldrsquo)

)

appget(rsquotestrsquo function(req res)

ressend(rsquotest renderrsquo)

)

appget(rsquouserrsquo function(req res)

ressend(rsquouser pagersquo)

)

52 6 Express介紹

Nodejs中文電子書

如上面的程式碼所表示appget可以帶入兩個參數第一個是路徑名稱設定第二個為回應函式 (call back function)回應函式裡面就如同之前的 createServer方法裡面包含 requestresponse兩個物件可供使用使用者就可以透過瀏覽器輸入不同的 url切換到不同的頁面顯示不同的結果

路由設定上也有基本的配對方式讓使用者從瀏覽器輸入的網址可以

是一個變數只要符合型態就可以有對應的頁面產出例如

Create http server

appget(rsquouseridrsquo function(req res)

ressend(rsquouser rsquo + reqparamsid)

)

appget(rsquonumberrsquo function(req res)

ressend(rsquonumber rsquo + reqparamsnumber)

)

裡 面 使 用 到number 從 網 址 輸 入 之 後 就 可 以 直 接 使 用reqparamsnumber 取得所輸入的資料變成 url 參數使用當然前面也是可以加上路徑的設定userid在瀏覽器上路徑必須符合userxxx透

過 reqparamsid就可以取到 xxx這個字串值

另外express參數處理也提供了路由參數配對處理也可以透過正規表示法作為參數設定

var app = require(rsquoexpressrsquo)createServer()

port = 1337

applisten(port)

appget(^ip((d23)((d23))((d23))((d23))) function(req res)

ressend(reqparams)

)

上面程式碼可以發現後面路由設定的型態是正規表示法裡面設定

格式為ip之後必須要加上 ip型態才會符合資料格式同時取得 ip資料已經由正規表示法將資料做分群因此可以取得 ip的四個數字

此程式執行之後可以透過瀏覽器測試輸入網址為 localhost3000ip25525510010可以從頁面獲得資料

63 Express路由處理 53

Nodejs中文電子書

此章節全部範例程式碼如下

overview

author Caesar Chi

blog clonnblogspotcom

version 20120226

create server

var app = require(rsquoexpressrsquo)createServer()

port = 1337

applisten(port)

normal style

appget(rsquorsquo function(req res)

ressend(rsquohello worldrsquo)

)

appget(rsquotestrsquo function(req res)

ressend(rsquotest renderrsquo)

)

parameter style

appget(rsquouseridrsquo function(req res)

ressend(rsquouser rsquo + reqparamsid)

)

appget(rsquonumberrsquo function(req res)

ressend(rsquonumber rsquo + reqparamsnumber)

)

REGX style

appget(^ip((d23)((d23))((d23))) function(req res)

ressend(reqparams)

)

appget(rsquorsquo function(req res)

ressend(rsquoPage not foundrsquo 404)

54 6 Express介紹

Nodejs中文電子書

)

consolelog(rsquostart express servernrsquo)

64 Express middleware

Express 裡面有一個十分好用的應用概念稱為 middleware可以透過middleware做出複雜的效果同時上面也有介紹 next方法參數傳遞就是靠 middleware的概念來傳遞參數讓開發者可以明確的控制程式邏輯

create http server

appuse(expressbodyParser())

appuse(expressmethodOverride())

appuse(expresssession())

上面都是一種 middleware的使用方式透過 appuse方式裡面載入函式執行方法回應函式會包含三個基本參數responserequestnext其中next表示下一個 middleware執行函式同時會自動將預設三個參數繼續帶往下個函式執行底下有個實驗

上面的片段程式執行後開啟瀏覽器連結上 localhost1337會發現伺服器回應結果順序如下

first middle ware

second middle ware

execute middle ware

end middleware function

從上面的結果可以得知剛才設定的 middleware都生效了在 appuse設定的 middleware是所有 url皆會執行方法如果有指定特定方法就可以使用 appget的 middleware設定在 appget函式的第二個參數就可以帶入函式或者是匿名函式只要函式裡面最後會接受 request response next這三個參數同時也有正確指定 next函式的執行時機最後都會執行到最後一個方法當然開發者也可以評估程式邏輯要執行到哪一個階段讓邏輯

可以更為分明

64 Express middleware 55

Nodejs中文電子書

65 Express路由應用

在實際開發上可能會遇到需要使用參數等方式混和變數一起使用

express裡面提供了一個很棒的處理方法 appall這個方式可以先採用基本路由配對再將設定為每個不同的處理方式開發者可以透過這個方式簡

化自己的程式邏輯

overview

author

version 20120226

create server

var app = require(rsquoexpressrsquo)createServer()

port = 1337

users = [

name rsquoClonnrsquo

name rsquoChirsquo

]

applisten(port)

appall(rsquouseridoprsquo function(req res next)

requser = users[reqparamsid]

if (requser)

next()

else

next(new Error(rsquocannot find user rsquo + reqparamsid))

)

appget(rsquouseridrsquo function(req res)

ressend(rsquoviewing rsquo + requsername)

)

appget(rsquouserideditrsquo function(req res)

ressend(rsquoediting rsquo + requsername)

)

56 6 Express介紹

Nodejs中文電子書

appget(rsquouseriddeletersquo function(req res)

ressend(rsquodeleting rsquo + requsername)

)

appget(rsquorsquo function(req res)

ressend(rsquoPage not foundrsquo 404)

)

consolelog(rsquostart express servernrsquo)

內部宣告一組預設的使用者分別給予名稱設定藉由 appall這個方法可以先將路由雛形建立再接下來設定 appget的路徑格式只要符合格式就會分配進入對應的方法中像上面的程式當中如果使用者輸入路徑為user0除了執行 appall程式之後執行 next方法就會對應到路徑設定為userid的這個方法當中如果使用者輸入路徑為user0edit就會執行到useridedit的對應方法

66 Express GET應用範例

我們準備一個使用 GET方法傳送資料的表單

ltDOCTYPE htmlgt

lthtmlgt

ltheadgt

ltmeta http-equiv=rdquoContent-Typerdquo content=rdquotexthtml charset=utf8rdquogt

lttitlegtNodejs 菜鳥筆記 (1)lttitlegt

ltlink rel=rdquostylesheetrdquo href=rdquocssstylecssrdquo type=rdquotextcssrdquo media=rdquoallrdquo gt

ltheadgt

ltbodygt

lth1gtNodejs 菜鳥筆記-註冊lth1gt

ltform id=rdquosignuprdquo method=rdquoGETrdquo action=rdquohttplocalhost3000Signuprdquogt

ltlabelgt 使用者名稱ltlabelgtltinput type=rdquotextrdquo id=rdquousernamerdquo name=rdquousernamerdquo gtltbrgt

ltlabelgt 電子郵件ltlabelgtltinput type=rdquotextrdquo id=rdquoemailrdquo name=rdquoemailrdquo gtltbrgt

ltinput type=rdquosubmitrdquo value=rdquo 註冊我的帳號rdquo gtltbrgt

ltformgt

ltbodygt

lthtmlgt

66 Express GET應用範例 57

Nodejs中文電子書

這個表單沒有什麼特別的地方我們只需要看第 9 行form 使用的method是 GET然後 action是rdquohttplocalhost3000Signupldquo等一下我們要來撰寫Signup這個 URL Path的處理程式

處理 Signup行為

我們知道所謂的 GET方法會透過 URL來把表單的值給帶過去以上面的表單來說到時候 URL會以這樣的形式傳遞

httplocalhost3000Signupusername=xxxampemail=xxx

所以要能處理這樣的資料必須有以下功能

bull 解析 URL

bull 辨別動作是 Signup

bull 解析出 username和 email

一旦能取得 username和 email的值程式就能加以應用了

處理 Signup的程式碼雛形

load module

var url = require(rsquourlrsquo)

urlData = urlparse(requrltrue)

action = urlDatapathname

reswriteHead(200 rdquoContent-Typerdquordquotexthtml charset=utf-8rdquo)

if (action === rdquoSignuprdquo)

user = urlDataquery

resend(rdquolth1gtrdquo + userusername + rdquo 歡迎您的加入 lth1gtltpgt 我們已經將會員啟用信寄至rdquo + useremail + rdquoltpgtrdquo)

首先需要加載 url module它是用來協助我們解析 URL的模組接著使用 urlparse方法第一個傳入 url字串變數也就是 requrl另外第二個參數的用意是設為 ture則引進 querystring模組來協助處理預設是 false它影響到的是 urlDataquery設為 true會傳回物件不然就只是一般的字串urlparse會將字串內容整理成一個物件我們把它指定給 urlData

action變數作為記錄 pathname這是我們稍後要來判斷目前網頁的動作是什麼接著先將 html表頭資訊 (Header)準備好再來判斷路徑邏輯如

58 6 Express介紹

Nodejs中文電子書

果是 Signup這個動作就把 urlDataquery裡的資料指定給 user然後輸出userusername和 useremail把使用者從表單註冊的資料顯示於頁面中

最後進行程式測試啟動 nodejs主程式之後開啟瀏覽器就會看到表單填寫完畢按下送出就可以看到結果了

完整 nodejs程式碼如下

var http = require(rsquohttprsquo)

url = require(rsquourlrsquo)

fs = require(rdquofsrdquo)

server

server = httpcreateServer(function (reqres)

var urlData

encode = rdquoutf8rdquo

filePath = rdquoviewexpress_get_example_formhtmlrdquo

action

urlData = urlparse(requrltrue)

action = urlDatapathname

reswriteHead(200 rdquoContent-Typerdquordquotexthtml charset=utf-8rdquo)

if (action === rdquoSignuprdquo)

user = urlDataquery

resend(rdquolth1gtrdquo + userusername + rdquo 歡迎您的加入 lth1gtltpgt 我們已經將會員啟用信寄至rdquo + useremail + rdquoltpgtrdquo)

else

fsreadFile(filePath encode function(err file)

reswrite(file)

resend()

)

)

serverlisten(3000)

consolelog(rsquoServer 跑起來了現在時間是rsquo + new Date())

66 Express GET應用範例 59

Nodejs中文電子書

67 Express POST應用範例

一開始準備基本的 html表單傳送內容以 POST方式form的 action屬性設定為 POST其餘 html內容與前一個範例應用相同

ltDOCTYPE htmlgt

lthtmlgt

ltheadgt

ltmeta http-equiv=rdquoContent-Typerdquo content=rdquotexthtml charset=utf8rdquogt

lttitlegtNodejs 菜鳥筆記 (1)lttitlegt

ltlink rel=rdquostylesheetrdquo href=rdquocssstylecssrdquo type=rdquotextcssrdquo media=rdquoallrdquo gt

ltheadgt

ltbodygt

lth1gtNodejs 菜鳥筆記-註冊lth1gt

ltform id=rdquosignuprdquo method=rdquoPOSTrdquo action=rdquohttplocalhost3000Signuprdquogt

ltlabelgt 使用者名稱ltlabelgtltinput type=rdquotextrdquo id=rdquousernamerdquo name=rdquousernamerdquo gtltbrgt

ltlabelgt 電子郵件ltlabelgtltinput type=rdquotextrdquo id=rdquoemailrdquo name=rdquoemailrdquo gtltbrgt

ltinput type=rdquosubmitrdquo value=rdquo 註冊我的帳號rdquo gtltbrgt

ltformgt

ltbodygt

lthtmlgt

nodejs的程式處理邏輯與前面 GET範例類似部分程式碼如下

qs = require(rsquoquerystringrsquo)

if (action === rdquoSignuprdquo)

formData = rsquorsquo

reqon(rdquodatardquo function (data)

formData += data

)

reqon(rdquoendrdquo function ()

user = qsparse(formData)

resend(rdquolth1gtrdquo + userusername + rdquo 歡迎您的加入 lth1gtltpgt 我們已經將會員啟用信寄至rdquo + useremail + rdquoltpgtrdquo)

)

60 6 Express介紹

Nodejs中文電子書

主要加入了rsquoquerystringrsquo這個moduel方便我們等一下解析由表單 POST回來的資料另外加入一個 formData的變數用來搜集待等一下表單回傳的資料前面的 GET範例我們只從 req拿出 url的資料這次要在利用req身上的事件處理

JavaScript 在訂閱事件時使用 addEventListener而 nodejs 使用的則是on這邊加上了監聽 data的事件會在瀏覽器傳送資料到Web Server時被執行參數是它所接收到的資料型態是字串

接著再增加 end的事件當瀏覽器的請求事件結束時它就會動作

由於瀏覽器使用 POST在上傳資料時會將資料一塊塊地上傳因為我們在監聽 data事件時透過 formData變數將它累加起來lt不過由於我們

上傳的資料很少一次就結束不過如果日後需要傳的是資料比較大的檔

案這個累加動作就很重要

當資料傳完就進到 end 事件中會用到 qsparse 來解析 formDataformData的內容是字串內容是

username=wordsmithampemail=wordsmith40somewhere

而 qsparse可以幫我們把這個 querystring轉成物件的格式也就是

username=wordsmithampemail=wordsmith40somewhere

一旦轉成物件並指定給 user之後其他的事情就和 GET方法時操作的一樣寫 response的表頭將內容回傳並將 userusername和 useremail代入到內容中

修改完成後接著執行 nodejs程式啟動 web server開啟瀏覽器進入表單測試看看POST的方式能否順利運作

完整程式碼如下

var http = require(rsquohttprsquo)

url = require(rsquourlrsquo)

fs = require(rdquofsrdquo)

qs = require(rsquoquerystringrsquo)

server

server = httpcreateServer(function (reqres)

var urlData

67 Express POST應用範例 61

Nodejs中文電子書

encode = rdquoutf8rdquo

filePath = rdquoviewexpress_post_example_formhtmlrdquo

formData

action

urlData = urlparse(requrltrue)

action = urlDatapathname

reswriteHead(200 rdquoContent-Typerdquordquotexthtml charset=utf-8rdquo)

if (action === rdquoSignuprdquo)

formData = rsquorsquo

reqon(rdquodatardquo function (data)

formData += data

)

reqon(rdquoendrdquo function ()

user = qsparse(formData)

resend(rdquolth1gtrdquo + userusername + rdquo 歡迎您的加入 lth1gtltpgt 我們已經將會員啟用信寄至rdquo + useremail + rdquoltpgtrdquo)

)

else

fsreadFile(filePath encode function(err file)

reswrite(file)

resend()

)

)

serverlisten(3000)

consolelog(rsquoServer 跑起來了現在時間是rsquo + new Date())

62 6 Express介紹

Nodejs中文電子書

68 Express AJAX應用範例

在 Nodejs要使用 Ajax傳送資料並且與之互動在接受資料的部份沒有太大的差別client端不是用 GET就是用 POST來傳資料重點在處理完後用 JSON格式回傳當然 Ajax不見得只傳 JSON格式有時是回傳一段 HTML碼不過後者對伺服器來說基本上就和前兩篇沒有差別了所以我們還是以回傳 JSON做為這一回的主題

這一回其實大多數的工作都會落在前端 Ajax上面前端要負責發送與接收資料並在接收資料後撤掉原先發送資料的表單並將取得的資料

改成 HTML格式之後放上頁面

首先先準備 HTML靜態頁面資料

ltDOCTYPE htmlgt

lthtmlgt

ltheadgt

lttitlegtNodejs 菜鳥筆記 (3)lttitlegt

ltlink rel=rdquostylesheetrdquo href=rdquocssstylecssrdquo type=rdquotextcssrdquo media=rdquoallrdquo gt

ltscript src=rdquohttpsajaxgoogleapiscomajaxlibsjquery171jqueryminjsrdquogtltscriptgt

ltheadgt

ltbodygt

lth1gtNodejs 菜鳥筆記-註冊 (用 Ajax)lth1gt

ltform id=rdquosignuprdquo action=rdquoSignuprdquo method=rdquoPOSTrdquogt

ltlabelgt 使用者名稱ltlabelgtltinput type=rdquotextrdquo id=rdquousernamerdquo name=rdquousernamerdquo gtltbrgt

ltlabelgt 電子郵件ltlabelgtltinput type=rdquotextrdquo id=rdquoemailrdquo name=rdquoemailrdquo gtltbrgt

ltinput type=rdquosubmitrdquo value=rdquo 註冊我的帳號rdquo gtltbrgt

ltformgt

ltdiv id=rdquoresultrdquogt

ltdivgt

ltscript type=rdquotextjavascriptrdquogt

$(function()$(rsquosignup input[type=rdquosubmitrdquo]rsquo)click(function(e)

var user =

userusername = $(rdquousernamerdquo)val()useremail = $(rdquoemailrdquo)val()

$post(rdquoSignuprdquouserfunction(data)greet(data)

)

68 Express AJAX應用範例 63

Nodejs中文電子書

epreventDefault()

)

var greet = function(msg)

var resultNode = $(rsquoresultrsquo)greeting = $(rsquolth2gtHirsquo+msgusername+rsquo 你的會員 id 是rsquo+ msgid +rsquo 我們會將會員啟動信寄至rsquo+msgemail+rsquolth2gtrsquo)

$(rsquosignuprsquo)hide()resultNodehtml(rdquordquo)

resultNodeappend(greeting)

)

ltscriptgt

ltbodygt

lthtmlgt

HTML頁面上準備了一個表單用來傳送註冊資料接著直接引用了Google CDN來載入 jQuery用來幫我們處理 Ajax的工作這次要傳送和接收的工作很大的變動都在 HTML頁面上的 JavaScript當中我們要做的事有 (相關 jQuery處理這邊不多做贅述指提起主要功能解說)

bull 用 jQUery取得 submit按鈕綁定它的 click動作

bull 取得表單 username和 email的值存放在 user這個物件中

bull 用 jQuery的 $post方法將 user的資料傳到 Server

bull 一旦成功取得資料後透過 greet這個 function組成回報給 user的訊息

bull 清空原本給使用者填資料的表單

bull 將 Server回傳的 usernameemail和 id這 3個資料組成回應的訊息

bull 將訊息放到原本表單的位置

經過以上的處理後一個 Ajax的表單的基本功能已經完成

接著進行 nodejs主要程式的編輯部分程式碼如下

var fs = require(rdquofsrdquo)

64 6 Express介紹

Nodejs中文電子書

qs = require(rsquoquerystringrsquo)

if (action === rdquoSignuprdquo)

formData = rsquorsquo

reqon(rdquodatardquo function (data)

formData += data

)

reqon(rdquoendrdquo function ()

var msg

user = qsparse(formData)

userid = rdquo123456rdquo

msg = JSONstringify(user)

reswriteHead(200 rdquoContent-Typerdquordquoapplicationjson charset=utf-8rdquordquoContent-Lengthrdquomsglength)

resend(msg)

)

這裡的程式和前面 POST範例基本上大同小異差別在

bull 幫 user的資料加上 id隨意存放一些文字進去讓 Server回傳的資料多於 Client端傳上來的不然會覺得 Server都沒做事

bull 增加了 msg 這個變數存放將 user 物件 JSON 文字化的結果JSONstringify這個轉換函式是 V8引擎所提供的如果你好奇的話

bull 大重點來了我們要告訴 Client端這次回傳的資料格式是 JSON所在 Content-type和 Content-Length要提供給 Client

Server很輕鬆就完成任務了最後進行程式測試啟動 nodejs主程式之後開啟瀏覽器就會看到表單填寫完畢按下送出就可以看到結果了

最後 nodejs本篇範例程式碼如下

var http = require(rsquohttprsquo)

url = require(rsquourlrsquo)

fs = require(rdquofsrdquo)

qs = require(rsquoquerystringrsquo)

server

68 Express AJAX應用範例 65

Nodejs中文電子書

server = httpcreateServer(function (reqres)

var urlData

encode = rdquoutf8rdquo

filePath = rdquoviewexpress_ajax_example_formhtmlrdquo

formData

action

urlData = urlparse(requrltrue)

action = urlDatapathname

if (action === rdquoSignuprdquo)

formData = rsquorsquo

reqon(rdquodatardquo function (data)

formData += data

)

reqon(rdquoendrdquo function ()

var msg

user = qsparse(formData)

userid = rdquo123456rdquo

msg = JSONstringify(user)

reswriteHead(200 rdquoContent-TyperdquordquoapplicationjsonrdquordquoContent-Lengthrdquomsglength)

resend(msg)

)

else

fsreadFile(filePath encode function(err file)

reswriteHead(200 rdquoContent-Typerdquordquotexthtml charset=utf-8rdquo)

reswrite(file)

resend()

)

)

serverlisten(3000)

66 6 Express介紹

Nodejs中文電子書

consolelog(rsquoServer 跑起來了現在時間是rsquo + new Date())

69 原始資料提供

bull [NodeJS初學者筆記 (1)-用 GET傳送資料] (httpithelpithomecomtwquestion10087402)

bull [NodeJS初學者筆記 (2)-用 POST傳送資料] (httpithelpithomecomtwquestion10087489)

bull [NodeJS 初學者筆記 (3)-用 Ajax 傳送資料] (httpithelpithomecomtwquestion10087627)

69 原始資料提供 67

Nodejs中文電子書

68 6 Express介紹

7

CoffeeScript

bull Spine

bull Hem

bull Spineapp

Letrsquos Have a Cup of CoffeeScript

bull es5-shim ECMAScript 5 compatibility shims for legacy JavaScript engines

ESnext

node-iform - A middleware for node-validator httpsgithubcomguileennode-iform

69

Nodejs中文電子書

70 7 CoffeeScript

8

製作一個 Hubot的 Plurk Adapter

81 應用事項提醒

此應用範例以CoffeeScript編寫主要程式架構採用Github釋放的Hubot專案以下有幾個主要注意事項請讀者注意

bull Hubot 一開始的架構除了內建的兩個 Adapter 之外其餘都要以Nodejs的Module方式才能運作

bull Hubot的 binhubot裡面寫著 npm install所以不管你怎麼改原始碼也不能改變第一點的狀況

其實上述都在討論同一件事情NodeJS的模組而且模組不能設定相對路徑之類的來安裝一定要包裝成 npm相符和格式才有辦法正確執行

82 建立 Adapter

首先需要準備兩個檔案

bull packagejson

bull plurkcoffee

71

Nodejs中文電子書

有這兩個就足以變成 NodeJS的模組了在開始 Coding之前先來設定一下 packagejson相依性

rdquonamerdquo rdquohubot-plurkrdquo

rdquoversionrdquo rdquo011rdquo

rdquomainrdquo rdquoplurkrdquo

rdquodependenciesrdquo

rdquohubotrdquo rdquogt=205rdquo

rdquooauthrdquo rdquordquo

rdquocronrdquordquordquo

這邊會使用到NodeJS的 cron模組(Module)而登入 Plurk需要OAuth標準授權才行所以也需要加載 OAuth模組

注意Hubot在讀取非內建模組時會自動在前面加上 hubot-的前置

83 建立 Robot跟 API

本篇應用基本上程式碼大部分參考 Hubot - Twitter Adapter來製作主要差異只有在使用 OAuth這個模組Twitter有 Streaming可用而 Plurk則得用 Comet方式來達到即時讀取

plurkcoffee程式碼

Robot = require(rdquohubotrdquo)robot()

Adapter = require(rdquohubtordquo)adapter()

EventEmitter = require(rdquoeventsrdquo)EventEmitter

oauth = require(rdquooauthrdquo)

cronJob = require(rdquocronrdquo)CronJob

class Plurk exntends Adapter

class PlurkStreaming exnteds EventEmitter

72 8 製作一個 Hubot的 Plurk Adapter

Nodejs中文電子書

先弄個基本架構主要 Class皆參考 Twitter Adapter命名方式接著再將 Plurk這個 Class增加幾個Method基本上只要有 run send reply就夠了而 run用來做初始化的部分

class Plurk entends Adapter

send (plurk id strings⋯) -gt

reply (plurk id strings⋯) -gt

run -gt

看起來有點東西接著來處理主要的 Plurk API結合部分

class PlurkStreaming extends EventEmitter

consuctor (options) -gt

plurk (callback) -gt

觀察河道

getChannel -gt

取得 Comet 網址

reply (plurk id message) -gt

回噗

acceptFriends -gt

接受好友

get (path callback) -gt

GET 請求

post (path body callback)-gt

POST 請求(其實是裝飾)

request (method path body callback)-gt

主要的 OAuth 請求

comet (server callback)-gt

噗浪的 Comet 傳回是 JavaScript Callback 要另外處理後才會變成 JSON

然後把注意力集中到 constructor上先把建構子弄好

constructor (options) -gt

super()

if optionskey and optionssecret and optionstoken and optionstoken secret

key = optionskey

secret = optionssecret

token = optionstoken

token secret = optionstoken secret

83 建立 Robot跟 API 73

Nodejs中文電子書

建立 OAuth 連接

consumer = new oauthOAuth(

rdquohttpwwwplurkcomOAuthrequest tokenrdquo

rdquohttpwwwplurkcomOAuthaccess tokenrdquo

key

secret

rdquo10rdquo

rdquohttpwwwplurkcomOAuthauthorizerdquo

rdquoHMAC-SHA1rdquo

)

domain = rdquowwwplurkcomrdquo

初始化取得 Comet 網址

do getChannel

else

throw new Error(rdquo 參數不足需要 Key Secret Token Token Secretrdquo)

接著來處理 request這個 method

request (method path body callback) -gt

記錄一下這次的 Request

consolelog(rdquohttpdomainpathrdquo)

Callback 這邊先不丟進去要用另一種方式處理

request = consumerget(rdquohttpdomainpathrdquo token token secret null)

requeston rdquoresponserdquo (res) -gt

reson rdquodatardquo (chunk) -gt

parseResponse(chunk+rsquorsquo callback)

reson rdquoendrdquo (data) -gt

consolelog rdquoEnd Request pathrdquo

reson rdquoerrorrdquo (data) -gt

consolelog rdquoError rdquo + data

requestend()

處理資料

parseResponse = (data callback) -gt

if datalength gt 0

用 TryCatch 避免處理 JSON 出錯導致整個中斷

try

callback null JSONparse(data)

catch err

74 8 製作一個 Hubot的 Plurk Adapter

Nodejs中文電子書

consolelog(rdquoError Parse JSONrdquo + data err)

繼續執行

callback null data ||

大致上就是這樣上面程式的架構已經將整個 Hubot Plurk Adapter完成因為在測試時竟然因為噗浪 Lag而沒讀到完整的 Comet資料然後造成程式異常為了避免這個問題發生因此需要加上為了完美呈現需要再

加上 Comet的處理所以要使用到 EventEmitter的功能

comet (server callback) -gt

在 Callback 裡面會找不到自身所以設定區域變數

self =

記錄一下這次的 Request

consolelog(rdquo[Comet] serverrdquo)

Callback 這邊先不丟進去要用另一種方式處理

request = consumerget(rdquohttpdomainpathrdquo token token secret null)

requeston rdquoresponserdquo (res) -gt

reson rdquodatardquo (chunk) -gt

parseResponse(chunk+rsquorsquo callback)

reson rdquoendrdquo (data) -gt

consolelog rdquoEnd Request pathrdquo

請求結束發出事件通知可以進行下一次請求

selfemit rdquonextPlurkrdquo

reson rdquoerrorrdquo (data) -gt

consolelog rdquoError rdquo + data

requestend()

處理資料

parseResponse = (data callback) -gt

if datalength gt 0

用 trycatch 避免失敗中斷

try

去掉 JavaScript 的 Callback

data = datamatch(CometChannelscriptCallback((+))s)

jsonData = rdquordquo

if data

83 建立 Robot跟 API 75

Nodejs中文電子書

jsonData = JSONparse(data[1])

else

如果沒有任何 Match 嘗試直接 parse

jsonData = JSONparse(data)

catch err

consolelog(rdquo[Comet] Errorrdquo data err)

用 TryCatch 避免處理 JSON 出錯導致整個中斷

try

只傳入 json 的 data 部分

callback null jsonDatadata

catch err

consolelog(rdquo[Comet]Error Parse JSONrdquo + data err)

繼續執行

callback null data ||

後面的 get跟 post就簡單多了

get (path callback) -gt

request(rdquoGETrdquo path null callback)

post (path body callback) -gt

request(rdquoPOSTrdquo path body callback)

接著處理取的 Comet網址的 getChannel

getChannel -gt

self =

get rdquoAPPRealtimegetUserChannelrdquo (error data) -gt

if error

檢查是否有 comet server

if datacomet server

selfchannel = datacomet server

如果沒有 Channel Ready 就嘗試連接會失敗

selfemit(rsquochannel readyrsquo)

那麼先來處理 Plurk Adaper好處理的部份

send (plruk id strings⋯)-gt

跟 Reply 一樣直接交給 reply 做

reply plurk id strings⋯

76 8 製作一個 Hubot的 Plurk Adapter

Nodejs中文電子書

reply (plurk id strings⋯) -gt

stringsforEach (message) =gt

botreply(plruk id message)

接著把 run處理好就可以上線運作摟

run -gt

self =

options =

key processenvHUBOT PLURK KEY

secret processenvHUBOT PLURK SECRET

token processenvHUBOT PLURK TOKEN

token secret processenvHUBOT PLURK TOKEN SECRET

創建剛剛的 API

bot = new PlurkStreaming(options)

依照 Twitter 的 new RobotTextMessage 會沒有反應所以參考 hubot-minecraft 的方式

r = robotconstructor

處理噗浪河道訊息

doPlurk = (data)-gt

檢查是否為回噗

if dataresponse

datacontent raw = dataresponsecontent raw

datauser id = dataresponseuser id

確定有噗浪 ID 跟訊息

if dataplurk id and datacontent raw

selfreceive new rTextMessage(dataplurk id datacontent raw)

取得 Comet Server 完成開始第一次 Comet 連接

boton rdquochannel readyrdquo () -gt

botplurk selfdoPlurk

上一次 Comet 完成繼續 Polling

boton rdquonextPlurkrdquo ()-gt

botplurk selfdoPlurk

定時接受好友邀請

do botacceptFriends

bot = bot

83 建立 Robot跟 API 77

Nodejs中文電子書

終於完成 AdapterHubot專案裡面的 scripts資料夾內是互動部分不需要像 Adapter如此大費周章處理只新增檔案並且設計好對白之後就會回噗了我開發用的機器人在此大家可以去跟他玩玩[httpplurkcomelct9620 bot](httpplurkcomelct9620 bot)

84 原始資料提供

bull [製 作 一 個 Hubot 的 噗 浪 Adapter](http revo-skillfrosttw blog20120318create-a-hubot-plurk-adapter)

78 8 製作一個 Hubot的 Plurk Adapter

9

Nodejs好用工具介紹

91 logio

Real-time log monitoring in your browser

httplogioorg

nodejs (gt=0412 lt=0611)

npm config set unsafe-perm true

npm install -g --prefix=usrlocal logio

logio server start

httplocalhost8998

79

Nodejs中文電子書

80 9 Nodejs好用工具介紹

10

精選文章

101 Express

bull 使用 NodeJS + Express從 GETPOST Request取值 [邀稿中]

bull link2 [邀稿中]

bull link3 [邀稿中]

81

Nodejs中文電子書

82 10 精選文章

11

參考資源

111 Nodejs書籍

bull Programming Nodejs April 2012 (est) Travis Swicegood Pragmatic Bookshelf

bull e Little Book on CoffeeScript January 2012 Alex MacCaw OrsquoReilly Media

bull Node for Front-End Developers January 2012 Garann Means OrsquoReilly Media

bull Building Hypermedia APIs with HTML5 and Node November 2011 MikeAmundsen OrsquoReilly Media

bull Node Web Development August 2011 David Herron PacktPub

bull CoffeeScript Accelerated JavaScript Development July 2011 Trevor BurnhamPragmatic Bookshelf

bull Getting Started with GEO CouchDB and Nodejs July 2011 Mick omp-son OrsquoReilly Media

bull What Is Node July 2011 Brett McLaughlin OrsquoReilly Media

bull Node Up and Running May 2011 Tom Hughes-Croucher Mike WilsonOrsquoReilly Media

83

Nodejs中文電子書

bull Hands-on Nodejs e Nodejs introduction and API reference May 2011 Pe-dro Teixeira LeanPub

bull e Node Beginner Book Manuel Kiessling

bull Mastering Nodejs visionmedia

112 Nodejs影音教學

bull e Node Sessions e Best of OSCON 2011 August 2011 OrsquoReilly Media(Video)

bull Tom Hughes-Croucher on Node March 2011 OrsquoReilly Media (Video)

113 Nodejs教學網站

bull Node Tuts

114 Nodejs課程

bull Introduction To Nodejs Van Nguyen CodeLesson

84 11 參考資源

Page 2: Nodejs Wiki

Nodejs中文電子書

Nodejs Taiwan

2012年 05月 02日

目錄

關於本書 1

授權 1作者 2下載電子書 2原始碼 201 精選文章收錄流程 3

前言 5

1 Nodejs簡介 7

2 JavaScript與 NodeJS 11

21 Event Loop 1122 Scope與 Closure 1223 Callback 1324 CPS(Continuation-Passing Style) 1625 函數返回函數與 Currying 1726 流程控制 18

i

3 Nodejs安裝與設定 25

31 Ubuntu Linux 2532 Other Linux 2633 Windows 26

4 Nodejs基礎 29

41 nodejs http伺服器建立 2942 nodejs http路徑建立 3143 nodejs檔案讀取 3444 nodejs http靜態檔案輸出 3645 nodejs http GET資料擷取 3846 本章結語 40

5 NPM套件管理工具 41

51 安裝 NPM 4152 使用 NPM安裝套件 4553 套件的更新及維護 4854 使用 packagejson 49

6 Express介紹 51

61 Express安裝 5162 Express基本操作 5263 Express路由處理 5264 Express middleware 5565 Express路由應用 5666 Express GET應用範例 5767 Express POST應用範例 6068 Express AJAX應用範例 6369 原始資料提供 67

7 CoffeeScript 69

8 製作一個 Hubot的 Plurk Adapter 71

81 應用事項提醒 7182 建立 Adapter 71

ii

83 建立 Robot跟 API 7284 原始資料提供 78

9 Nodejs好用工具介紹 79

91 logio 79

10 精選文章 81

101 Express 81

11 參考資源 83

111 Nodejs書籍 83112 Nodejs影音教學 84113 Nodejs教學網站 84114 Nodejs課程 84

iii

iv

關於本書

這是一本關於 Nodejs技術的開放源碼電子書我們使用 GitHub維護電子書內容並交由 ContPub(Continuous Publishing)系統自動線上發佈本書提供 PDFEPUBMOBI及 HTML等格式您除了可以在網站檢視本書所有內容也可以將電子書下載至閱讀器保存

本書的線上閱讀網址與 GitHub資料同步更新

httpbooknodejstw

如果您想要取得本書的其他格式可以從 ContPub的電子書專頁下載最新版本

httpcontpuborgreadnodejs-wiki-book

本書適合 Nodejs初學者至進階開發者也歡迎您在學習時一起參與本書內容撰寫

授權

Nodejs台灣社群協作電子書採用創用 CC姓名標示 -非商業性授權您不必為本書付費

Nodejs Wiki Book book is licensed under the Attribution-NonCommercial 30Unported license You should not have paid for this book

您可以複製散佈及修改本書內容但請勿將本書用於商業用途

您可以在以下網址取得授權條款全文

1

Nodejs中文電子書

httpcreativecommonsorglicensesby-nc30legalcode

作者

本書由 Nodejs Taiwan社群成員協作以下名單依照字母排序

bull Caesar Chi (clonn)

bull Fillano Feng ( llano)

bull Kyle Lin (lyhcode)

Nodejs Taiwan是一個自由開放的技術學習社群我們歡迎您加入一起學習研究及分享

下載電子書

線上閱讀本書

httpbooknodejstw

PDF格式適合一般電腦及 7吋以上平板電腦閱讀

httpcontpuborgdownloadnodejs-wiki-bookpdf

EPUB格式適合 iPadiPhone行動裝置閱讀

httpcontpuborgdownloadnodejs-wiki-bookepub

MOBI格式適合 Kindle電子書閱讀器

httpcontpuborgdownloadnodejs-wiki-bookmobi

原始碼

本書最新的原始碼(中文版)網址如下

httpgithubcomnodejs-twnodejs-wiki-book

2 關於本書

Nodejs中文電子書

01 精選文章收錄流程

精選文章的用意是鼓勵作者在自已的網誌發表 Nodejs 教學再由Nodejs Taiwan社群挑選系列文章列入電子書的精選文集

1 將文章標題及連結貼到「精選文章」分類

2 社群工作小組以 E-Mail通知作者文章列入精選並邀請將內文授權給Nodejs Taiwan電子書分享

3 作者同意後由工作小組負責整理圖文發佈至電子書

4 以 E-Mail寄出感謝函通知原作者文章已收錄依作者意願調整文章內容

5 於 Nodejs Taiwan首頁及粉絲專頁推薦作者的文章

01 精選文章收錄流程 3

Nodejs中文電子書

4 關於本書

前言

Nodejs是 JavaScript程式語言的開發框架由 04x至目前 v060版本核心上已經有許多變更當然目的只有一個就是讓開發變得更簡單速

度能夠更快這是所有人奮鬥的目標而 nodeJS 華文維基平台主力由nodeJStw一群喜好 javascript開發者共同主筆內文以中文為主希望能夠降低開發者學習門檻藉由我們拋磚引玉讓更多華人開發者共同學習

討論

CoffeeScript好好玩

5

Nodejs中文電子書

6 前言

1

Nodejs簡介

Nodejs是一個高效能易擴充的網站應用程式開發框架 (Web Applica-tion Framework)它誕生的原因是為了讓開發者能夠更容易開發高延展性的網路服務不需要經過太多複雜的調校效能調整及程式修改就能

滿足網路服務在不同發展階段對效能的要求

Ryan Dahl是NodeJS的催生者目前任職於 Joyent (主機託管服務公司)他開發 NodeJS的目的就是希望能解決 Apache在連線數量過高時緩衝區 (buffer)和系統資源會很快被耗盡的問題希望能建立一個新的開發框架以解決這個問題因此嘗試使用效能十分優秀的 V8 JavaScript Engine讓網站開發人員熟悉的 JavaScript程式語言也能應用於後端服務程式的開發並且具有出色的執行效能

JavaScript是功能強大的物件導向程式語言但是在 JavaScript的官方規格中主要是定義網頁 (以瀏覽器為基礎) 應用程式需要的應用程式介面(API)對 JavaScript程式的應用範圍有所侷限為使 JavaScript能夠在更多用途發展CommonJS規範一組標準函式庫 (standard library)使 JavaScript的應用範圍能夠和 RubyPython及 Java等語言同樣豐富撰寫 NodeJS的JavaScript程式碼符合 CommonJS規範可以使用 CommonJS API為基礎開發程式並且在不同的 CommonJS兼容 (compliant) JavaScript執行環境中程式碼具有可攜性

7

Nodejs中文電子書

瀏覽器的 JavaScript與實現 CommonJS規範的 NodeJS有何不同呢瀏覽器的 JavaScript 提供 XMLHttpRequest 讓程式可以和網頁伺服器建立資料傳輸連線但這通常只能適用於網站開發的需求因為我們只能用

XMLHttpRequest與網頁伺服器通訊卻無法利用它建立其他類型如 Telnet FTP NTP的伺服器通訊如果我們想開發網路服務程式例如 SMTP電子郵件伺服器就必須使用 Sockets建立 TCP (某些服務則用 UDP)監聽及連線其他程式語言如 PHPJavaPythonPerl及 Ruby等在標準開發環境中皆有提供 Sockets API而瀏覽器的 JavaScript基於安全及貼近網站設計需求的考量下並未將 Sockets列入標準函式庫之中CommonJS的規範填補這種基礎函式庫功能的空缺遵循 CommonJS規範的 NodeJS可以直接使用 Sockets API建立各種網路服務程式

JavaScript語言本身支援 Lambda的特性因此一個匿名函式 (anonymousfunction)可以被儲存成一個變數並當作參數傳遞給另一個函式

var proc1 = function(op x) return op(x)

var op1 = function(x) return x+1 var op2 = function(x) return xx

proc1(op1 3) result is 4 proc1(op2 5) result is 25

另一個 JavaScript開發者必須掌握的語言特性稱為 Closure

NodeJS符合 CommonJS的規範使得 Callback方式易於實現也能夠讓更多同好基於 JavaScript開發符合 NodeJS的外掛模組 (Module)

回想以前要寫一個能夠同時容納上百人的上線的網路服務需要花費

多大的苦工可能 10人多就需要經過一次程式調整而 NodeJS就是為了解決這個困境NodeJS因此誕生它是一種利用 V8 Javascript編譯器所開發的產品利用 V8編譯器的高效能與 Javascript的程式開發特性所產生的網路程式

開發人員所編寫出來的 Javascript腳本程式怎麼可能會比其他語言寫出來的網路程式還要快上許多呢以前的網路程式原理是將使用者每次的

連線 (connection)都開啟一個執行緒 (thread)當連線爆增的時候將會快速耗盡系統效能並且容易產生阻塞 (block)的發生

8 1 Nodejs簡介

Nodejs中文電子書

NodeJS對於資源的調校有所不同當程式接收到一筆連線 (connection)會通知作業系統透過 epoll kqueue devpoll或 select將連線保留並且放入heap中配置先讓連線進入休眠 (Sleep)狀態當系統通知時才會觸發連線的 callback這種處理連線方式只會佔用掉記憶體並不會使用到 CPU資源另外因為採用 Javascript語言的特性每個 request都會有一個 callback如此可以避免發生 Block的狀況發生

基於 Callback特性目前NodeJS大多應用於 Comet(long pulling) RequestServer或者是高連線數量的網路服務上目前也有許多公司將 NodeJS設為內部核心網路服務之一在 NodeJS也提供了外掛管理 (Node packagemanagement)讓愛好 NodeJS輕易開發更多有趣的服務外掛並且提供到 npm讓全世界使用者快速安裝使用

本書最後執行測試版本為 nodejs v068相關 API文件可查詢 lsquohttpnodejsorg lthttpnodejsorggtlsquo本書所有範例均可於 linux windows上執行如遇到任何問題歡迎至 lsquohttpnodejstw lthttpnodejstwgtlsquo詢問對於nodejs相關問題

9

Nodejs中文電子書

10 1 Nodejs簡介

2

JavaScript與 NodeJS

其實使用 JavaScript 在網頁端與伺服器端的差距並不大但是為了使NodeJS可以發揮他最強大的能力有一些知識還是必要的所以還是針對這些主題介紹一下其中 Event LoopScope以及 Callback其實是比較需要了解的基本知識cpscurrying ow control是更進階的技巧與應用

21 Event Loop

可能很多人在寫 Javascript 時並不知道他是怎麼被執行的這個時候可以參考一下 jQuery作者 John Resig一篇好文章介紹事件及 timer怎麼在瀏覽器中執行How JavaScript Timers Work通常在網頁中所有的Javascript執行完畢後(這部份全部都在 global scope跑除非執行函數)接下來就是如 John Resig解釋的這樣所有的事件處理函數以及 timer執行的函數會排在一個 queue結構中利用一個無窮迴圈不斷從 queue中取出函數來執行這個就是 event loop

(除了 John Resig的那篇文章Nicholas C Zakas的 ldquoProfessional Javascriptfor Web Developer 2nd editionrdquo 有一個試閱本httpyuiblogcomassetspdfzakas-projs-2ed-ch18pdf598頁剛好也有簡短的說明)

所以在 Javascript中雖然有非同步但是他並不是使用執行緒所有

11

Nodejs中文電子書

的事件或是非同步執行的函數都是在同一個執行緒中利用 event loop的方式在執行至於一些比較慢的動作例如 IO網頁 render re ow等實際動作會在其他執行緒跑等到有結果時才利用事件來觸發處理函數來處理

這樣的模型有幾個好處沒有執行緒的額外成本所以反應速度很快不會

有任何程式同時用到同一個變數不必考慮 lock也不會產生 dead lock所以程式撰寫很簡單但是也有一些潛在問題任一個函數執行時間較長都

會讓其他函數更慢執行(因為一個跑完才會跑另一個)在多核心硬體普遍

的現在無法用單一的應用程式 instance發揮所有的硬體能力用 NodeJS撰寫伺服器程式碰到的也是一樣的狀況要讓系統發揮 event loop的效能就要盡量利用事件的方式來組織程式架構另外對於一些有可能較為耗

時的操作可以考慮使用 processnextTick函數來讓他以非同步的方式執行避免在同一個函數中執行太久擋住所有函數的執行

如果想要測試 event loop怎樣在「瀏覽器」中運行可以在函數中呼叫alert()這樣會讓所有 Javascript的執行停下來尤其會干擾所有使用 timer的函數執行有一個簡單的例子這是一個會依照設定的時間間隔嚴格執

行動作的動畫如果時間過了就會跳過要執行的動作點按圖片以後人

物會快速旋轉但是在旋轉執行完畢前按下「delay」按鈕讓 alert訊息等久一點接下來的動畫就完全不會出現了

22 Scope與 Closure

要快速理解 JavaScript的 Scope(變數作用範圍)原理只要記住他是Lexical Scope就差不多了簡單地說變數作用範圍是依照程式定義時(或者叫做程式文本)的上下文決定而不是執行時的上下文決定

為了維護程式執行時所依賴的變數即使執行時程式運行在原本的

scope之外他的變數作用範圍仍然維持不變這時程式依賴的自由變數(定義時不是 local的而是在上一層 scope定義的變數)一樣可以使用就好像被關閉起來所以叫做 Closure用程式看比較好懂

function outter(arg1)

arg1 及 free_variable1 對 inner 函數來說都是自由變數

var free_variable1 = 3

return function inner(arg2)

12 2 JavaScript與 NodeJS

Nodejs中文電子書

var local_variable1 =2arg2 及 local_variable1 對 inner 函數來說都是本地變數

return arg1 + arg2 + free_variable + local_variable1

var a = outter(1)變數 a就是 outter函數執行後返回的 inner函數 var b =a(4)執行 inner函數執行時上下文已經在 outter函數之外但是仍然能正常執行而且可以使用定義在 outter函數裡面的 arg1及 free variable1變數 consolelog(b)結果 10

在 Javascript中scope最主要的單位是函數(另外有 global及 eval)所以有可能製造出 closure的狀況通常在形式上都是有巢狀的函數定義而且內側的函數使用到定義在外側函數裡面的變數

Closure有可能會造成記憶體洩漏主要是因為被參考的變數無法被垃圾收集機制處理造成佔用的資源無法釋放所以使用上必須考慮清楚

不要造成意外的記憶體洩漏(在上面的例子中如果 a一直未執行使用到的記憶體就不會被釋放)

跟透過函數的參數把變數傳給函數比較起來Javascript Engine會比較難對 Closure進行最佳化如果有效能上的考量這一點也需要注意

23 Callback

要介紹 Callback之前要先提到 JavaScript的特色

JavaScript是一種函數式語言(functional language)所有 Javascript語言內的函數都是高階函數 (higher order function這是數學名詞計算機用語好像是 rst class function意指函數使用沒有任何限制與其他物件一樣)也就是說函數可以作為函數的參數傳給函數也可以當作函數的返回值這個特性讓 Javascript的函數使用上非常有彈性而且功能強大

callback在形式上其實就是把函數傳給函數然後在適當的時機呼叫傳入的函數Javascript使用的事件系統通常就是使用這種形式NodeJS中有一個物件叫做 EventEmitter這是 NodeJS事件處理的核心物件所有會使用事件處理的函數都會「繼承」這個物件(這裡說的繼承實

作上應該像是 mixin)他的使用很簡單可以使用物件on(事件名稱callback

23 Callback 13

Nodejs中文電子書

函數) 或是物件addListener(事件名稱callback 函數) 把你想要處理事件的函數傳入在物件中可以使用物件emit(事件名稱 參數) 呼叫傳入的callback函數這是 Observer Pattern的簡單實作而且跟在網頁中使用 DOM的 addEventListener使用上很類似也很容易上手不過 NodeJS是大量使用非同步方式執行的應用所以程式邏輯幾乎都是寫在 callback函數中當邏輯比較複雜時大量的 callback會讓程式看起來很複雜也比較難單元測試舉例來說

var p client = new Db(lsquointegration tests 20rsquo new Server(ldquo127001rdquo 27017) lsquopkrsquoCustomPKFactory) p clientopen(function(err p client)

p clientdropDatabase(function(err done)

p clientcreateCollection(lsquotest custom keyrsquo function(err collection)

collectioninsert(lsquoarsquo1 function(err docs)

collection nd(lsquo idrsquonew ObjectID(ldquoaaaaaaaaaaaardquo) function(err cursor)

cursortoArray(function(err items) testassertEquals(1 itemslength) p clientclose()

)

)

)

)

)

)

這是在網路上看到的一段操作 mongodb的程式碼為了循序操作所以必須在一個 callback裡面呼叫下一個動作要使用的函數這個函數裡面還是會使用 callback最後就形成一個非常深的巢狀

這樣的程式碼會比較難進行單元測試有一個簡單的解決方式是

盡量不要使用匿名函數來當作 callback或是 event handler透過這樣的方式

14 2 JavaScript與 NodeJS

Nodejs中文電子書

就可以對各個 handler做單元測試了例如

var http = require(lsquohttprsquo) var tools =

cookieParser function(request response) if(requestheaders[rsquoCookiersquo]) do parsing

var server = httpcreateServer(function(request response)

thisemit(lsquoinitrsquo request response)

) serveron(lsquoinitrsquo toolscookieParser) serverlisten(8080 lsquo127001rsquo)

更進一步可以把 tools改成外部 module例如叫做 toolsjs

moduleexports = cookieParser function(request response) if(requestheaders[rsquoCookiersquo]) do parsing

然後把程式改成

var http = require(lsquohttprsquo)

var server = httpcreateServer(function(request response) thisemit(lsquoinitrsquo re-quest response)

) serveron(lsquoinitrsquo require(lsquo toolsrsquo)cookieParser) serverlisten(8080lsquo127001rsquo)

這樣就可以單元測試 cookieParser了例如使用 nodeunit時可以這樣寫

var testCase = require(lsquonodeunitrsquo)testCase moduleexports = testCase(

ldquosetUprdquo function(cb) thisrequest = headers Cookielsquoname1val1 name2val2rsquo thisresponse = thisresult= name1rsquoval1rsquoname2rsquoval2rsquo

cb()

ldquotearDownrdquo function(cb)

cb()

23 Callback 15

Nodejs中文電子書

ldquonormal caserdquo function(test)

testexpect(1) var obj = require(lsquotoolsrsquo)cookieParser(thisrequest thisresponse)testdeepEqual(obj thisresult) testdone()

)

善於利用模組可以讓程式更好維護與測試

24 CPS(Continuation-Passing Style)

cps 是 callback 使用上的特例形式上就是在函數最後呼叫 callback這樣就好像把函數執行後把結果交給 callback 繼續運行所以稱作continuation-passing style利用 cps可以在非同步執行的情況下透過傳給 callback的這個 cps callback來獲知 callback執行完畢或是取得執行結果例如

lthtmlgt ltbodygt ltdiv id=rdquopanelrdquo style=rdquovisibilityhiddenrdquogtlt divgtlt bodygt lt htmlgt ltscriptgt var request = new XMLHttpRequest() re-questopen(lsquoGETrsquo lsquotest749txt timestamp=rsquo+new Date()getTime() true) re-questaddEventListener(lsquoreadystatechangersquo function(next)

return function() if(thisreadyState===4ampampthisstatus===200) next(thisresponseText)lt== 傳入的 cps callback 在動作完成時執行並取得結果進一步處理

(function(str)lt==這個匿名函數就是 cps callback

documentgetElementById(lsquopanelrsquo)innerHTML=str docu-mentgetElementById(lsquopanelrsquo)stylevisibility = lsquovisiblersquo

) false) requestsend() ltscriptgt

進一步的應用也可以參考 2-6流程控制

16 2 JavaScript與 NodeJS

Nodejs中文電子書

25 函數返回函數與 Currying

前面的 cps範例裡面使用了函數返回函數這是為了把 cps callback傳遞給 onreadystatechange事件處理函數的方法(因為這個事件處理函數並沒有設計好會傳送接收這樣的參數)實際會執行的事件處理函數其實是內層返回的那個函數之外包覆的這個函數主要是為了利用 Closure把 next傳給內層的事件處理函數這個方法更常使用的地方是為了解決一些

scope問題例如

ltscriptgt var accu=0count=10 for(var i=0 iltcount i++)

setTimeout(

function() countndash accu+=i if(countlt=0)

consolelog(accu)

50)

ltscriptgt

最後得出的結果會是 100而不是想像中的 45這是因為等到 setTime-out指定的函數執行時變數 i已經變成 10而離開迴圈了要解決這個問題就需要透過 Closure來保存變數 i

ltscriptgt var accu=0count=10 for(var i=0 iltcount i++)

setTimeout(

function(i) return function() countndash

accu+=i if(countlt=0)

consolelog(accu)

(i)

50)

25 函數返回函數與 Currying 17

Nodejs中文電子書

淺藍色底色的部份是跟上面例子不一樣的地方 ltscriptgt

函數返回函數的另外一個用途是可以暫緩函數執行例如

function add(m n) return m+n

var a = add(20 10) consolelog(a)

add這個函數必須同時輸入兩個參數才有辦法執行如果我希望這個函數可以先給它一個參數等一些處理過後再給一個參數然後得到結

果就必須用函數返回函數的方式做修改

function add(m)

return function(n) return m+n

var wait another arg = add(20)先給一個參數 var a = function(arr)

var ret=0 for(var i=0iltarrlengthi++) ret+=arr[i] return ret

([1234])計算一下另一個參數 var b = wait another arg(a)然後再繼續執行 consolelog(b)

像這樣利用函數返回函數使得原本接受多個參數的函數可以一次

接受一個參數直到參數接收完成才執行得到結果的方式有一個學名就

叫做Currying

綜合以上許多奇技淫巧就可以透過用函數來處理函數的方式調整

程式流程接下來看看

26 流程控制

(以 sync方式使用 async函數避開巢狀 callback循序呼叫 async callback等奇技淫巧)

建議參考

bull httphowtonodeorgcontrol- ow

bull httphowtonodeorgcontrol- ow-part-ii

18 2 JavaScript與 NodeJS

Nodejs中文電子書

bull httphowtonodeorgcontrol- ow-part-iii

bull httpblogmixunet20110202essential-node-js-patterns-and-snippets

這幾篇都是非常經典的 NodeJSJavascript流程控制好文章(阿mixu是在介紹一些 pattern時提到這方面的主題)不過我還是用幾個簡單的程式介紹一下做法跟概念

並發與等待

下面的程式參考了 mixu文章中的做法

var wait = function(callbacks done) consolelog(lsquowait startrsquo) var counter = call-backslength var results = [] var next = function(result) 接收函數執行結果並判斷是否結束執行 resultspush(result) if(ndashcounter == 0) done(results)如果結束執行就把所有執行結果傳給指定的 callback處理 for(var i = 0 i lt callbackslength i++) 依次呼叫所有要執行的函數 callbacks[i](next) consolelog(lsquowait endrsquo)

wait( [ function(next) setTimeout(function() consolelog(lsquodone arsquo) var result =500 next(result) 500) function(next) setTimeout(function() con-solelog(lsquodone brsquo) var result = 1000 next(result) 1000) function(next)setTimeout(function() consolelog(lsquodone crsquo) var result = 1500 next(1500) 1500) ] function(results) var ret = 0 i=0 for( iltresultslength i++) ret+= results[i] consolelog(lsquodone all result lsquo+ret)

)

執行結果wait start wait end done a done b done c done all result 3000

可以看出來其實 wait並不是真的等到所有函數執行完才結束執行而是在所有傳給他的函數執行完畢後(不論同步非同步)才執行處理結

果的函數(也就是 done())

不過這樣的寫法還不夠實用因為沒辦法實際讓函數可以等待執行

完畢又能當作事件處理函數來實際使用上面參考到的 Tim Caswell的文

26 流程控制 19

Nodejs中文電子書

章裡面有一種解法不過還需要額外包裝(在他的例子中)NodeJS核心的 fs物件把一些函數(例如 readFile)用 Currying處理類似像這樣

var fs = require(lsquofsrsquo) var readFile = function(path)

return function(callback errback)

fsreadFile(path function(err data)

if(err) errback()

else callback(data)

)

其他部份可以參考 Tim Caswell的文章他的 Doparallel跟上面的 wait差不多意思這裡只提示一下他沒說到的地方

另外一種做法是去修飾一下 callback當他作為事件處理函數執行後再用 cps的方式取得結果

ltscriptgt function Wait(fns done)

var count = 0 var results = [] thisgetCallback = function(index)

count++ return (function(waitback)

return function() var i=0args=[]for(iltargumentslengthi++)

argspush(arguments[i])

argspush(waitback) fns[index]apply(thisargs)

)(function(result) resultspush(result) if(ndashcount == 0)

20 2 JavaScript與 NodeJS

Nodejs中文電子書

done(results)

)

var a = new Wait(

[ function(waitback) consolelog(lsquodone arsquo) var result = 500 wait-back(result) function(waitback) consolelog(lsquodone brsquo) var result =1000 waitback(result) function(waitback) consolelog(lsquodone crsquo) varresult = 1500 waitback(result) ] function(results) var ret = 0 i=0for( iltresultslength i++) ret += results[i] consolelog(lsquodone allresult lsquo+ret)

) var callbacks = [agetCallback(0)agetCallback(1)agetCallback(0)agetCallback(2)]一次取出要使用的 callbacks避免結果提早送出 setTimeout(callbacks[0]500) setTimeout(callbacks[1] 1000) setTimeout(callbacks[2] 1500) setTime-out(callbacks[3] 2000) 當所有取出的 callbacks執行完畢就呼叫 done()來處理結果 ltscriptgt

執行結果

done a done b done a done c done all result 3500

上面只是一些小實驗更成熟的作品是 Tim Caswell 的 stephttpsgithubcomcreationixstep

如果希望真正使用同步的方式寫非同步則需要使用 Promisejs這一類的 library來轉換非同步函數不過他結構比較複雜 XD(見仁見智不過有些人認為 Promise有點過頭了)httpblogsmsdncombrbucktonarchive20110815promise-js-2-0-promise-framework-for-javascriptaspx

如果想不透過其他 Library做轉換又能直接用同步方式執行非同步函數大概就要使用一些需要額外 compile原始程式碼的方法了例如 BrunoJouhier的 streamlinejshttpsgithubcomSagestreamlinejs

26 流程控制 21

Nodejs中文電子書

循序執行

循序執行可以協助把非常深的巢狀 callback結構攤平例如用這樣的簡單模組來做(serialjs)

moduleexports = function(funs) var c = 0 if(isArrayOfFunctions(funs))

throw(lsquoArgument type was not matched Should be array of func-tionsrsquo)

return function()

var args = Arrayprototypeslicecall(arguments 0) if((cgt=funslength))

c++ return funs[c-1]apply(this args)

function isArrayOfFunctions(f ) if(typeof f == lsquoobjectrsquo) return false if(flength)return false if(fconcat) return false if(fsplice) return false var i = 0 for(iltflength i++)

if(typeof f[i] == lsquofunctionrsquo) return false

return true

簡單的測試範例(testSerialjs)使用 fs 模組確定某個 path 是檔案然後讀取印出檔案內容這樣會用到兩層的 callback所以測試中有使用serial的版本與 nested callbacks的版本做對照

var serial = require(lsquoserialrsquo) fs = require(lsquofsrsquo) path = lsquodclientjsrsquo cb = serial([ func-tion(err data)

if(err)

if(dataisFile) fsreadFile(path cb)

22 2 JavaScript與 NodeJS

Nodejs中文電子書

else consolelog(err)

function(err data)

if(err) consolelog(lsquo[ attened by searial]rsquo) con-solelog(datatoString(lsquoutf8rsquo))

else consolelog(err)

]) fsstat(path cb)

fsstat(path function(err data) 第一層 callback if(err)

if(dataisFile)

fsreadFile(path function(err data) 第二層 callback if(err)

consolelog(lsquo[nested callbacks]rsquo) con-solelog(datatoString(lsquoutf8rsquo))

else consolelog(err)

)

else consolelog(err)

)

關鍵在於這些 callback的執行是有順序性的所以利用 serial返回的一個函數 cb來取代這些 callback然後在 cb中控制每次會循序呼叫的函數

26 流程控制 23

Nodejs中文電子書

就可以把巢狀的 callback攤平成循序的 function陣列(就是傳給 serial函數的參數)

測試中的dclientjs 是一個簡單的 dnode 測試程式放在跟 testSerialjs同一個目錄

var dnode = require(lsquodnodersquo)

dnodeconnect(8000 lsquolocalhostrsquo function(remote)

remoterestart(function(str) consolelog(str) processexit()

)

)

執行測試程式後出現結果

[ attened by searial] var dnode = require(lsquodnodersquo)

dnodeconnect(8000 lsquolocalhostrsquo function(remote)

remoterestart(function(str) consolelog(str) processexit()

)

)

[nested callbacks] var dnode = require(lsquodnodersquo)

dnodeconnect(8000 lsquolocalhostrsquo function(remote)

remoterestart(function(str) consolelog(str) processexit()

)

)

對照起來看兩種寫法的結果其實是一樣的但是利用 serialjs巢狀的 callback結構就會消失

不過這樣也只限於順序單純的狀況如果函數執行的順序比較複雜

(不只是一直線)還是需要用功能更完整的流程控制模組比較好例如

httpsgithubcomcaolanasync

24 2 JavaScript與 NodeJS

3

Nodejs安裝與設定

本篇將講解如何在各個不同 OS建立 NodeJS環境目前 NodeJS 048版本環境架設方式需依賴 Linux指令才可編譯完成當然在不同作業系統中也已經有 NodeJS package可以直接使用指令快速架設以下各不同作業系統解說如何安裝 NodeJS

31 Ubuntu Linux

更新推薦使用 nvm

git clone gitgithubcomcreationixnvmgit ~nvm

echo rdquo ~nvmnvmshrdquo gtgt ~bashrc

nvm install v0614

nvm alias default v0614

以 上 可 參 考 http dreamerslabcom blog tw how-to-setup-a-node-js-development-environment-on-ubuntu-11-04

使用 APT套件管理工具是常見的方法以下是使用社群提供的 PPA安裝方式

sudo apt-get install python-software-properties

sudo add-apt-repository ppachris-leanodejs-devel

25

Nodejs中文電子書

sudo apt-get update

sudo apt-get install nodejs

32 Other Linux

Linux很適合作為 NodeJS的伺服器作業系統及開發環境安裝前請先確認以下套件已正確安裝

bull curl (wget)用來下載檔案的工具

bull git先進的版本控制工具

bull g++ GNU C++軟體編譯工具

bull make GNU軟體專案建置工具

安裝指令如下如設有權限問題請在指令前面加上 sudo

git clone httpsgithubcomjoyentnodegit

cd node

git checkout v067

configure

make

sudo make install

接著測試 nodeJS是否正常執行

node --version

出現版本訊息即表示安裝成功

33 Windows

nodeJS 在 v060 版本之後開始正式支援 windows native直接使用nodeexe 就可以執行程式支援性完全與 linux 相同更棒的部份就是不需經過編譯經過下載之後簡單設定完成立即開發 node程式

26 3 Nodejs安裝與設定

Nodejs中文電子書

下載 nodejs安裝檔案 lthttpnodejsorgdownloadgt

如此完成 windows native nodeexe安裝接著可以進入 command line執行測試在 command line輸入rdquonoderdquo會出現 nodejs版本訊息畫面表示安裝完成

33 Windows 27

Nodejs中文電子書

28 3 Nodejs安裝與設定

4

Nodejs基礎

前篇文章已經由介紹安裝至設定都有完整介紹nodeJS 內部除了javascript常用的函式 (function)物件 (object)之外也有許多不同的自訂物件nodeJS預設建立這些物件為核心物件是為了要讓開發流程更為這些資料在官方文件已經具有許多具體說明接下來將會介紹在開發 nodeJS程式時常見的物件特性與使用方法

41 nodejs http伺服器建立

在 lsquonodejs官方網站 lthttpnodejsorggtlsquo裡面有舉一個最簡單的 HTTP伺服器建立一開始初步就是建立一個伺服器平台讓 nodejs可以與瀏覽器互相行為每種語言一開始的程式建立都是以 Hello world開始最初也從 Hello world帶各位進入 nodejs的世界

輸入以下程式碼儲存檔案為 node basic http hello worldjs

var server

ip = rdquo127001rdquo

port = 1337

http = require(rsquohttprsquo)

server = httpcreateServer(function (req res)

29

Nodejs中文電子書

reswriteHead(200 rsquoContent-Typersquo rsquotextplainrsquo)

resend(rsquoHello Worldnrsquo)

)

serverlisten(port ip)

consolelog(rdquoServer running at httprdquo + ip + rdquordquo + port)

程式碼解講一開始需要有幾個基本的變數

bull ip 機器本身的 ip位置因為使用本地端因此設定為 127001

bull port 需要開通的阜號通常設定為 http port 80因範例不希望與基本 port相衝隨意設定為 1337

在 nodejs 的程式中有許多預設的模組可以使用因此需要使用require方法將模組引入在這邊我們需要使用 http這個模組因此將 http載入Http模組裡面內建有許多方法可以使用這邊採用 createServer創建一個基本的 http伺服器再將 http伺服器給予一個 server變數裡面的回呼函式 (call back function)可以載入 http伺服器的資料與回應方法 (requestresponse)在程式裡面就可以看到我們直接回應給瀏覽器端所需的 Header回應內容

reswriteHead(200 rsquoContent-Typersquo rsquotextplainrsquo)

resend(rsquoHello Worldnrsquo)

Http伺服器需要設定 port ip在最後需要設定 Http監聽需要使用到listen事件監聽所有 Http伺服器行為

httplisten(port ip)

所有事情都完成之後需要確認伺服器正確執行因此使用 console在javascript裡就有這個原生物件console所印出的資料都會顯示於 nodejs伺服器頁面這邊印出的資料並不會傳送到使用者頁面上之後許多除壞

(debug)都會用到 console物件

consolelog(rdquoServer running at httprdquo + ip + rdquordquo + port)

30 4 Nodejs基礎

Nodejs中文電子書

42 nodejs http路徑建立

前面已經介紹如何建立一個簡單的 http伺服器接下來本章節將會介紹如何處理伺服器路徑 (rout)問題在 http的協定下所有從瀏覽器發出的要求 (request)都需要經過處理路徑上的建立也是如此

路徑就是指伺服器 ip位置或者是網域名稱之後對於伺服器給予的要求修改剛才的 hello world檔案修改如下

server = httpcreateServer(function (req res)

consolelog(requrl)

reswriteHead(200 rsquoContent-Typersquo rsquotextplainrsquo)

resend(rsquohello worldnrsquo)

)

重新啟動 nodejs程式後在瀏覽器端測試一下路徑行為結果如下圖

當在瀏覽器輸入 http1270011337test 在伺服器端會收到兩個要求一個是我們輸入的test要求另外一個則是faviconicotest的路徑要求http伺服器本身需要經過程式設定才有辦法回應給瀏覽器端所需要的回應在伺服器中所有的路徑要求都是需要被解析才有辦法取得資料從

42 nodejs http路徑建立 31

Nodejs中文電子書

上面解說可以了解到在 nodejs當中所有的路徑都需要經過設定未經過設定的路由會讓瀏覽器無法取得任何資料導致錯誤頁面的發生底下將會解

說如何設定路由同時避免發生錯誤情形先前 nodejs程式需要增加一些修改才能讓使用者透過瀏覽器在不同路徑時有不同的結果根據剛才

的程式做如下的修改

var server

ip = rdquo127001rdquo

port = 1337

http = require(rsquohttprsquo)

url = require(rsquourlrsquo)

path

server = httpcreateServer(function (req res)

path = urlparse(requrl)

reswriteHead(200 rsquoContent-Typersquo rsquotextplainrsquo)

switch (pathpathname)

case rdquoindexrdquo

resend(rsquoI am indexnrsquo)

break

case rdquotestrdquo

resend(rsquothis is test pagenrsquo)

break

default

resend(rsquodefault pagenrsquo)

break

)

serverlisten(port ip)

consolelog(rdquoServer running at httprdquo + ip + rdquordquo + port)

程式做了片段的修改首先載入 url模組另外增加一個 path變數url模組就跟如同他的命名一般專門處理 url字串處理裡面提供了許多方法來解決路徑上的問題因為從瀏覽器發出的要求路徑可能會帶有多種需求

或者 GET參數組合等因此我們需要將路徑單純化取用路徑部分的資料即可例如使用者可能會送出 http1270011337testsend=1如果直接

32 4 Nodejs基礎

Nodejs中文電子書

信任 requrl就會收到結果為testsend=1所以需要透過 url模組的方法將路徑資料過濾

在這邊使用 urlparse的方法裡面帶入網址格式資料會回傳路徑資料為了後需方便使用將回傳的資料設定到 path變數當中在回傳的路徑資料裡面包含資訊如下圖

這邊只需要使用單純的路徑要求直接取用 pathpathname就可以達到我們的目的

最後要做路徑的判別在不同的路徑可以指定不同的輸出在範例中

有三個可能結果第一個從瀏覽器輸入index就會顯示 index結果test 就會呈現出 test頁面最後如果都不符合預期的輸入會直接顯示 default的頁面最後的預防可以讓瀏覽器不會出現非預期結果讓程式的可靠性提昇

底下為測試結果

42 nodejs http路徑建立 33

Nodejs中文電子書

43 nodejs檔案讀取

前面已經介紹如何使用路由(rount)做出不同的回應實際應用只有在瀏覽器只有輸出幾個文字資料總是不夠的在本章節中將介紹如何使

用檔案讀取輸出檔案資料讓使用者在前端瀏覽器也可以讀取到完整的

html css javascript檔案輸出

檔案管理最重要的部分就是 lsquoFile system lthttpnodejsorgdocslatestapifshtmlgtlsquo這個模組此模組可以針對檔案做管理監控讀取等行為裡面有許多預設的方法底下是檔案輸出的基本範例底下會有兩個檔案

第一個是靜態 html檔案

ltDOCTYPE htmlgt

lthtml xmlns=rdquohttpwwww3org1999xhtmlrdquo xmllang=rdquozh-CNrdquo lang=rdquozh-CNrdquogt

ltheadgt

ltmeta http-equiv=rdquoContent-Typerdquo content=rdquotexthtml charset=UTF-7rdquo gt

lttitlegtnodejs index html filelttitlegt

ltheadgt

ltbodygt

34 4 Nodejs基礎

Nodejs中文電子書

lth1gtnodejs index html filelth1gt

ltbodygt

lthtmlgt

另一個為 nodejs程式

var fs = require(rdquofsrdquo)

filename = rdquostaticindexhtmlrdquo

encode = rdquoutf8rdquo

fsreadFile(filename encode function(err file)

consolelog(file)

)

一開始直接載入 le system模組 載入名稱為 fs讀取檔案主要使

用的方法為 readFile裡面以三個參數路徑 ( le path) 編碼方式 (encoding)

回應函式 (callback)路徑必須要設定為靜態 html所在位置才能指定到正確的檔案靜態檔案的編碼方式也必須正確這邊使用靜態檔案的編

碼為 utf8如果編碼設定錯誤nodejs讀取出來檔案結果會使用 byte raw格式輸出如果錯誤編碼格式會導致輸出資料為 byte raw

回應函式中裡面會使用兩個變數error為錯誤資訊如果讀取的檔案不存在或者發生錯誤error數值會是 true如果成功讀取資料 error將會是 falsecontent則是檔案內容資料讀取後將會把資料全數丟到 content這個變數當中

最後程式的輸出結果畫面如下

43 nodejs檔案讀取 35

Nodejs中文電子書

44 nodejs http靜態檔案輸出

前面已經了解如何讀取本地端檔案接下來將配合 http伺服器路由讓每個路由都能夠輸出相對應的靜態 html檔案首先新增加幾個靜態 html檔案

ltDOCTYPE htmlgt

lthtml xmlns=rdquohttpwwww3org1999xhtmlrdquo xmllang=rdquozh-CNrdquo lang=rdquozh-CNrdquogt

ltheadgt

ltmeta http-equiv=rdquoContent-Typerdquo content=rdquotexthtml charset=UTF-7rdquo gt

lttitlegtnodejs index html filelttitlegt

ltheadgt

ltbodygt

lth1gtnodejs index html filelth1gt

ltbodygt

lthtmlgt

ltDOCTYPE htmlgt

lthtml xmlns=rdquohttpwwww3org1999xhtmlrdquo xmllang=rdquozh-CNrdquo lang=rdquozh-CNrdquogt

ltheadgt

ltmeta http-equiv=rdquoContent-Typerdquo content=rdquotexthtml charset=UTF-7rdquo gt

lttitlegtnodejs test html filelttitlegt

ltheadgt

ltbodygt

lth1gtnodejs test html filelth1gt

ltbodygt

lthtmlgt

ltDOCTYPE htmlgt

lthtml xmlns=rdquohttpwwww3org1999xhtmlrdquo xmllang=rdquozh-CNrdquo lang=rdquozh-CNrdquogt

ltheadgt

ltmeta http-equiv=rdquoContent-Typerdquo content=rdquotexthtml charset=UTF-7rdquo gt

lttitlegtnodejs static html filelttitlegt

ltheadgt

ltbodygt

lth1gtnodejs static html filelth1gt

ltbodygt

lthtmlgt

36 4 Nodejs基礎

Nodejs中文電子書

準備一個包含基本路由功能的 http伺服器

var server

ip = rdquo127001rdquo

port = 1337

http = require(rsquohttprsquo)

url = require(rsquourlrsquo)

server = httpcreateServer(function (req res)

var path = urlparse(requrl)

)

serverlisten(port ip)

consolelog(rdquoServer running at httprdquo + ip + rdquordquo + port)

加入 le system 模組使用 readFile 的功能將這一段程式放置於

createServer的回應函式中

fsreadFile(filePath encode function(err file)

)

readFile的回應函式裡面加入頁面輸出讓瀏覽器可以正確讀到檔案在這邊我們設定讀取的檔案為 html 靜態檔案所以 Content-type 設定為texthtml讀取到檔案的內容將會正確輸出成 html靜態檔案

fsreadFile(filePath encode function(err file)

reswriteHead(200 rsquoContent-Typersquo rsquotexthtmlrsquo)

reswrite(file)

resend()

)

到這邊為止基本的程式內容都已經完成剩下一些細節的調整首先

路徑上必須做調整目前的靜態檔案全部都放置於 static資料夾底下設定一個變數來記住資料夾位置

接著將瀏覽器發出要求路徑與資料夾組合讀取正確 html靜態檔案使用者有可能會輸入錯誤路徑所以在讀取檔案的時候要加入錯誤處理

同時回應 404伺服器無法正確回應的 http header格式

加入這些細節的修改一個基本的 http 靜態 html輸出伺服器就完成了完整程式碼如下

44 nodejs http靜態檔案輸出 37

Nodejs中文電子書

var server

ip = rdquo127001rdquo

port = 1337

http = require(rsquohttprsquo)

fs = require(rdquofsrdquo)

folderPath = rdquostaticrdquo

url = require(rsquourlrsquo)

path

filePath

encode = rdquoutf8rdquo

server = httpcreateServer(function (req res)

path = urlparse(requrl)

filePath = folderPath + pathpathname

fsreadFile(filePath encode function(err file)

if (err)

reswriteHead(404 rsquoContent-Typersquo rsquotextplainrsquo)

resend()

return

reswriteHead(200 rsquoContent-Typersquo rsquotextapplicationrsquo)

reswrite(file)

resend()

)

)

serverlisten(port ip)

consolelog(rdquoServer running at httprdquo + ip + rdquordquo + port)

45 nodejs http GET資料擷取

http伺服器中除了路由之外另一個最常使用的方法就是擷取 GET資料本單元將會介紹如何透過基本 http伺服器擷取瀏覽器傳來的要求擷取 GET資料

在 http協定中GET參數都是藉由 URL從瀏覽器發出要求送至伺服器

38 4 Nodejs基礎

Nodejs中文電子書

端基本的傳送網址格式可能如下

http127001testsend=1amptest=2

上面這段網址裡面的 GET參數就是 send而這個變數的數值就為 1如果想要在 http伺服器取得 GET資料需要在瀏覽器給予的要求 (request)做處理

首先需要載入 query string這個模組這個模組主要是用來將字串資料

過濾後轉換成javascript物件

qs = require(rsquoquerystringrsquo)

接著在第一階段利用 url模組過濾瀏覽器發出的 URL資料後將回應的物件裡面的 query這個變數是一個字串值資料過濾後如下

send=1amptest=2

透過 query string使用 parse這個方法將資料轉換成 javascript物件就表示 GET的資料已經被伺服器端正式擷取下來

path = urlparse(requrl)

parameter = qsparse(pathquery)

整個 nodejs http GET參數完整擷取程式碼如下

var server

ip = rdquo127001rdquo

port = 1337

http = require(rsquohttprsquo)

qs = require(rsquoquerystringrsquo)

url = require(rsquourlrsquo)

server = httpcreateServer(function (req res)

var path = urlparse(requrl)

parameter = qsparse(pathquery)

consoledir(parameter)

reswriteHead(200 rsquoContent-Typersquo rsquotextplainrsquo)

reswrite(rsquoBrowser test GET parameternrsquo)

resend()

)

45 nodejs http GET資料擷取 39

Nodejs中文電子書

serverlisten(port ip)

consolelog(rdquoServer running at httprdquo + ip + rdquordquo + port)

程式運作之後由瀏覽器輸入要求網址之後nodejs伺服器端回應資料為

Server running at http1270011337

send rsquo1rsquo test rsquo1rsquo

46 本章結語

前面所解說的部份一大部分主要是處理 http伺服器基本問題雖然在某些部分有牽扯到 http 伺服器基本運作原理主要還是希望可以藉由這些基本範例練習 nodejs練習回應函式與語法串接的特點習慣編寫javascript風格程式當然直接這樣開發 nodejs是非常辛苦的接下來在模組實戰開發的部份將會介紹特定的模組一步一步帶領各位從無到有進行

nodejs應用程式開發

40 4 Nodejs基礎

5

NPM套件管理工具

npm全名為 Node Package Manager是 Nodejs的套件(package)管理工具類似 Perl的 ppm或 PHP的 PEAR等安裝 npm後使用npm install

module name指令即可安裝新套件維護管理套件的工作會更加輕鬆

npm可以讓Nodejs的開發者直接利用擴充線上的套件庫(packagesregistry)加速軟體專案的開發npm提供很友善的搜尋功能可以快速找到安裝需要的套件當這些套件發行新版本時npm也可以協助開發者自動更新這些套件

npm不僅可用於安裝新的套件它也支援搜尋列出已安裝模組及更新的功能

51 安裝 NPM

Nodejs在 063版本開始內建 npm讀者安裝的版本若是此版本或更新的版本就可以略過以下安裝說明

若要檢查 npm是否正確安裝可以使用以下的指令

npm -v

41

Nodejs中文電子書

執行結果說明

若 npm正確安裝執行 npm -v將會看到類似 110-2的版本訊息

若讀者安裝的 Nodejs版本比較舊或是有興趣嘗試自己動手安裝 npm工具則可以參考以下的說明

安裝於Windows系統

Nodejs for Windows 於 062 版開始內建 npm使用 nodejsorg 官方提供的安裝程式不需要進一步的設定就可以立即使用 npm指令對於Windows的開發者來說大幅降低環境設定的問題與門檻

除了使用 Nodejs內建的 npm讀者也可以從 npm官方提供的以下網址

httpnpmjsorgdist

這是由 npm提供的 Fancy Windows Install版本請下載壓縮檔(例如npm-110-3zip)並將壓縮檔內容解壓縮至 Nodejs的安裝路徑(例如CProgram Filesnodejs)

解壓縮後在 Nodejs的安裝路徑下應該有以下的檔案及資料夾

bull npmcmd(檔案)

bull node modules(資料夾)

安裝於 Linux系統

Ubuntu Linux的使用者可以加入 NPM Unoffcial PPA這個 repository即可使用 apt-get完成 npm安裝

42 5 NPM套件管理工具

Nodejs中文電子書

Ubuntu Linux使用 apt-get安裝 npm

sudo apt-get install python-software-properties

sudo add-apt-repository ppagias-kay-leenpm

sudo apt-get update

sudo apt-get install npm

npm官方提供的安裝程式 installsh可以適用於大多數的 Linux系統使用這個安裝程式請先確認

1 系統已安裝 curl工具(請使用 curl --version查看版本訊息)

2 已安裝 Nodejs並且 PATH正確設置

3 Nodejs的版本必須大於 04x

以下為 npm提供的安裝指令

curl httpnpmjsorginstallsh | sh

安裝成功會看到如下訊息

installsh安裝成功的訊息

npm10105 homeUSERNAMElocalnodelibnode_modulesnpm

It worked

安裝於Mac OS X

建議採用與 Nodejs相同的方式進行 npm的安裝例如使用MacPorts安裝 Nodejs就同樣使用MacPorts安裝 npm這樣對日後的維護才會更方便容易

使用 MacPorts安裝 npm是本書比較建議的方式它可以讓 npm的安裝移除及更新工作自動化將會幫助開發者節省寶貴時間

51 安裝 NPM 43

Nodejs中文電子書

安裝MacPorts的提示

在MacPorts網站可以取得 OS X系統版本對應的安裝程式(例如 106或 107)httpwwwmacportsorg安裝過程會詢問系統管理者密碼使用預設的選項完成安裝即可安

裝MacPorts之後在終端機執行 port -v將會看到MacPorts的版本訊息

安裝 npm之前先更新MacPorts的套件清單以確保安裝的 npm是最新版本

sudo port -d selfupdate

接著安裝 npm

sudo port install npm

若讀者的 Nodejs並非使用MacPorts安裝則不建議使用MacPorts安裝npm因為 MacPorts會自動檢查並安裝相依套件而 npm相依 nodejs所以MacPorts也會一併將 nodejs套件安裝造成先前讀者使用其它方式安裝的 nodejs被覆蓋

讀者可以先使用 MacPorts安裝 curl(sudo port install curl)

再參考 Linux的 installsh安裝方式即可使用 npm官方提供的安裝程式

NPM安裝後測試

npm是指令列工具(command-line tool)使用時請先打開系統的文字終端機工具

測試 npm安裝與設定是否正確請輸入指令如下

npm -v

或是

npm --version

如果 npm已經正確安裝設定就會顯示版本訊息

44 5 NPM套件管理工具

Nodejs中文電子書

執行結果(範例)

110-2

52 使用 NPM安裝套件

npm目前擁有超過 6000種套件(packages)可以在 npm registry使用關鍵字搜尋套件

httpsearchnpmjsorg

舉例來說在關鍵字欄位輸入「coffee-script」下方的清單就會自動列出包含 coffee-script關鍵字的套件

接著我們回到終端機模式的操作npm的指令工具本身就可以完成套

件搜尋的任務

例如以下的指令同樣可以找出 coffee-script相關套件

npm search coffee-script

以下是搜尋結果的參考畫面

52 使用 NPM安裝套件 45

Nodejs中文電子書

找到需要的套件後(例如 express)即可使用以下指令安裝

npm install coffee-script

值得注意的一點是使用 npm install會將指定的套件安裝在工

作目錄(Working Directory)的 node modules資料夾下

以Windows為例如果執行 npm install的目錄位於

Cproject1

那麼 npm將會自動建立一個 node modules的子目錄(如果不存在)

Cproject1node modules

並且將下載的套件放置於這個子目錄例如

Cproject1node modulescoffee-script

這個設計讓專案可以個別管理相依的套件並且可以在專案佈署或發

行時將這些套件(位於 node modules)一併打包方便其它專案的使用者不必再重新下載套件

這個 npm install的預設安裝模式為 local(本地)只會變更當前專案的資料夾不會影響系統

另一種安裝模式稱為 global(全域)這種模式會將套件安裝到系統資

料夾也就是 npm安裝路徑的 node modules資料夾例如

CProgram Filesnodejsnode modules

46 5 NPM套件管理工具

Nodejs中文電子書

是否要使用全域安裝可以依照套件是否提供新指令來判斷舉例來說express 套件提供 express 這個指令而 coffee-script 則提供 coffee

指令

在 local 安 裝 模 式 中這 些 指 令 的 程 式 檔 案會 被 安 裝 到node modules的 bin這個隱藏資料夾下除非將bin的路徑加入 PATH環境變數否則要執行這些指令將會相當不便

為了方便指令的執行我們可以在 npm install 加上 -g 或 --

global參數啟用 global安裝模式例如

npm install -g coffee-script

npm install -g express

使用 global安裝模式需要注意執行權限的問題若權限不足可能會出現類似以下的錯誤訊息

npm ERR Error EACCES permission denied rsquorsquo

npm ERR

npm ERR Please try running this command again as rootAdministrator

要獲得足夠得執行權限請參考以下說明

bull Windows 7 或 2008 以上在「命令提示字元」的捷徑按右鍵選擇「以系統管理員身分執行」執行 npm指令時就會具有 Administrator身分

bull Mac OS X或 Linux系統可以使用 sudo指令例如

sudo npm install -g express

bull Linux系統可以使用 root權限登入或是以「sudo su -」切換成 root身分(使用 root權限操作系統相當危險因此並不建議使用這種方式)

若加上 -g參數使用 npm install -g coffee-script完成安裝

後就可以在終端機執行 coffee指令例如

coffee -v

52 使用 NPM安裝套件 47

Nodejs中文電子書

執行結果(範例)

CoffeeScript version 120

53 套件的更新及維護

除了前一節說明的 search 及 install 用法npm 還提供其他許多指令(commands)

使用 npm help可以查詢可用的指令

npm help

執行結果(部分)

where ltcommandgt is one of

adduser apihelp author bin bugs c cache completion

config deprecate docs edit explore faq find get

help help-search home i info init install la link

list ll ln login ls outdated owner pack prefix

prune publish r rb rebuild remove restart rm root

run-script s se search set show star start stop

submodule tag test un uninstall unlink unpublish

unstar up update version view whoami

使用 npm help command可以查詢指令的詳細用法例如

npm help list

接下來本節要介紹開發過程常用的 npm指令

使用 list可以列出已安裝套件

npm list

48 5 NPM套件管理工具

Nodejs中文電子書

執行結果(範例)

-- coffee-script120

- express256

- connect185

| -- formidable108

-- mime124

-- mkdirp007

-- qs041

檢視某個套件的詳細資訊例如

npm show express

升級所有套件(如果該套件已發佈更新版本)

npm update

升級指定的套件

npm update express

移除指定的套件

npm uninstall express

54 使用 packagejson

對於正式的 Nodejs專案可以建立一個命名為 packagejson的設

定檔(純文字格式)檔案內容參考範例如下

54 使用 packagejson 49

Nodejs中文電子書

packagejson(範例)

rdquonamerdquo rdquoapplication-namerdquo

rdquoversionrdquo rdquo001rdquo

rdquoprivaterdquo true

rdquodependenciesrdquo

rdquoexpressrdquo rdquo255rdquo

rdquocoffee-scriptrdquo rdquolatestrdquo

rdquomongooserdquo rdquogt= 253rdquo

其中 name與 version依照專案的需求設置

需要注意的是 dependencies的設定它用於指定專案相依的套件名

稱及版本

bull rdquoexpressrdquo rdquo255rdquo

代表此專案相依版本 255的 express套件

bull rdquocoffee-scriptrdquo rdquolatestrdquo

使用最新版的 coffee-script套件(每次更新都會檢查新版)

bull rdquomongooserdquo rdquogt= 253rdquo

使用版本大於 253的 mongoose套件

假設某個套件的新版可能造成專案無法正常運作就必須指定套件的

版本避免專案的程式碼來不及更新以相容新版套件通常在開發初期的

專案需要盡可能維持新套件的相容性(以取得套件的更新或修正)可以

用「gt=」設定最低相容的版本或是使用「latest」設定永遠保持最新

套件

50 5 NPM套件管理工具

6

Express介紹

在前面的 nodejs基礎當中介紹許多許多開設 http的使用方法及介紹以及許多基本的 nodejs基本應用

接下來要介紹一個套件稱為 express [Express](httpexpressjscom)這個套件主要幫忙解決許多 nodejs http server所需要的基本服務讓開發 httpservice變得更為容易不需要像之前需要透過層層模組(module)才有辦法開始編寫自己的程式

這個套件是由 TJ Holowaychuk 製作而成的套件裡面包含基本的路由處理 (route)http資料處理(GETPOSTPUT)另外還與樣板套件(jshtml template engine)搭配同時也可以處理許多複雜化的問題

61 Express安裝

安裝方式十分簡單只要透過之前介紹的 NPM就可以使用簡單的指令安裝指令如下

這邊建議需要將此套件安裝成為全域模組方便日後使用

51

Nodejs中文電子書

62 Express基本操作

express的使用也十分簡單先來建立一個基本的 hello world

var app = require(rsquoexpressrsquo)createServer()

port = 1337

applisten(port)

appget(rsquorsquo function(req res)

ressend(rsquohello worldrsquo)

)

consolelog(rsquostart express servernrsquo)

可以從上面的程式碼發現基本操作與 nodejs http的建立方式沒有太大差異主要差在當我們設定路由時可以直接透過 appget方式設定回應與接受方式

63 Express路由處理

Express對於 http服務上有許多包裝讓開發者使用及設定上更為方便例如有幾個路由設定那我們就統一藉由 appget來處理

Create http server

appget(rsquorsquo function(req res)

ressend(rsquohello worldrsquo)

)

appget(rsquotestrsquo function(req res)

ressend(rsquotest renderrsquo)

)

appget(rsquouserrsquo function(req res)

ressend(rsquouser pagersquo)

)

52 6 Express介紹

Nodejs中文電子書

如上面的程式碼所表示appget可以帶入兩個參數第一個是路徑名稱設定第二個為回應函式 (call back function)回應函式裡面就如同之前的 createServer方法裡面包含 requestresponse兩個物件可供使用使用者就可以透過瀏覽器輸入不同的 url切換到不同的頁面顯示不同的結果

路由設定上也有基本的配對方式讓使用者從瀏覽器輸入的網址可以

是一個變數只要符合型態就可以有對應的頁面產出例如

Create http server

appget(rsquouseridrsquo function(req res)

ressend(rsquouser rsquo + reqparamsid)

)

appget(rsquonumberrsquo function(req res)

ressend(rsquonumber rsquo + reqparamsnumber)

)

裡 面 使 用 到number 從 網 址 輸 入 之 後 就 可 以 直 接 使 用reqparamsnumber 取得所輸入的資料變成 url 參數使用當然前面也是可以加上路徑的設定userid在瀏覽器上路徑必須符合userxxx透

過 reqparamsid就可以取到 xxx這個字串值

另外express參數處理也提供了路由參數配對處理也可以透過正規表示法作為參數設定

var app = require(rsquoexpressrsquo)createServer()

port = 1337

applisten(port)

appget(^ip((d23)((d23))((d23))((d23))) function(req res)

ressend(reqparams)

)

上面程式碼可以發現後面路由設定的型態是正規表示法裡面設定

格式為ip之後必須要加上 ip型態才會符合資料格式同時取得 ip資料已經由正規表示法將資料做分群因此可以取得 ip的四個數字

此程式執行之後可以透過瀏覽器測試輸入網址為 localhost3000ip25525510010可以從頁面獲得資料

63 Express路由處理 53

Nodejs中文電子書

此章節全部範例程式碼如下

overview

author Caesar Chi

blog clonnblogspotcom

version 20120226

create server

var app = require(rsquoexpressrsquo)createServer()

port = 1337

applisten(port)

normal style

appget(rsquorsquo function(req res)

ressend(rsquohello worldrsquo)

)

appget(rsquotestrsquo function(req res)

ressend(rsquotest renderrsquo)

)

parameter style

appget(rsquouseridrsquo function(req res)

ressend(rsquouser rsquo + reqparamsid)

)

appget(rsquonumberrsquo function(req res)

ressend(rsquonumber rsquo + reqparamsnumber)

)

REGX style

appget(^ip((d23)((d23))((d23))) function(req res)

ressend(reqparams)

)

appget(rsquorsquo function(req res)

ressend(rsquoPage not foundrsquo 404)

54 6 Express介紹

Nodejs中文電子書

)

consolelog(rsquostart express servernrsquo)

64 Express middleware

Express 裡面有一個十分好用的應用概念稱為 middleware可以透過middleware做出複雜的效果同時上面也有介紹 next方法參數傳遞就是靠 middleware的概念來傳遞參數讓開發者可以明確的控制程式邏輯

create http server

appuse(expressbodyParser())

appuse(expressmethodOverride())

appuse(expresssession())

上面都是一種 middleware的使用方式透過 appuse方式裡面載入函式執行方法回應函式會包含三個基本參數responserequestnext其中next表示下一個 middleware執行函式同時會自動將預設三個參數繼續帶往下個函式執行底下有個實驗

上面的片段程式執行後開啟瀏覽器連結上 localhost1337會發現伺服器回應結果順序如下

first middle ware

second middle ware

execute middle ware

end middleware function

從上面的結果可以得知剛才設定的 middleware都生效了在 appuse設定的 middleware是所有 url皆會執行方法如果有指定特定方法就可以使用 appget的 middleware設定在 appget函式的第二個參數就可以帶入函式或者是匿名函式只要函式裡面最後會接受 request response next這三個參數同時也有正確指定 next函式的執行時機最後都會執行到最後一個方法當然開發者也可以評估程式邏輯要執行到哪一個階段讓邏輯

可以更為分明

64 Express middleware 55

Nodejs中文電子書

65 Express路由應用

在實際開發上可能會遇到需要使用參數等方式混和變數一起使用

express裡面提供了一個很棒的處理方法 appall這個方式可以先採用基本路由配對再將設定為每個不同的處理方式開發者可以透過這個方式簡

化自己的程式邏輯

overview

author

version 20120226

create server

var app = require(rsquoexpressrsquo)createServer()

port = 1337

users = [

name rsquoClonnrsquo

name rsquoChirsquo

]

applisten(port)

appall(rsquouseridoprsquo function(req res next)

requser = users[reqparamsid]

if (requser)

next()

else

next(new Error(rsquocannot find user rsquo + reqparamsid))

)

appget(rsquouseridrsquo function(req res)

ressend(rsquoviewing rsquo + requsername)

)

appget(rsquouserideditrsquo function(req res)

ressend(rsquoediting rsquo + requsername)

)

56 6 Express介紹

Nodejs中文電子書

appget(rsquouseriddeletersquo function(req res)

ressend(rsquodeleting rsquo + requsername)

)

appget(rsquorsquo function(req res)

ressend(rsquoPage not foundrsquo 404)

)

consolelog(rsquostart express servernrsquo)

內部宣告一組預設的使用者分別給予名稱設定藉由 appall這個方法可以先將路由雛形建立再接下來設定 appget的路徑格式只要符合格式就會分配進入對應的方法中像上面的程式當中如果使用者輸入路徑為user0除了執行 appall程式之後執行 next方法就會對應到路徑設定為userid的這個方法當中如果使用者輸入路徑為user0edit就會執行到useridedit的對應方法

66 Express GET應用範例

我們準備一個使用 GET方法傳送資料的表單

ltDOCTYPE htmlgt

lthtmlgt

ltheadgt

ltmeta http-equiv=rdquoContent-Typerdquo content=rdquotexthtml charset=utf8rdquogt

lttitlegtNodejs 菜鳥筆記 (1)lttitlegt

ltlink rel=rdquostylesheetrdquo href=rdquocssstylecssrdquo type=rdquotextcssrdquo media=rdquoallrdquo gt

ltheadgt

ltbodygt

lth1gtNodejs 菜鳥筆記-註冊lth1gt

ltform id=rdquosignuprdquo method=rdquoGETrdquo action=rdquohttplocalhost3000Signuprdquogt

ltlabelgt 使用者名稱ltlabelgtltinput type=rdquotextrdquo id=rdquousernamerdquo name=rdquousernamerdquo gtltbrgt

ltlabelgt 電子郵件ltlabelgtltinput type=rdquotextrdquo id=rdquoemailrdquo name=rdquoemailrdquo gtltbrgt

ltinput type=rdquosubmitrdquo value=rdquo 註冊我的帳號rdquo gtltbrgt

ltformgt

ltbodygt

lthtmlgt

66 Express GET應用範例 57

Nodejs中文電子書

這個表單沒有什麼特別的地方我們只需要看第 9 行form 使用的method是 GET然後 action是rdquohttplocalhost3000Signupldquo等一下我們要來撰寫Signup這個 URL Path的處理程式

處理 Signup行為

我們知道所謂的 GET方法會透過 URL來把表單的值給帶過去以上面的表單來說到時候 URL會以這樣的形式傳遞

httplocalhost3000Signupusername=xxxampemail=xxx

所以要能處理這樣的資料必須有以下功能

bull 解析 URL

bull 辨別動作是 Signup

bull 解析出 username和 email

一旦能取得 username和 email的值程式就能加以應用了

處理 Signup的程式碼雛形

load module

var url = require(rsquourlrsquo)

urlData = urlparse(requrltrue)

action = urlDatapathname

reswriteHead(200 rdquoContent-Typerdquordquotexthtml charset=utf-8rdquo)

if (action === rdquoSignuprdquo)

user = urlDataquery

resend(rdquolth1gtrdquo + userusername + rdquo 歡迎您的加入 lth1gtltpgt 我們已經將會員啟用信寄至rdquo + useremail + rdquoltpgtrdquo)

首先需要加載 url module它是用來協助我們解析 URL的模組接著使用 urlparse方法第一個傳入 url字串變數也就是 requrl另外第二個參數的用意是設為 ture則引進 querystring模組來協助處理預設是 false它影響到的是 urlDataquery設為 true會傳回物件不然就只是一般的字串urlparse會將字串內容整理成一個物件我們把它指定給 urlData

action變數作為記錄 pathname這是我們稍後要來判斷目前網頁的動作是什麼接著先將 html表頭資訊 (Header)準備好再來判斷路徑邏輯如

58 6 Express介紹

Nodejs中文電子書

果是 Signup這個動作就把 urlDataquery裡的資料指定給 user然後輸出userusername和 useremail把使用者從表單註冊的資料顯示於頁面中

最後進行程式測試啟動 nodejs主程式之後開啟瀏覽器就會看到表單填寫完畢按下送出就可以看到結果了

完整 nodejs程式碼如下

var http = require(rsquohttprsquo)

url = require(rsquourlrsquo)

fs = require(rdquofsrdquo)

server

server = httpcreateServer(function (reqres)

var urlData

encode = rdquoutf8rdquo

filePath = rdquoviewexpress_get_example_formhtmlrdquo

action

urlData = urlparse(requrltrue)

action = urlDatapathname

reswriteHead(200 rdquoContent-Typerdquordquotexthtml charset=utf-8rdquo)

if (action === rdquoSignuprdquo)

user = urlDataquery

resend(rdquolth1gtrdquo + userusername + rdquo 歡迎您的加入 lth1gtltpgt 我們已經將會員啟用信寄至rdquo + useremail + rdquoltpgtrdquo)

else

fsreadFile(filePath encode function(err file)

reswrite(file)

resend()

)

)

serverlisten(3000)

consolelog(rsquoServer 跑起來了現在時間是rsquo + new Date())

66 Express GET應用範例 59

Nodejs中文電子書

67 Express POST應用範例

一開始準備基本的 html表單傳送內容以 POST方式form的 action屬性設定為 POST其餘 html內容與前一個範例應用相同

ltDOCTYPE htmlgt

lthtmlgt

ltheadgt

ltmeta http-equiv=rdquoContent-Typerdquo content=rdquotexthtml charset=utf8rdquogt

lttitlegtNodejs 菜鳥筆記 (1)lttitlegt

ltlink rel=rdquostylesheetrdquo href=rdquocssstylecssrdquo type=rdquotextcssrdquo media=rdquoallrdquo gt

ltheadgt

ltbodygt

lth1gtNodejs 菜鳥筆記-註冊lth1gt

ltform id=rdquosignuprdquo method=rdquoPOSTrdquo action=rdquohttplocalhost3000Signuprdquogt

ltlabelgt 使用者名稱ltlabelgtltinput type=rdquotextrdquo id=rdquousernamerdquo name=rdquousernamerdquo gtltbrgt

ltlabelgt 電子郵件ltlabelgtltinput type=rdquotextrdquo id=rdquoemailrdquo name=rdquoemailrdquo gtltbrgt

ltinput type=rdquosubmitrdquo value=rdquo 註冊我的帳號rdquo gtltbrgt

ltformgt

ltbodygt

lthtmlgt

nodejs的程式處理邏輯與前面 GET範例類似部分程式碼如下

qs = require(rsquoquerystringrsquo)

if (action === rdquoSignuprdquo)

formData = rsquorsquo

reqon(rdquodatardquo function (data)

formData += data

)

reqon(rdquoendrdquo function ()

user = qsparse(formData)

resend(rdquolth1gtrdquo + userusername + rdquo 歡迎您的加入 lth1gtltpgt 我們已經將會員啟用信寄至rdquo + useremail + rdquoltpgtrdquo)

)

60 6 Express介紹

Nodejs中文電子書

主要加入了rsquoquerystringrsquo這個moduel方便我們等一下解析由表單 POST回來的資料另外加入一個 formData的變數用來搜集待等一下表單回傳的資料前面的 GET範例我們只從 req拿出 url的資料這次要在利用req身上的事件處理

JavaScript 在訂閱事件時使用 addEventListener而 nodejs 使用的則是on這邊加上了監聽 data的事件會在瀏覽器傳送資料到Web Server時被執行參數是它所接收到的資料型態是字串

接著再增加 end的事件當瀏覽器的請求事件結束時它就會動作

由於瀏覽器使用 POST在上傳資料時會將資料一塊塊地上傳因為我們在監聽 data事件時透過 formData變數將它累加起來lt不過由於我們

上傳的資料很少一次就結束不過如果日後需要傳的是資料比較大的檔

案這個累加動作就很重要

當資料傳完就進到 end 事件中會用到 qsparse 來解析 formDataformData的內容是字串內容是

username=wordsmithampemail=wordsmith40somewhere

而 qsparse可以幫我們把這個 querystring轉成物件的格式也就是

username=wordsmithampemail=wordsmith40somewhere

一旦轉成物件並指定給 user之後其他的事情就和 GET方法時操作的一樣寫 response的表頭將內容回傳並將 userusername和 useremail代入到內容中

修改完成後接著執行 nodejs程式啟動 web server開啟瀏覽器進入表單測試看看POST的方式能否順利運作

完整程式碼如下

var http = require(rsquohttprsquo)

url = require(rsquourlrsquo)

fs = require(rdquofsrdquo)

qs = require(rsquoquerystringrsquo)

server

server = httpcreateServer(function (reqres)

var urlData

67 Express POST應用範例 61

Nodejs中文電子書

encode = rdquoutf8rdquo

filePath = rdquoviewexpress_post_example_formhtmlrdquo

formData

action

urlData = urlparse(requrltrue)

action = urlDatapathname

reswriteHead(200 rdquoContent-Typerdquordquotexthtml charset=utf-8rdquo)

if (action === rdquoSignuprdquo)

formData = rsquorsquo

reqon(rdquodatardquo function (data)

formData += data

)

reqon(rdquoendrdquo function ()

user = qsparse(formData)

resend(rdquolth1gtrdquo + userusername + rdquo 歡迎您的加入 lth1gtltpgt 我們已經將會員啟用信寄至rdquo + useremail + rdquoltpgtrdquo)

)

else

fsreadFile(filePath encode function(err file)

reswrite(file)

resend()

)

)

serverlisten(3000)

consolelog(rsquoServer 跑起來了現在時間是rsquo + new Date())

62 6 Express介紹

Nodejs中文電子書

68 Express AJAX應用範例

在 Nodejs要使用 Ajax傳送資料並且與之互動在接受資料的部份沒有太大的差別client端不是用 GET就是用 POST來傳資料重點在處理完後用 JSON格式回傳當然 Ajax不見得只傳 JSON格式有時是回傳一段 HTML碼不過後者對伺服器來說基本上就和前兩篇沒有差別了所以我們還是以回傳 JSON做為這一回的主題

這一回其實大多數的工作都會落在前端 Ajax上面前端要負責發送與接收資料並在接收資料後撤掉原先發送資料的表單並將取得的資料

改成 HTML格式之後放上頁面

首先先準備 HTML靜態頁面資料

ltDOCTYPE htmlgt

lthtmlgt

ltheadgt

lttitlegtNodejs 菜鳥筆記 (3)lttitlegt

ltlink rel=rdquostylesheetrdquo href=rdquocssstylecssrdquo type=rdquotextcssrdquo media=rdquoallrdquo gt

ltscript src=rdquohttpsajaxgoogleapiscomajaxlibsjquery171jqueryminjsrdquogtltscriptgt

ltheadgt

ltbodygt

lth1gtNodejs 菜鳥筆記-註冊 (用 Ajax)lth1gt

ltform id=rdquosignuprdquo action=rdquoSignuprdquo method=rdquoPOSTrdquogt

ltlabelgt 使用者名稱ltlabelgtltinput type=rdquotextrdquo id=rdquousernamerdquo name=rdquousernamerdquo gtltbrgt

ltlabelgt 電子郵件ltlabelgtltinput type=rdquotextrdquo id=rdquoemailrdquo name=rdquoemailrdquo gtltbrgt

ltinput type=rdquosubmitrdquo value=rdquo 註冊我的帳號rdquo gtltbrgt

ltformgt

ltdiv id=rdquoresultrdquogt

ltdivgt

ltscript type=rdquotextjavascriptrdquogt

$(function()$(rsquosignup input[type=rdquosubmitrdquo]rsquo)click(function(e)

var user =

userusername = $(rdquousernamerdquo)val()useremail = $(rdquoemailrdquo)val()

$post(rdquoSignuprdquouserfunction(data)greet(data)

)

68 Express AJAX應用範例 63

Nodejs中文電子書

epreventDefault()

)

var greet = function(msg)

var resultNode = $(rsquoresultrsquo)greeting = $(rsquolth2gtHirsquo+msgusername+rsquo 你的會員 id 是rsquo+ msgid +rsquo 我們會將會員啟動信寄至rsquo+msgemail+rsquolth2gtrsquo)

$(rsquosignuprsquo)hide()resultNodehtml(rdquordquo)

resultNodeappend(greeting)

)

ltscriptgt

ltbodygt

lthtmlgt

HTML頁面上準備了一個表單用來傳送註冊資料接著直接引用了Google CDN來載入 jQuery用來幫我們處理 Ajax的工作這次要傳送和接收的工作很大的變動都在 HTML頁面上的 JavaScript當中我們要做的事有 (相關 jQuery處理這邊不多做贅述指提起主要功能解說)

bull 用 jQUery取得 submit按鈕綁定它的 click動作

bull 取得表單 username和 email的值存放在 user這個物件中

bull 用 jQuery的 $post方法將 user的資料傳到 Server

bull 一旦成功取得資料後透過 greet這個 function組成回報給 user的訊息

bull 清空原本給使用者填資料的表單

bull 將 Server回傳的 usernameemail和 id這 3個資料組成回應的訊息

bull 將訊息放到原本表單的位置

經過以上的處理後一個 Ajax的表單的基本功能已經完成

接著進行 nodejs主要程式的編輯部分程式碼如下

var fs = require(rdquofsrdquo)

64 6 Express介紹

Nodejs中文電子書

qs = require(rsquoquerystringrsquo)

if (action === rdquoSignuprdquo)

formData = rsquorsquo

reqon(rdquodatardquo function (data)

formData += data

)

reqon(rdquoendrdquo function ()

var msg

user = qsparse(formData)

userid = rdquo123456rdquo

msg = JSONstringify(user)

reswriteHead(200 rdquoContent-Typerdquordquoapplicationjson charset=utf-8rdquordquoContent-Lengthrdquomsglength)

resend(msg)

)

這裡的程式和前面 POST範例基本上大同小異差別在

bull 幫 user的資料加上 id隨意存放一些文字進去讓 Server回傳的資料多於 Client端傳上來的不然會覺得 Server都沒做事

bull 增加了 msg 這個變數存放將 user 物件 JSON 文字化的結果JSONstringify這個轉換函式是 V8引擎所提供的如果你好奇的話

bull 大重點來了我們要告訴 Client端這次回傳的資料格式是 JSON所在 Content-type和 Content-Length要提供給 Client

Server很輕鬆就完成任務了最後進行程式測試啟動 nodejs主程式之後開啟瀏覽器就會看到表單填寫完畢按下送出就可以看到結果了

最後 nodejs本篇範例程式碼如下

var http = require(rsquohttprsquo)

url = require(rsquourlrsquo)

fs = require(rdquofsrdquo)

qs = require(rsquoquerystringrsquo)

server

68 Express AJAX應用範例 65

Nodejs中文電子書

server = httpcreateServer(function (reqres)

var urlData

encode = rdquoutf8rdquo

filePath = rdquoviewexpress_ajax_example_formhtmlrdquo

formData

action

urlData = urlparse(requrltrue)

action = urlDatapathname

if (action === rdquoSignuprdquo)

formData = rsquorsquo

reqon(rdquodatardquo function (data)

formData += data

)

reqon(rdquoendrdquo function ()

var msg

user = qsparse(formData)

userid = rdquo123456rdquo

msg = JSONstringify(user)

reswriteHead(200 rdquoContent-TyperdquordquoapplicationjsonrdquordquoContent-Lengthrdquomsglength)

resend(msg)

)

else

fsreadFile(filePath encode function(err file)

reswriteHead(200 rdquoContent-Typerdquordquotexthtml charset=utf-8rdquo)

reswrite(file)

resend()

)

)

serverlisten(3000)

66 6 Express介紹

Nodejs中文電子書

consolelog(rsquoServer 跑起來了現在時間是rsquo + new Date())

69 原始資料提供

bull [NodeJS初學者筆記 (1)-用 GET傳送資料] (httpithelpithomecomtwquestion10087402)

bull [NodeJS初學者筆記 (2)-用 POST傳送資料] (httpithelpithomecomtwquestion10087489)

bull [NodeJS 初學者筆記 (3)-用 Ajax 傳送資料] (httpithelpithomecomtwquestion10087627)

69 原始資料提供 67

Nodejs中文電子書

68 6 Express介紹

7

CoffeeScript

bull Spine

bull Hem

bull Spineapp

Letrsquos Have a Cup of CoffeeScript

bull es5-shim ECMAScript 5 compatibility shims for legacy JavaScript engines

ESnext

node-iform - A middleware for node-validator httpsgithubcomguileennode-iform

69

Nodejs中文電子書

70 7 CoffeeScript

8

製作一個 Hubot的 Plurk Adapter

81 應用事項提醒

此應用範例以CoffeeScript編寫主要程式架構採用Github釋放的Hubot專案以下有幾個主要注意事項請讀者注意

bull Hubot 一開始的架構除了內建的兩個 Adapter 之外其餘都要以Nodejs的Module方式才能運作

bull Hubot的 binhubot裡面寫著 npm install所以不管你怎麼改原始碼也不能改變第一點的狀況

其實上述都在討論同一件事情NodeJS的模組而且模組不能設定相對路徑之類的來安裝一定要包裝成 npm相符和格式才有辦法正確執行

82 建立 Adapter

首先需要準備兩個檔案

bull packagejson

bull plurkcoffee

71

Nodejs中文電子書

有這兩個就足以變成 NodeJS的模組了在開始 Coding之前先來設定一下 packagejson相依性

rdquonamerdquo rdquohubot-plurkrdquo

rdquoversionrdquo rdquo011rdquo

rdquomainrdquo rdquoplurkrdquo

rdquodependenciesrdquo

rdquohubotrdquo rdquogt=205rdquo

rdquooauthrdquo rdquordquo

rdquocronrdquordquordquo

這邊會使用到NodeJS的 cron模組(Module)而登入 Plurk需要OAuth標準授權才行所以也需要加載 OAuth模組

注意Hubot在讀取非內建模組時會自動在前面加上 hubot-的前置

83 建立 Robot跟 API

本篇應用基本上程式碼大部分參考 Hubot - Twitter Adapter來製作主要差異只有在使用 OAuth這個模組Twitter有 Streaming可用而 Plurk則得用 Comet方式來達到即時讀取

plurkcoffee程式碼

Robot = require(rdquohubotrdquo)robot()

Adapter = require(rdquohubtordquo)adapter()

EventEmitter = require(rdquoeventsrdquo)EventEmitter

oauth = require(rdquooauthrdquo)

cronJob = require(rdquocronrdquo)CronJob

class Plurk exntends Adapter

class PlurkStreaming exnteds EventEmitter

72 8 製作一個 Hubot的 Plurk Adapter

Nodejs中文電子書

先弄個基本架構主要 Class皆參考 Twitter Adapter命名方式接著再將 Plurk這個 Class增加幾個Method基本上只要有 run send reply就夠了而 run用來做初始化的部分

class Plurk entends Adapter

send (plurk id strings⋯) -gt

reply (plurk id strings⋯) -gt

run -gt

看起來有點東西接著來處理主要的 Plurk API結合部分

class PlurkStreaming extends EventEmitter

consuctor (options) -gt

plurk (callback) -gt

觀察河道

getChannel -gt

取得 Comet 網址

reply (plurk id message) -gt

回噗

acceptFriends -gt

接受好友

get (path callback) -gt

GET 請求

post (path body callback)-gt

POST 請求(其實是裝飾)

request (method path body callback)-gt

主要的 OAuth 請求

comet (server callback)-gt

噗浪的 Comet 傳回是 JavaScript Callback 要另外處理後才會變成 JSON

然後把注意力集中到 constructor上先把建構子弄好

constructor (options) -gt

super()

if optionskey and optionssecret and optionstoken and optionstoken secret

key = optionskey

secret = optionssecret

token = optionstoken

token secret = optionstoken secret

83 建立 Robot跟 API 73

Nodejs中文電子書

建立 OAuth 連接

consumer = new oauthOAuth(

rdquohttpwwwplurkcomOAuthrequest tokenrdquo

rdquohttpwwwplurkcomOAuthaccess tokenrdquo

key

secret

rdquo10rdquo

rdquohttpwwwplurkcomOAuthauthorizerdquo

rdquoHMAC-SHA1rdquo

)

domain = rdquowwwplurkcomrdquo

初始化取得 Comet 網址

do getChannel

else

throw new Error(rdquo 參數不足需要 Key Secret Token Token Secretrdquo)

接著來處理 request這個 method

request (method path body callback) -gt

記錄一下這次的 Request

consolelog(rdquohttpdomainpathrdquo)

Callback 這邊先不丟進去要用另一種方式處理

request = consumerget(rdquohttpdomainpathrdquo token token secret null)

requeston rdquoresponserdquo (res) -gt

reson rdquodatardquo (chunk) -gt

parseResponse(chunk+rsquorsquo callback)

reson rdquoendrdquo (data) -gt

consolelog rdquoEnd Request pathrdquo

reson rdquoerrorrdquo (data) -gt

consolelog rdquoError rdquo + data

requestend()

處理資料

parseResponse = (data callback) -gt

if datalength gt 0

用 TryCatch 避免處理 JSON 出錯導致整個中斷

try

callback null JSONparse(data)

catch err

74 8 製作一個 Hubot的 Plurk Adapter

Nodejs中文電子書

consolelog(rdquoError Parse JSONrdquo + data err)

繼續執行

callback null data ||

大致上就是這樣上面程式的架構已經將整個 Hubot Plurk Adapter完成因為在測試時竟然因為噗浪 Lag而沒讀到完整的 Comet資料然後造成程式異常為了避免這個問題發生因此需要加上為了完美呈現需要再

加上 Comet的處理所以要使用到 EventEmitter的功能

comet (server callback) -gt

在 Callback 裡面會找不到自身所以設定區域變數

self =

記錄一下這次的 Request

consolelog(rdquo[Comet] serverrdquo)

Callback 這邊先不丟進去要用另一種方式處理

request = consumerget(rdquohttpdomainpathrdquo token token secret null)

requeston rdquoresponserdquo (res) -gt

reson rdquodatardquo (chunk) -gt

parseResponse(chunk+rsquorsquo callback)

reson rdquoendrdquo (data) -gt

consolelog rdquoEnd Request pathrdquo

請求結束發出事件通知可以進行下一次請求

selfemit rdquonextPlurkrdquo

reson rdquoerrorrdquo (data) -gt

consolelog rdquoError rdquo + data

requestend()

處理資料

parseResponse = (data callback) -gt

if datalength gt 0

用 trycatch 避免失敗中斷

try

去掉 JavaScript 的 Callback

data = datamatch(CometChannelscriptCallback((+))s)

jsonData = rdquordquo

if data

83 建立 Robot跟 API 75

Nodejs中文電子書

jsonData = JSONparse(data[1])

else

如果沒有任何 Match 嘗試直接 parse

jsonData = JSONparse(data)

catch err

consolelog(rdquo[Comet] Errorrdquo data err)

用 TryCatch 避免處理 JSON 出錯導致整個中斷

try

只傳入 json 的 data 部分

callback null jsonDatadata

catch err

consolelog(rdquo[Comet]Error Parse JSONrdquo + data err)

繼續執行

callback null data ||

後面的 get跟 post就簡單多了

get (path callback) -gt

request(rdquoGETrdquo path null callback)

post (path body callback) -gt

request(rdquoPOSTrdquo path body callback)

接著處理取的 Comet網址的 getChannel

getChannel -gt

self =

get rdquoAPPRealtimegetUserChannelrdquo (error data) -gt

if error

檢查是否有 comet server

if datacomet server

selfchannel = datacomet server

如果沒有 Channel Ready 就嘗試連接會失敗

selfemit(rsquochannel readyrsquo)

那麼先來處理 Plurk Adaper好處理的部份

send (plruk id strings⋯)-gt

跟 Reply 一樣直接交給 reply 做

reply plurk id strings⋯

76 8 製作一個 Hubot的 Plurk Adapter

Nodejs中文電子書

reply (plurk id strings⋯) -gt

stringsforEach (message) =gt

botreply(plruk id message)

接著把 run處理好就可以上線運作摟

run -gt

self =

options =

key processenvHUBOT PLURK KEY

secret processenvHUBOT PLURK SECRET

token processenvHUBOT PLURK TOKEN

token secret processenvHUBOT PLURK TOKEN SECRET

創建剛剛的 API

bot = new PlurkStreaming(options)

依照 Twitter 的 new RobotTextMessage 會沒有反應所以參考 hubot-minecraft 的方式

r = robotconstructor

處理噗浪河道訊息

doPlurk = (data)-gt

檢查是否為回噗

if dataresponse

datacontent raw = dataresponsecontent raw

datauser id = dataresponseuser id

確定有噗浪 ID 跟訊息

if dataplurk id and datacontent raw

selfreceive new rTextMessage(dataplurk id datacontent raw)

取得 Comet Server 完成開始第一次 Comet 連接

boton rdquochannel readyrdquo () -gt

botplurk selfdoPlurk

上一次 Comet 完成繼續 Polling

boton rdquonextPlurkrdquo ()-gt

botplurk selfdoPlurk

定時接受好友邀請

do botacceptFriends

bot = bot

83 建立 Robot跟 API 77

Nodejs中文電子書

終於完成 AdapterHubot專案裡面的 scripts資料夾內是互動部分不需要像 Adapter如此大費周章處理只新增檔案並且設計好對白之後就會回噗了我開發用的機器人在此大家可以去跟他玩玩[httpplurkcomelct9620 bot](httpplurkcomelct9620 bot)

84 原始資料提供

bull [製 作 一 個 Hubot 的 噗 浪 Adapter](http revo-skillfrosttw blog20120318create-a-hubot-plurk-adapter)

78 8 製作一個 Hubot的 Plurk Adapter

9

Nodejs好用工具介紹

91 logio

Real-time log monitoring in your browser

httplogioorg

nodejs (gt=0412 lt=0611)

npm config set unsafe-perm true

npm install -g --prefix=usrlocal logio

logio server start

httplocalhost8998

79

Nodejs中文電子書

80 9 Nodejs好用工具介紹

10

精選文章

101 Express

bull 使用 NodeJS + Express從 GETPOST Request取值 [邀稿中]

bull link2 [邀稿中]

bull link3 [邀稿中]

81

Nodejs中文電子書

82 10 精選文章

11

參考資源

111 Nodejs書籍

bull Programming Nodejs April 2012 (est) Travis Swicegood Pragmatic Bookshelf

bull e Little Book on CoffeeScript January 2012 Alex MacCaw OrsquoReilly Media

bull Node for Front-End Developers January 2012 Garann Means OrsquoReilly Media

bull Building Hypermedia APIs with HTML5 and Node November 2011 MikeAmundsen OrsquoReilly Media

bull Node Web Development August 2011 David Herron PacktPub

bull CoffeeScript Accelerated JavaScript Development July 2011 Trevor BurnhamPragmatic Bookshelf

bull Getting Started with GEO CouchDB and Nodejs July 2011 Mick omp-son OrsquoReilly Media

bull What Is Node July 2011 Brett McLaughlin OrsquoReilly Media

bull Node Up and Running May 2011 Tom Hughes-Croucher Mike WilsonOrsquoReilly Media

83

Nodejs中文電子書

bull Hands-on Nodejs e Nodejs introduction and API reference May 2011 Pe-dro Teixeira LeanPub

bull e Node Beginner Book Manuel Kiessling

bull Mastering Nodejs visionmedia

112 Nodejs影音教學

bull e Node Sessions e Best of OSCON 2011 August 2011 OrsquoReilly Media(Video)

bull Tom Hughes-Croucher on Node March 2011 OrsquoReilly Media (Video)

113 Nodejs教學網站

bull Node Tuts

114 Nodejs課程

bull Introduction To Nodejs Van Nguyen CodeLesson

84 11 參考資源

Page 3: Nodejs Wiki

目錄

關於本書 1

授權 1作者 2下載電子書 2原始碼 201 精選文章收錄流程 3

前言 5

1 Nodejs簡介 7

2 JavaScript與 NodeJS 11

21 Event Loop 1122 Scope與 Closure 1223 Callback 1324 CPS(Continuation-Passing Style) 1625 函數返回函數與 Currying 1726 流程控制 18

i

3 Nodejs安裝與設定 25

31 Ubuntu Linux 2532 Other Linux 2633 Windows 26

4 Nodejs基礎 29

41 nodejs http伺服器建立 2942 nodejs http路徑建立 3143 nodejs檔案讀取 3444 nodejs http靜態檔案輸出 3645 nodejs http GET資料擷取 3846 本章結語 40

5 NPM套件管理工具 41

51 安裝 NPM 4152 使用 NPM安裝套件 4553 套件的更新及維護 4854 使用 packagejson 49

6 Express介紹 51

61 Express安裝 5162 Express基本操作 5263 Express路由處理 5264 Express middleware 5565 Express路由應用 5666 Express GET應用範例 5767 Express POST應用範例 6068 Express AJAX應用範例 6369 原始資料提供 67

7 CoffeeScript 69

8 製作一個 Hubot的 Plurk Adapter 71

81 應用事項提醒 7182 建立 Adapter 71

ii

83 建立 Robot跟 API 7284 原始資料提供 78

9 Nodejs好用工具介紹 79

91 logio 79

10 精選文章 81

101 Express 81

11 參考資源 83

111 Nodejs書籍 83112 Nodejs影音教學 84113 Nodejs教學網站 84114 Nodejs課程 84

iii

iv

關於本書

這是一本關於 Nodejs技術的開放源碼電子書我們使用 GitHub維護電子書內容並交由 ContPub(Continuous Publishing)系統自動線上發佈本書提供 PDFEPUBMOBI及 HTML等格式您除了可以在網站檢視本書所有內容也可以將電子書下載至閱讀器保存

本書的線上閱讀網址與 GitHub資料同步更新

httpbooknodejstw

如果您想要取得本書的其他格式可以從 ContPub的電子書專頁下載最新版本

httpcontpuborgreadnodejs-wiki-book

本書適合 Nodejs初學者至進階開發者也歡迎您在學習時一起參與本書內容撰寫

授權

Nodejs台灣社群協作電子書採用創用 CC姓名標示 -非商業性授權您不必為本書付費

Nodejs Wiki Book book is licensed under the Attribution-NonCommercial 30Unported license You should not have paid for this book

您可以複製散佈及修改本書內容但請勿將本書用於商業用途

您可以在以下網址取得授權條款全文

1

Nodejs中文電子書

httpcreativecommonsorglicensesby-nc30legalcode

作者

本書由 Nodejs Taiwan社群成員協作以下名單依照字母排序

bull Caesar Chi (clonn)

bull Fillano Feng ( llano)

bull Kyle Lin (lyhcode)

Nodejs Taiwan是一個自由開放的技術學習社群我們歡迎您加入一起學習研究及分享

下載電子書

線上閱讀本書

httpbooknodejstw

PDF格式適合一般電腦及 7吋以上平板電腦閱讀

httpcontpuborgdownloadnodejs-wiki-bookpdf

EPUB格式適合 iPadiPhone行動裝置閱讀

httpcontpuborgdownloadnodejs-wiki-bookepub

MOBI格式適合 Kindle電子書閱讀器

httpcontpuborgdownloadnodejs-wiki-bookmobi

原始碼

本書最新的原始碼(中文版)網址如下

httpgithubcomnodejs-twnodejs-wiki-book

2 關於本書

Nodejs中文電子書

01 精選文章收錄流程

精選文章的用意是鼓勵作者在自已的網誌發表 Nodejs 教學再由Nodejs Taiwan社群挑選系列文章列入電子書的精選文集

1 將文章標題及連結貼到「精選文章」分類

2 社群工作小組以 E-Mail通知作者文章列入精選並邀請將內文授權給Nodejs Taiwan電子書分享

3 作者同意後由工作小組負責整理圖文發佈至電子書

4 以 E-Mail寄出感謝函通知原作者文章已收錄依作者意願調整文章內容

5 於 Nodejs Taiwan首頁及粉絲專頁推薦作者的文章

01 精選文章收錄流程 3

Nodejs中文電子書

4 關於本書

前言

Nodejs是 JavaScript程式語言的開發框架由 04x至目前 v060版本核心上已經有許多變更當然目的只有一個就是讓開發變得更簡單速

度能夠更快這是所有人奮鬥的目標而 nodeJS 華文維基平台主力由nodeJStw一群喜好 javascript開發者共同主筆內文以中文為主希望能夠降低開發者學習門檻藉由我們拋磚引玉讓更多華人開發者共同學習

討論

CoffeeScript好好玩

5

Nodejs中文電子書

6 前言

1

Nodejs簡介

Nodejs是一個高效能易擴充的網站應用程式開發框架 (Web Applica-tion Framework)它誕生的原因是為了讓開發者能夠更容易開發高延展性的網路服務不需要經過太多複雜的調校效能調整及程式修改就能

滿足網路服務在不同發展階段對效能的要求

Ryan Dahl是NodeJS的催生者目前任職於 Joyent (主機託管服務公司)他開發 NodeJS的目的就是希望能解決 Apache在連線數量過高時緩衝區 (buffer)和系統資源會很快被耗盡的問題希望能建立一個新的開發框架以解決這個問題因此嘗試使用效能十分優秀的 V8 JavaScript Engine讓網站開發人員熟悉的 JavaScript程式語言也能應用於後端服務程式的開發並且具有出色的執行效能

JavaScript是功能強大的物件導向程式語言但是在 JavaScript的官方規格中主要是定義網頁 (以瀏覽器為基礎) 應用程式需要的應用程式介面(API)對 JavaScript程式的應用範圍有所侷限為使 JavaScript能夠在更多用途發展CommonJS規範一組標準函式庫 (standard library)使 JavaScript的應用範圍能夠和 RubyPython及 Java等語言同樣豐富撰寫 NodeJS的JavaScript程式碼符合 CommonJS規範可以使用 CommonJS API為基礎開發程式並且在不同的 CommonJS兼容 (compliant) JavaScript執行環境中程式碼具有可攜性

7

Nodejs中文電子書

瀏覽器的 JavaScript與實現 CommonJS規範的 NodeJS有何不同呢瀏覽器的 JavaScript 提供 XMLHttpRequest 讓程式可以和網頁伺服器建立資料傳輸連線但這通常只能適用於網站開發的需求因為我們只能用

XMLHttpRequest與網頁伺服器通訊卻無法利用它建立其他類型如 Telnet FTP NTP的伺服器通訊如果我們想開發網路服務程式例如 SMTP電子郵件伺服器就必須使用 Sockets建立 TCP (某些服務則用 UDP)監聽及連線其他程式語言如 PHPJavaPythonPerl及 Ruby等在標準開發環境中皆有提供 Sockets API而瀏覽器的 JavaScript基於安全及貼近網站設計需求的考量下並未將 Sockets列入標準函式庫之中CommonJS的規範填補這種基礎函式庫功能的空缺遵循 CommonJS規範的 NodeJS可以直接使用 Sockets API建立各種網路服務程式

JavaScript語言本身支援 Lambda的特性因此一個匿名函式 (anonymousfunction)可以被儲存成一個變數並當作參數傳遞給另一個函式

var proc1 = function(op x) return op(x)

var op1 = function(x) return x+1 var op2 = function(x) return xx

proc1(op1 3) result is 4 proc1(op2 5) result is 25

另一個 JavaScript開發者必須掌握的語言特性稱為 Closure

NodeJS符合 CommonJS的規範使得 Callback方式易於實現也能夠讓更多同好基於 JavaScript開發符合 NodeJS的外掛模組 (Module)

回想以前要寫一個能夠同時容納上百人的上線的網路服務需要花費

多大的苦工可能 10人多就需要經過一次程式調整而 NodeJS就是為了解決這個困境NodeJS因此誕生它是一種利用 V8 Javascript編譯器所開發的產品利用 V8編譯器的高效能與 Javascript的程式開發特性所產生的網路程式

開發人員所編寫出來的 Javascript腳本程式怎麼可能會比其他語言寫出來的網路程式還要快上許多呢以前的網路程式原理是將使用者每次的

連線 (connection)都開啟一個執行緒 (thread)當連線爆增的時候將會快速耗盡系統效能並且容易產生阻塞 (block)的發生

8 1 Nodejs簡介

Nodejs中文電子書

NodeJS對於資源的調校有所不同當程式接收到一筆連線 (connection)會通知作業系統透過 epoll kqueue devpoll或 select將連線保留並且放入heap中配置先讓連線進入休眠 (Sleep)狀態當系統通知時才會觸發連線的 callback這種處理連線方式只會佔用掉記憶體並不會使用到 CPU資源另外因為採用 Javascript語言的特性每個 request都會有一個 callback如此可以避免發生 Block的狀況發生

基於 Callback特性目前NodeJS大多應用於 Comet(long pulling) RequestServer或者是高連線數量的網路服務上目前也有許多公司將 NodeJS設為內部核心網路服務之一在 NodeJS也提供了外掛管理 (Node packagemanagement)讓愛好 NodeJS輕易開發更多有趣的服務外掛並且提供到 npm讓全世界使用者快速安裝使用

本書最後執行測試版本為 nodejs v068相關 API文件可查詢 lsquohttpnodejsorg lthttpnodejsorggtlsquo本書所有範例均可於 linux windows上執行如遇到任何問題歡迎至 lsquohttpnodejstw lthttpnodejstwgtlsquo詢問對於nodejs相關問題

9

Nodejs中文電子書

10 1 Nodejs簡介

2

JavaScript與 NodeJS

其實使用 JavaScript 在網頁端與伺服器端的差距並不大但是為了使NodeJS可以發揮他最強大的能力有一些知識還是必要的所以還是針對這些主題介紹一下其中 Event LoopScope以及 Callback其實是比較需要了解的基本知識cpscurrying ow control是更進階的技巧與應用

21 Event Loop

可能很多人在寫 Javascript 時並不知道他是怎麼被執行的這個時候可以參考一下 jQuery作者 John Resig一篇好文章介紹事件及 timer怎麼在瀏覽器中執行How JavaScript Timers Work通常在網頁中所有的Javascript執行完畢後(這部份全部都在 global scope跑除非執行函數)接下來就是如 John Resig解釋的這樣所有的事件處理函數以及 timer執行的函數會排在一個 queue結構中利用一個無窮迴圈不斷從 queue中取出函數來執行這個就是 event loop

(除了 John Resig的那篇文章Nicholas C Zakas的 ldquoProfessional Javascriptfor Web Developer 2nd editionrdquo 有一個試閱本httpyuiblogcomassetspdfzakas-projs-2ed-ch18pdf598頁剛好也有簡短的說明)

所以在 Javascript中雖然有非同步但是他並不是使用執行緒所有

11

Nodejs中文電子書

的事件或是非同步執行的函數都是在同一個執行緒中利用 event loop的方式在執行至於一些比較慢的動作例如 IO網頁 render re ow等實際動作會在其他執行緒跑等到有結果時才利用事件來觸發處理函數來處理

這樣的模型有幾個好處沒有執行緒的額外成本所以反應速度很快不會

有任何程式同時用到同一個變數不必考慮 lock也不會產生 dead lock所以程式撰寫很簡單但是也有一些潛在問題任一個函數執行時間較長都

會讓其他函數更慢執行(因為一個跑完才會跑另一個)在多核心硬體普遍

的現在無法用單一的應用程式 instance發揮所有的硬體能力用 NodeJS撰寫伺服器程式碰到的也是一樣的狀況要讓系統發揮 event loop的效能就要盡量利用事件的方式來組織程式架構另外對於一些有可能較為耗

時的操作可以考慮使用 processnextTick函數來讓他以非同步的方式執行避免在同一個函數中執行太久擋住所有函數的執行

如果想要測試 event loop怎樣在「瀏覽器」中運行可以在函數中呼叫alert()這樣會讓所有 Javascript的執行停下來尤其會干擾所有使用 timer的函數執行有一個簡單的例子這是一個會依照設定的時間間隔嚴格執

行動作的動畫如果時間過了就會跳過要執行的動作點按圖片以後人

物會快速旋轉但是在旋轉執行完畢前按下「delay」按鈕讓 alert訊息等久一點接下來的動畫就完全不會出現了

22 Scope與 Closure

要快速理解 JavaScript的 Scope(變數作用範圍)原理只要記住他是Lexical Scope就差不多了簡單地說變數作用範圍是依照程式定義時(或者叫做程式文本)的上下文決定而不是執行時的上下文決定

為了維護程式執行時所依賴的變數即使執行時程式運行在原本的

scope之外他的變數作用範圍仍然維持不變這時程式依賴的自由變數(定義時不是 local的而是在上一層 scope定義的變數)一樣可以使用就好像被關閉起來所以叫做 Closure用程式看比較好懂

function outter(arg1)

arg1 及 free_variable1 對 inner 函數來說都是自由變數

var free_variable1 = 3

return function inner(arg2)

12 2 JavaScript與 NodeJS

Nodejs中文電子書

var local_variable1 =2arg2 及 local_variable1 對 inner 函數來說都是本地變數

return arg1 + arg2 + free_variable + local_variable1

var a = outter(1)變數 a就是 outter函數執行後返回的 inner函數 var b =a(4)執行 inner函數執行時上下文已經在 outter函數之外但是仍然能正常執行而且可以使用定義在 outter函數裡面的 arg1及 free variable1變數 consolelog(b)結果 10

在 Javascript中scope最主要的單位是函數(另外有 global及 eval)所以有可能製造出 closure的狀況通常在形式上都是有巢狀的函數定義而且內側的函數使用到定義在外側函數裡面的變數

Closure有可能會造成記憶體洩漏主要是因為被參考的變數無法被垃圾收集機制處理造成佔用的資源無法釋放所以使用上必須考慮清楚

不要造成意外的記憶體洩漏(在上面的例子中如果 a一直未執行使用到的記憶體就不會被釋放)

跟透過函數的參數把變數傳給函數比較起來Javascript Engine會比較難對 Closure進行最佳化如果有效能上的考量這一點也需要注意

23 Callback

要介紹 Callback之前要先提到 JavaScript的特色

JavaScript是一種函數式語言(functional language)所有 Javascript語言內的函數都是高階函數 (higher order function這是數學名詞計算機用語好像是 rst class function意指函數使用沒有任何限制與其他物件一樣)也就是說函數可以作為函數的參數傳給函數也可以當作函數的返回值這個特性讓 Javascript的函數使用上非常有彈性而且功能強大

callback在形式上其實就是把函數傳給函數然後在適當的時機呼叫傳入的函數Javascript使用的事件系統通常就是使用這種形式NodeJS中有一個物件叫做 EventEmitter這是 NodeJS事件處理的核心物件所有會使用事件處理的函數都會「繼承」這個物件(這裡說的繼承實

作上應該像是 mixin)他的使用很簡單可以使用物件on(事件名稱callback

23 Callback 13

Nodejs中文電子書

函數) 或是物件addListener(事件名稱callback 函數) 把你想要處理事件的函數傳入在物件中可以使用物件emit(事件名稱 參數) 呼叫傳入的callback函數這是 Observer Pattern的簡單實作而且跟在網頁中使用 DOM的 addEventListener使用上很類似也很容易上手不過 NodeJS是大量使用非同步方式執行的應用所以程式邏輯幾乎都是寫在 callback函數中當邏輯比較複雜時大量的 callback會讓程式看起來很複雜也比較難單元測試舉例來說

var p client = new Db(lsquointegration tests 20rsquo new Server(ldquo127001rdquo 27017) lsquopkrsquoCustomPKFactory) p clientopen(function(err p client)

p clientdropDatabase(function(err done)

p clientcreateCollection(lsquotest custom keyrsquo function(err collection)

collectioninsert(lsquoarsquo1 function(err docs)

collection nd(lsquo idrsquonew ObjectID(ldquoaaaaaaaaaaaardquo) function(err cursor)

cursortoArray(function(err items) testassertEquals(1 itemslength) p clientclose()

)

)

)

)

)

)

這是在網路上看到的一段操作 mongodb的程式碼為了循序操作所以必須在一個 callback裡面呼叫下一個動作要使用的函數這個函數裡面還是會使用 callback最後就形成一個非常深的巢狀

這樣的程式碼會比較難進行單元測試有一個簡單的解決方式是

盡量不要使用匿名函數來當作 callback或是 event handler透過這樣的方式

14 2 JavaScript與 NodeJS

Nodejs中文電子書

就可以對各個 handler做單元測試了例如

var http = require(lsquohttprsquo) var tools =

cookieParser function(request response) if(requestheaders[rsquoCookiersquo]) do parsing

var server = httpcreateServer(function(request response)

thisemit(lsquoinitrsquo request response)

) serveron(lsquoinitrsquo toolscookieParser) serverlisten(8080 lsquo127001rsquo)

更進一步可以把 tools改成外部 module例如叫做 toolsjs

moduleexports = cookieParser function(request response) if(requestheaders[rsquoCookiersquo]) do parsing

然後把程式改成

var http = require(lsquohttprsquo)

var server = httpcreateServer(function(request response) thisemit(lsquoinitrsquo re-quest response)

) serveron(lsquoinitrsquo require(lsquo toolsrsquo)cookieParser) serverlisten(8080lsquo127001rsquo)

這樣就可以單元測試 cookieParser了例如使用 nodeunit時可以這樣寫

var testCase = require(lsquonodeunitrsquo)testCase moduleexports = testCase(

ldquosetUprdquo function(cb) thisrequest = headers Cookielsquoname1val1 name2val2rsquo thisresponse = thisresult= name1rsquoval1rsquoname2rsquoval2rsquo

cb()

ldquotearDownrdquo function(cb)

cb()

23 Callback 15

Nodejs中文電子書

ldquonormal caserdquo function(test)

testexpect(1) var obj = require(lsquotoolsrsquo)cookieParser(thisrequest thisresponse)testdeepEqual(obj thisresult) testdone()

)

善於利用模組可以讓程式更好維護與測試

24 CPS(Continuation-Passing Style)

cps 是 callback 使用上的特例形式上就是在函數最後呼叫 callback這樣就好像把函數執行後把結果交給 callback 繼續運行所以稱作continuation-passing style利用 cps可以在非同步執行的情況下透過傳給 callback的這個 cps callback來獲知 callback執行完畢或是取得執行結果例如

lthtmlgt ltbodygt ltdiv id=rdquopanelrdquo style=rdquovisibilityhiddenrdquogtlt divgtlt bodygt lt htmlgt ltscriptgt var request = new XMLHttpRequest() re-questopen(lsquoGETrsquo lsquotest749txt timestamp=rsquo+new Date()getTime() true) re-questaddEventListener(lsquoreadystatechangersquo function(next)

return function() if(thisreadyState===4ampampthisstatus===200) next(thisresponseText)lt== 傳入的 cps callback 在動作完成時執行並取得結果進一步處理

(function(str)lt==這個匿名函數就是 cps callback

documentgetElementById(lsquopanelrsquo)innerHTML=str docu-mentgetElementById(lsquopanelrsquo)stylevisibility = lsquovisiblersquo

) false) requestsend() ltscriptgt

進一步的應用也可以參考 2-6流程控制

16 2 JavaScript與 NodeJS

Nodejs中文電子書

25 函數返回函數與 Currying

前面的 cps範例裡面使用了函數返回函數這是為了把 cps callback傳遞給 onreadystatechange事件處理函數的方法(因為這個事件處理函數並沒有設計好會傳送接收這樣的參數)實際會執行的事件處理函數其實是內層返回的那個函數之外包覆的這個函數主要是為了利用 Closure把 next傳給內層的事件處理函數這個方法更常使用的地方是為了解決一些

scope問題例如

ltscriptgt var accu=0count=10 for(var i=0 iltcount i++)

setTimeout(

function() countndash accu+=i if(countlt=0)

consolelog(accu)

50)

ltscriptgt

最後得出的結果會是 100而不是想像中的 45這是因為等到 setTime-out指定的函數執行時變數 i已經變成 10而離開迴圈了要解決這個問題就需要透過 Closure來保存變數 i

ltscriptgt var accu=0count=10 for(var i=0 iltcount i++)

setTimeout(

function(i) return function() countndash

accu+=i if(countlt=0)

consolelog(accu)

(i)

50)

25 函數返回函數與 Currying 17

Nodejs中文電子書

淺藍色底色的部份是跟上面例子不一樣的地方 ltscriptgt

函數返回函數的另外一個用途是可以暫緩函數執行例如

function add(m n) return m+n

var a = add(20 10) consolelog(a)

add這個函數必須同時輸入兩個參數才有辦法執行如果我希望這個函數可以先給它一個參數等一些處理過後再給一個參數然後得到結

果就必須用函數返回函數的方式做修改

function add(m)

return function(n) return m+n

var wait another arg = add(20)先給一個參數 var a = function(arr)

var ret=0 for(var i=0iltarrlengthi++) ret+=arr[i] return ret

([1234])計算一下另一個參數 var b = wait another arg(a)然後再繼續執行 consolelog(b)

像這樣利用函數返回函數使得原本接受多個參數的函數可以一次

接受一個參數直到參數接收完成才執行得到結果的方式有一個學名就

叫做Currying

綜合以上許多奇技淫巧就可以透過用函數來處理函數的方式調整

程式流程接下來看看

26 流程控制

(以 sync方式使用 async函數避開巢狀 callback循序呼叫 async callback等奇技淫巧)

建議參考

bull httphowtonodeorgcontrol- ow

bull httphowtonodeorgcontrol- ow-part-ii

18 2 JavaScript與 NodeJS

Nodejs中文電子書

bull httphowtonodeorgcontrol- ow-part-iii

bull httpblogmixunet20110202essential-node-js-patterns-and-snippets

這幾篇都是非常經典的 NodeJSJavascript流程控制好文章(阿mixu是在介紹一些 pattern時提到這方面的主題)不過我還是用幾個簡單的程式介紹一下做法跟概念

並發與等待

下面的程式參考了 mixu文章中的做法

var wait = function(callbacks done) consolelog(lsquowait startrsquo) var counter = call-backslength var results = [] var next = function(result) 接收函數執行結果並判斷是否結束執行 resultspush(result) if(ndashcounter == 0) done(results)如果結束執行就把所有執行結果傳給指定的 callback處理 for(var i = 0 i lt callbackslength i++) 依次呼叫所有要執行的函數 callbacks[i](next) consolelog(lsquowait endrsquo)

wait( [ function(next) setTimeout(function() consolelog(lsquodone arsquo) var result =500 next(result) 500) function(next) setTimeout(function() con-solelog(lsquodone brsquo) var result = 1000 next(result) 1000) function(next)setTimeout(function() consolelog(lsquodone crsquo) var result = 1500 next(1500) 1500) ] function(results) var ret = 0 i=0 for( iltresultslength i++) ret+= results[i] consolelog(lsquodone all result lsquo+ret)

)

執行結果wait start wait end done a done b done c done all result 3000

可以看出來其實 wait並不是真的等到所有函數執行完才結束執行而是在所有傳給他的函數執行完畢後(不論同步非同步)才執行處理結

果的函數(也就是 done())

不過這樣的寫法還不夠實用因為沒辦法實際讓函數可以等待執行

完畢又能當作事件處理函數來實際使用上面參考到的 Tim Caswell的文

26 流程控制 19

Nodejs中文電子書

章裡面有一種解法不過還需要額外包裝(在他的例子中)NodeJS核心的 fs物件把一些函數(例如 readFile)用 Currying處理類似像這樣

var fs = require(lsquofsrsquo) var readFile = function(path)

return function(callback errback)

fsreadFile(path function(err data)

if(err) errback()

else callback(data)

)

其他部份可以參考 Tim Caswell的文章他的 Doparallel跟上面的 wait差不多意思這裡只提示一下他沒說到的地方

另外一種做法是去修飾一下 callback當他作為事件處理函數執行後再用 cps的方式取得結果

ltscriptgt function Wait(fns done)

var count = 0 var results = [] thisgetCallback = function(index)

count++ return (function(waitback)

return function() var i=0args=[]for(iltargumentslengthi++)

argspush(arguments[i])

argspush(waitback) fns[index]apply(thisargs)

)(function(result) resultspush(result) if(ndashcount == 0)

20 2 JavaScript與 NodeJS

Nodejs中文電子書

done(results)

)

var a = new Wait(

[ function(waitback) consolelog(lsquodone arsquo) var result = 500 wait-back(result) function(waitback) consolelog(lsquodone brsquo) var result =1000 waitback(result) function(waitback) consolelog(lsquodone crsquo) varresult = 1500 waitback(result) ] function(results) var ret = 0 i=0for( iltresultslength i++) ret += results[i] consolelog(lsquodone allresult lsquo+ret)

) var callbacks = [agetCallback(0)agetCallback(1)agetCallback(0)agetCallback(2)]一次取出要使用的 callbacks避免結果提早送出 setTimeout(callbacks[0]500) setTimeout(callbacks[1] 1000) setTimeout(callbacks[2] 1500) setTime-out(callbacks[3] 2000) 當所有取出的 callbacks執行完畢就呼叫 done()來處理結果 ltscriptgt

執行結果

done a done b done a done c done all result 3500

上面只是一些小實驗更成熟的作品是 Tim Caswell 的 stephttpsgithubcomcreationixstep

如果希望真正使用同步的方式寫非同步則需要使用 Promisejs這一類的 library來轉換非同步函數不過他結構比較複雜 XD(見仁見智不過有些人認為 Promise有點過頭了)httpblogsmsdncombrbucktonarchive20110815promise-js-2-0-promise-framework-for-javascriptaspx

如果想不透過其他 Library做轉換又能直接用同步方式執行非同步函數大概就要使用一些需要額外 compile原始程式碼的方法了例如 BrunoJouhier的 streamlinejshttpsgithubcomSagestreamlinejs

26 流程控制 21

Nodejs中文電子書

循序執行

循序執行可以協助把非常深的巢狀 callback結構攤平例如用這樣的簡單模組來做(serialjs)

moduleexports = function(funs) var c = 0 if(isArrayOfFunctions(funs))

throw(lsquoArgument type was not matched Should be array of func-tionsrsquo)

return function()

var args = Arrayprototypeslicecall(arguments 0) if((cgt=funslength))

c++ return funs[c-1]apply(this args)

function isArrayOfFunctions(f ) if(typeof f == lsquoobjectrsquo) return false if(flength)return false if(fconcat) return false if(fsplice) return false var i = 0 for(iltflength i++)

if(typeof f[i] == lsquofunctionrsquo) return false

return true

簡單的測試範例(testSerialjs)使用 fs 模組確定某個 path 是檔案然後讀取印出檔案內容這樣會用到兩層的 callback所以測試中有使用serial的版本與 nested callbacks的版本做對照

var serial = require(lsquoserialrsquo) fs = require(lsquofsrsquo) path = lsquodclientjsrsquo cb = serial([ func-tion(err data)

if(err)

if(dataisFile) fsreadFile(path cb)

22 2 JavaScript與 NodeJS

Nodejs中文電子書

else consolelog(err)

function(err data)

if(err) consolelog(lsquo[ attened by searial]rsquo) con-solelog(datatoString(lsquoutf8rsquo))

else consolelog(err)

]) fsstat(path cb)

fsstat(path function(err data) 第一層 callback if(err)

if(dataisFile)

fsreadFile(path function(err data) 第二層 callback if(err)

consolelog(lsquo[nested callbacks]rsquo) con-solelog(datatoString(lsquoutf8rsquo))

else consolelog(err)

)

else consolelog(err)

)

關鍵在於這些 callback的執行是有順序性的所以利用 serial返回的一個函數 cb來取代這些 callback然後在 cb中控制每次會循序呼叫的函數

26 流程控制 23

Nodejs中文電子書

就可以把巢狀的 callback攤平成循序的 function陣列(就是傳給 serial函數的參數)

測試中的dclientjs 是一個簡單的 dnode 測試程式放在跟 testSerialjs同一個目錄

var dnode = require(lsquodnodersquo)

dnodeconnect(8000 lsquolocalhostrsquo function(remote)

remoterestart(function(str) consolelog(str) processexit()

)

)

執行測試程式後出現結果

[ attened by searial] var dnode = require(lsquodnodersquo)

dnodeconnect(8000 lsquolocalhostrsquo function(remote)

remoterestart(function(str) consolelog(str) processexit()

)

)

[nested callbacks] var dnode = require(lsquodnodersquo)

dnodeconnect(8000 lsquolocalhostrsquo function(remote)

remoterestart(function(str) consolelog(str) processexit()

)

)

對照起來看兩種寫法的結果其實是一樣的但是利用 serialjs巢狀的 callback結構就會消失

不過這樣也只限於順序單純的狀況如果函數執行的順序比較複雜

(不只是一直線)還是需要用功能更完整的流程控制模組比較好例如

httpsgithubcomcaolanasync

24 2 JavaScript與 NodeJS

3

Nodejs安裝與設定

本篇將講解如何在各個不同 OS建立 NodeJS環境目前 NodeJS 048版本環境架設方式需依賴 Linux指令才可編譯完成當然在不同作業系統中也已經有 NodeJS package可以直接使用指令快速架設以下各不同作業系統解說如何安裝 NodeJS

31 Ubuntu Linux

更新推薦使用 nvm

git clone gitgithubcomcreationixnvmgit ~nvm

echo rdquo ~nvmnvmshrdquo gtgt ~bashrc

nvm install v0614

nvm alias default v0614

以 上 可 參 考 http dreamerslabcom blog tw how-to-setup-a-node-js-development-environment-on-ubuntu-11-04

使用 APT套件管理工具是常見的方法以下是使用社群提供的 PPA安裝方式

sudo apt-get install python-software-properties

sudo add-apt-repository ppachris-leanodejs-devel

25

Nodejs中文電子書

sudo apt-get update

sudo apt-get install nodejs

32 Other Linux

Linux很適合作為 NodeJS的伺服器作業系統及開發環境安裝前請先確認以下套件已正確安裝

bull curl (wget)用來下載檔案的工具

bull git先進的版本控制工具

bull g++ GNU C++軟體編譯工具

bull make GNU軟體專案建置工具

安裝指令如下如設有權限問題請在指令前面加上 sudo

git clone httpsgithubcomjoyentnodegit

cd node

git checkout v067

configure

make

sudo make install

接著測試 nodeJS是否正常執行

node --version

出現版本訊息即表示安裝成功

33 Windows

nodeJS 在 v060 版本之後開始正式支援 windows native直接使用nodeexe 就可以執行程式支援性完全與 linux 相同更棒的部份就是不需經過編譯經過下載之後簡單設定完成立即開發 node程式

26 3 Nodejs安裝與設定

Nodejs中文電子書

下載 nodejs安裝檔案 lthttpnodejsorgdownloadgt

如此完成 windows native nodeexe安裝接著可以進入 command line執行測試在 command line輸入rdquonoderdquo會出現 nodejs版本訊息畫面表示安裝完成

33 Windows 27

Nodejs中文電子書

28 3 Nodejs安裝與設定

4

Nodejs基礎

前篇文章已經由介紹安裝至設定都有完整介紹nodeJS 內部除了javascript常用的函式 (function)物件 (object)之外也有許多不同的自訂物件nodeJS預設建立這些物件為核心物件是為了要讓開發流程更為這些資料在官方文件已經具有許多具體說明接下來將會介紹在開發 nodeJS程式時常見的物件特性與使用方法

41 nodejs http伺服器建立

在 lsquonodejs官方網站 lthttpnodejsorggtlsquo裡面有舉一個最簡單的 HTTP伺服器建立一開始初步就是建立一個伺服器平台讓 nodejs可以與瀏覽器互相行為每種語言一開始的程式建立都是以 Hello world開始最初也從 Hello world帶各位進入 nodejs的世界

輸入以下程式碼儲存檔案為 node basic http hello worldjs

var server

ip = rdquo127001rdquo

port = 1337

http = require(rsquohttprsquo)

server = httpcreateServer(function (req res)

29

Nodejs中文電子書

reswriteHead(200 rsquoContent-Typersquo rsquotextplainrsquo)

resend(rsquoHello Worldnrsquo)

)

serverlisten(port ip)

consolelog(rdquoServer running at httprdquo + ip + rdquordquo + port)

程式碼解講一開始需要有幾個基本的變數

bull ip 機器本身的 ip位置因為使用本地端因此設定為 127001

bull port 需要開通的阜號通常設定為 http port 80因範例不希望與基本 port相衝隨意設定為 1337

在 nodejs 的程式中有許多預設的模組可以使用因此需要使用require方法將模組引入在這邊我們需要使用 http這個模組因此將 http載入Http模組裡面內建有許多方法可以使用這邊採用 createServer創建一個基本的 http伺服器再將 http伺服器給予一個 server變數裡面的回呼函式 (call back function)可以載入 http伺服器的資料與回應方法 (requestresponse)在程式裡面就可以看到我們直接回應給瀏覽器端所需的 Header回應內容

reswriteHead(200 rsquoContent-Typersquo rsquotextplainrsquo)

resend(rsquoHello Worldnrsquo)

Http伺服器需要設定 port ip在最後需要設定 Http監聽需要使用到listen事件監聽所有 Http伺服器行為

httplisten(port ip)

所有事情都完成之後需要確認伺服器正確執行因此使用 console在javascript裡就有這個原生物件console所印出的資料都會顯示於 nodejs伺服器頁面這邊印出的資料並不會傳送到使用者頁面上之後許多除壞

(debug)都會用到 console物件

consolelog(rdquoServer running at httprdquo + ip + rdquordquo + port)

30 4 Nodejs基礎

Nodejs中文電子書

42 nodejs http路徑建立

前面已經介紹如何建立一個簡單的 http伺服器接下來本章節將會介紹如何處理伺服器路徑 (rout)問題在 http的協定下所有從瀏覽器發出的要求 (request)都需要經過處理路徑上的建立也是如此

路徑就是指伺服器 ip位置或者是網域名稱之後對於伺服器給予的要求修改剛才的 hello world檔案修改如下

server = httpcreateServer(function (req res)

consolelog(requrl)

reswriteHead(200 rsquoContent-Typersquo rsquotextplainrsquo)

resend(rsquohello worldnrsquo)

)

重新啟動 nodejs程式後在瀏覽器端測試一下路徑行為結果如下圖

當在瀏覽器輸入 http1270011337test 在伺服器端會收到兩個要求一個是我們輸入的test要求另外一個則是faviconicotest的路徑要求http伺服器本身需要經過程式設定才有辦法回應給瀏覽器端所需要的回應在伺服器中所有的路徑要求都是需要被解析才有辦法取得資料從

42 nodejs http路徑建立 31

Nodejs中文電子書

上面解說可以了解到在 nodejs當中所有的路徑都需要經過設定未經過設定的路由會讓瀏覽器無法取得任何資料導致錯誤頁面的發生底下將會解

說如何設定路由同時避免發生錯誤情形先前 nodejs程式需要增加一些修改才能讓使用者透過瀏覽器在不同路徑時有不同的結果根據剛才

的程式做如下的修改

var server

ip = rdquo127001rdquo

port = 1337

http = require(rsquohttprsquo)

url = require(rsquourlrsquo)

path

server = httpcreateServer(function (req res)

path = urlparse(requrl)

reswriteHead(200 rsquoContent-Typersquo rsquotextplainrsquo)

switch (pathpathname)

case rdquoindexrdquo

resend(rsquoI am indexnrsquo)

break

case rdquotestrdquo

resend(rsquothis is test pagenrsquo)

break

default

resend(rsquodefault pagenrsquo)

break

)

serverlisten(port ip)

consolelog(rdquoServer running at httprdquo + ip + rdquordquo + port)

程式做了片段的修改首先載入 url模組另外增加一個 path變數url模組就跟如同他的命名一般專門處理 url字串處理裡面提供了許多方法來解決路徑上的問題因為從瀏覽器發出的要求路徑可能會帶有多種需求

或者 GET參數組合等因此我們需要將路徑單純化取用路徑部分的資料即可例如使用者可能會送出 http1270011337testsend=1如果直接

32 4 Nodejs基礎

Nodejs中文電子書

信任 requrl就會收到結果為testsend=1所以需要透過 url模組的方法將路徑資料過濾

在這邊使用 urlparse的方法裡面帶入網址格式資料會回傳路徑資料為了後需方便使用將回傳的資料設定到 path變數當中在回傳的路徑資料裡面包含資訊如下圖

這邊只需要使用單純的路徑要求直接取用 pathpathname就可以達到我們的目的

最後要做路徑的判別在不同的路徑可以指定不同的輸出在範例中

有三個可能結果第一個從瀏覽器輸入index就會顯示 index結果test 就會呈現出 test頁面最後如果都不符合預期的輸入會直接顯示 default的頁面最後的預防可以讓瀏覽器不會出現非預期結果讓程式的可靠性提昇

底下為測試結果

42 nodejs http路徑建立 33

Nodejs中文電子書

43 nodejs檔案讀取

前面已經介紹如何使用路由(rount)做出不同的回應實際應用只有在瀏覽器只有輸出幾個文字資料總是不夠的在本章節中將介紹如何使

用檔案讀取輸出檔案資料讓使用者在前端瀏覽器也可以讀取到完整的

html css javascript檔案輸出

檔案管理最重要的部分就是 lsquoFile system lthttpnodejsorgdocslatestapifshtmlgtlsquo這個模組此模組可以針對檔案做管理監控讀取等行為裡面有許多預設的方法底下是檔案輸出的基本範例底下會有兩個檔案

第一個是靜態 html檔案

ltDOCTYPE htmlgt

lthtml xmlns=rdquohttpwwww3org1999xhtmlrdquo xmllang=rdquozh-CNrdquo lang=rdquozh-CNrdquogt

ltheadgt

ltmeta http-equiv=rdquoContent-Typerdquo content=rdquotexthtml charset=UTF-7rdquo gt

lttitlegtnodejs index html filelttitlegt

ltheadgt

ltbodygt

34 4 Nodejs基礎

Nodejs中文電子書

lth1gtnodejs index html filelth1gt

ltbodygt

lthtmlgt

另一個為 nodejs程式

var fs = require(rdquofsrdquo)

filename = rdquostaticindexhtmlrdquo

encode = rdquoutf8rdquo

fsreadFile(filename encode function(err file)

consolelog(file)

)

一開始直接載入 le system模組 載入名稱為 fs讀取檔案主要使

用的方法為 readFile裡面以三個參數路徑 ( le path) 編碼方式 (encoding)

回應函式 (callback)路徑必須要設定為靜態 html所在位置才能指定到正確的檔案靜態檔案的編碼方式也必須正確這邊使用靜態檔案的編

碼為 utf8如果編碼設定錯誤nodejs讀取出來檔案結果會使用 byte raw格式輸出如果錯誤編碼格式會導致輸出資料為 byte raw

回應函式中裡面會使用兩個變數error為錯誤資訊如果讀取的檔案不存在或者發生錯誤error數值會是 true如果成功讀取資料 error將會是 falsecontent則是檔案內容資料讀取後將會把資料全數丟到 content這個變數當中

最後程式的輸出結果畫面如下

43 nodejs檔案讀取 35

Nodejs中文電子書

44 nodejs http靜態檔案輸出

前面已經了解如何讀取本地端檔案接下來將配合 http伺服器路由讓每個路由都能夠輸出相對應的靜態 html檔案首先新增加幾個靜態 html檔案

ltDOCTYPE htmlgt

lthtml xmlns=rdquohttpwwww3org1999xhtmlrdquo xmllang=rdquozh-CNrdquo lang=rdquozh-CNrdquogt

ltheadgt

ltmeta http-equiv=rdquoContent-Typerdquo content=rdquotexthtml charset=UTF-7rdquo gt

lttitlegtnodejs index html filelttitlegt

ltheadgt

ltbodygt

lth1gtnodejs index html filelth1gt

ltbodygt

lthtmlgt

ltDOCTYPE htmlgt

lthtml xmlns=rdquohttpwwww3org1999xhtmlrdquo xmllang=rdquozh-CNrdquo lang=rdquozh-CNrdquogt

ltheadgt

ltmeta http-equiv=rdquoContent-Typerdquo content=rdquotexthtml charset=UTF-7rdquo gt

lttitlegtnodejs test html filelttitlegt

ltheadgt

ltbodygt

lth1gtnodejs test html filelth1gt

ltbodygt

lthtmlgt

ltDOCTYPE htmlgt

lthtml xmlns=rdquohttpwwww3org1999xhtmlrdquo xmllang=rdquozh-CNrdquo lang=rdquozh-CNrdquogt

ltheadgt

ltmeta http-equiv=rdquoContent-Typerdquo content=rdquotexthtml charset=UTF-7rdquo gt

lttitlegtnodejs static html filelttitlegt

ltheadgt

ltbodygt

lth1gtnodejs static html filelth1gt

ltbodygt

lthtmlgt

36 4 Nodejs基礎

Nodejs中文電子書

準備一個包含基本路由功能的 http伺服器

var server

ip = rdquo127001rdquo

port = 1337

http = require(rsquohttprsquo)

url = require(rsquourlrsquo)

server = httpcreateServer(function (req res)

var path = urlparse(requrl)

)

serverlisten(port ip)

consolelog(rdquoServer running at httprdquo + ip + rdquordquo + port)

加入 le system 模組使用 readFile 的功能將這一段程式放置於

createServer的回應函式中

fsreadFile(filePath encode function(err file)

)

readFile的回應函式裡面加入頁面輸出讓瀏覽器可以正確讀到檔案在這邊我們設定讀取的檔案為 html 靜態檔案所以 Content-type 設定為texthtml讀取到檔案的內容將會正確輸出成 html靜態檔案

fsreadFile(filePath encode function(err file)

reswriteHead(200 rsquoContent-Typersquo rsquotexthtmlrsquo)

reswrite(file)

resend()

)

到這邊為止基本的程式內容都已經完成剩下一些細節的調整首先

路徑上必須做調整目前的靜態檔案全部都放置於 static資料夾底下設定一個變數來記住資料夾位置

接著將瀏覽器發出要求路徑與資料夾組合讀取正確 html靜態檔案使用者有可能會輸入錯誤路徑所以在讀取檔案的時候要加入錯誤處理

同時回應 404伺服器無法正確回應的 http header格式

加入這些細節的修改一個基本的 http 靜態 html輸出伺服器就完成了完整程式碼如下

44 nodejs http靜態檔案輸出 37

Nodejs中文電子書

var server

ip = rdquo127001rdquo

port = 1337

http = require(rsquohttprsquo)

fs = require(rdquofsrdquo)

folderPath = rdquostaticrdquo

url = require(rsquourlrsquo)

path

filePath

encode = rdquoutf8rdquo

server = httpcreateServer(function (req res)

path = urlparse(requrl)

filePath = folderPath + pathpathname

fsreadFile(filePath encode function(err file)

if (err)

reswriteHead(404 rsquoContent-Typersquo rsquotextplainrsquo)

resend()

return

reswriteHead(200 rsquoContent-Typersquo rsquotextapplicationrsquo)

reswrite(file)

resend()

)

)

serverlisten(port ip)

consolelog(rdquoServer running at httprdquo + ip + rdquordquo + port)

45 nodejs http GET資料擷取

http伺服器中除了路由之外另一個最常使用的方法就是擷取 GET資料本單元將會介紹如何透過基本 http伺服器擷取瀏覽器傳來的要求擷取 GET資料

在 http協定中GET參數都是藉由 URL從瀏覽器發出要求送至伺服器

38 4 Nodejs基礎

Nodejs中文電子書

端基本的傳送網址格式可能如下

http127001testsend=1amptest=2

上面這段網址裡面的 GET參數就是 send而這個變數的數值就為 1如果想要在 http伺服器取得 GET資料需要在瀏覽器給予的要求 (request)做處理

首先需要載入 query string這個模組這個模組主要是用來將字串資料

過濾後轉換成javascript物件

qs = require(rsquoquerystringrsquo)

接著在第一階段利用 url模組過濾瀏覽器發出的 URL資料後將回應的物件裡面的 query這個變數是一個字串值資料過濾後如下

send=1amptest=2

透過 query string使用 parse這個方法將資料轉換成 javascript物件就表示 GET的資料已經被伺服器端正式擷取下來

path = urlparse(requrl)

parameter = qsparse(pathquery)

整個 nodejs http GET參數完整擷取程式碼如下

var server

ip = rdquo127001rdquo

port = 1337

http = require(rsquohttprsquo)

qs = require(rsquoquerystringrsquo)

url = require(rsquourlrsquo)

server = httpcreateServer(function (req res)

var path = urlparse(requrl)

parameter = qsparse(pathquery)

consoledir(parameter)

reswriteHead(200 rsquoContent-Typersquo rsquotextplainrsquo)

reswrite(rsquoBrowser test GET parameternrsquo)

resend()

)

45 nodejs http GET資料擷取 39

Nodejs中文電子書

serverlisten(port ip)

consolelog(rdquoServer running at httprdquo + ip + rdquordquo + port)

程式運作之後由瀏覽器輸入要求網址之後nodejs伺服器端回應資料為

Server running at http1270011337

send rsquo1rsquo test rsquo1rsquo

46 本章結語

前面所解說的部份一大部分主要是處理 http伺服器基本問題雖然在某些部分有牽扯到 http 伺服器基本運作原理主要還是希望可以藉由這些基本範例練習 nodejs練習回應函式與語法串接的特點習慣編寫javascript風格程式當然直接這樣開發 nodejs是非常辛苦的接下來在模組實戰開發的部份將會介紹特定的模組一步一步帶領各位從無到有進行

nodejs應用程式開發

40 4 Nodejs基礎

5

NPM套件管理工具

npm全名為 Node Package Manager是 Nodejs的套件(package)管理工具類似 Perl的 ppm或 PHP的 PEAR等安裝 npm後使用npm install

module name指令即可安裝新套件維護管理套件的工作會更加輕鬆

npm可以讓Nodejs的開發者直接利用擴充線上的套件庫(packagesregistry)加速軟體專案的開發npm提供很友善的搜尋功能可以快速找到安裝需要的套件當這些套件發行新版本時npm也可以協助開發者自動更新這些套件

npm不僅可用於安裝新的套件它也支援搜尋列出已安裝模組及更新的功能

51 安裝 NPM

Nodejs在 063版本開始內建 npm讀者安裝的版本若是此版本或更新的版本就可以略過以下安裝說明

若要檢查 npm是否正確安裝可以使用以下的指令

npm -v

41

Nodejs中文電子書

執行結果說明

若 npm正確安裝執行 npm -v將會看到類似 110-2的版本訊息

若讀者安裝的 Nodejs版本比較舊或是有興趣嘗試自己動手安裝 npm工具則可以參考以下的說明

安裝於Windows系統

Nodejs for Windows 於 062 版開始內建 npm使用 nodejsorg 官方提供的安裝程式不需要進一步的設定就可以立即使用 npm指令對於Windows的開發者來說大幅降低環境設定的問題與門檻

除了使用 Nodejs內建的 npm讀者也可以從 npm官方提供的以下網址

httpnpmjsorgdist

這是由 npm提供的 Fancy Windows Install版本請下載壓縮檔(例如npm-110-3zip)並將壓縮檔內容解壓縮至 Nodejs的安裝路徑(例如CProgram Filesnodejs)

解壓縮後在 Nodejs的安裝路徑下應該有以下的檔案及資料夾

bull npmcmd(檔案)

bull node modules(資料夾)

安裝於 Linux系統

Ubuntu Linux的使用者可以加入 NPM Unoffcial PPA這個 repository即可使用 apt-get完成 npm安裝

42 5 NPM套件管理工具

Nodejs中文電子書

Ubuntu Linux使用 apt-get安裝 npm

sudo apt-get install python-software-properties

sudo add-apt-repository ppagias-kay-leenpm

sudo apt-get update

sudo apt-get install npm

npm官方提供的安裝程式 installsh可以適用於大多數的 Linux系統使用這個安裝程式請先確認

1 系統已安裝 curl工具(請使用 curl --version查看版本訊息)

2 已安裝 Nodejs並且 PATH正確設置

3 Nodejs的版本必須大於 04x

以下為 npm提供的安裝指令

curl httpnpmjsorginstallsh | sh

安裝成功會看到如下訊息

installsh安裝成功的訊息

npm10105 homeUSERNAMElocalnodelibnode_modulesnpm

It worked

安裝於Mac OS X

建議採用與 Nodejs相同的方式進行 npm的安裝例如使用MacPorts安裝 Nodejs就同樣使用MacPorts安裝 npm這樣對日後的維護才會更方便容易

使用 MacPorts安裝 npm是本書比較建議的方式它可以讓 npm的安裝移除及更新工作自動化將會幫助開發者節省寶貴時間

51 安裝 NPM 43

Nodejs中文電子書

安裝MacPorts的提示

在MacPorts網站可以取得 OS X系統版本對應的安裝程式(例如 106或 107)httpwwwmacportsorg安裝過程會詢問系統管理者密碼使用預設的選項完成安裝即可安

裝MacPorts之後在終端機執行 port -v將會看到MacPorts的版本訊息

安裝 npm之前先更新MacPorts的套件清單以確保安裝的 npm是最新版本

sudo port -d selfupdate

接著安裝 npm

sudo port install npm

若讀者的 Nodejs並非使用MacPorts安裝則不建議使用MacPorts安裝npm因為 MacPorts會自動檢查並安裝相依套件而 npm相依 nodejs所以MacPorts也會一併將 nodejs套件安裝造成先前讀者使用其它方式安裝的 nodejs被覆蓋

讀者可以先使用 MacPorts安裝 curl(sudo port install curl)

再參考 Linux的 installsh安裝方式即可使用 npm官方提供的安裝程式

NPM安裝後測試

npm是指令列工具(command-line tool)使用時請先打開系統的文字終端機工具

測試 npm安裝與設定是否正確請輸入指令如下

npm -v

或是

npm --version

如果 npm已經正確安裝設定就會顯示版本訊息

44 5 NPM套件管理工具

Nodejs中文電子書

執行結果(範例)

110-2

52 使用 NPM安裝套件

npm目前擁有超過 6000種套件(packages)可以在 npm registry使用關鍵字搜尋套件

httpsearchnpmjsorg

舉例來說在關鍵字欄位輸入「coffee-script」下方的清單就會自動列出包含 coffee-script關鍵字的套件

接著我們回到終端機模式的操作npm的指令工具本身就可以完成套

件搜尋的任務

例如以下的指令同樣可以找出 coffee-script相關套件

npm search coffee-script

以下是搜尋結果的參考畫面

52 使用 NPM安裝套件 45

Nodejs中文電子書

找到需要的套件後(例如 express)即可使用以下指令安裝

npm install coffee-script

值得注意的一點是使用 npm install會將指定的套件安裝在工

作目錄(Working Directory)的 node modules資料夾下

以Windows為例如果執行 npm install的目錄位於

Cproject1

那麼 npm將會自動建立一個 node modules的子目錄(如果不存在)

Cproject1node modules

並且將下載的套件放置於這個子目錄例如

Cproject1node modulescoffee-script

這個設計讓專案可以個別管理相依的套件並且可以在專案佈署或發

行時將這些套件(位於 node modules)一併打包方便其它專案的使用者不必再重新下載套件

這個 npm install的預設安裝模式為 local(本地)只會變更當前專案的資料夾不會影響系統

另一種安裝模式稱為 global(全域)這種模式會將套件安裝到系統資

料夾也就是 npm安裝路徑的 node modules資料夾例如

CProgram Filesnodejsnode modules

46 5 NPM套件管理工具

Nodejs中文電子書

是否要使用全域安裝可以依照套件是否提供新指令來判斷舉例來說express 套件提供 express 這個指令而 coffee-script 則提供 coffee

指令

在 local 安 裝 模 式 中這 些 指 令 的 程 式 檔 案會 被 安 裝 到node modules的 bin這個隱藏資料夾下除非將bin的路徑加入 PATH環境變數否則要執行這些指令將會相當不便

為了方便指令的執行我們可以在 npm install 加上 -g 或 --

global參數啟用 global安裝模式例如

npm install -g coffee-script

npm install -g express

使用 global安裝模式需要注意執行權限的問題若權限不足可能會出現類似以下的錯誤訊息

npm ERR Error EACCES permission denied rsquorsquo

npm ERR

npm ERR Please try running this command again as rootAdministrator

要獲得足夠得執行權限請參考以下說明

bull Windows 7 或 2008 以上在「命令提示字元」的捷徑按右鍵選擇「以系統管理員身分執行」執行 npm指令時就會具有 Administrator身分

bull Mac OS X或 Linux系統可以使用 sudo指令例如

sudo npm install -g express

bull Linux系統可以使用 root權限登入或是以「sudo su -」切換成 root身分(使用 root權限操作系統相當危險因此並不建議使用這種方式)

若加上 -g參數使用 npm install -g coffee-script完成安裝

後就可以在終端機執行 coffee指令例如

coffee -v

52 使用 NPM安裝套件 47

Nodejs中文電子書

執行結果(範例)

CoffeeScript version 120

53 套件的更新及維護

除了前一節說明的 search 及 install 用法npm 還提供其他許多指令(commands)

使用 npm help可以查詢可用的指令

npm help

執行結果(部分)

where ltcommandgt is one of

adduser apihelp author bin bugs c cache completion

config deprecate docs edit explore faq find get

help help-search home i info init install la link

list ll ln login ls outdated owner pack prefix

prune publish r rb rebuild remove restart rm root

run-script s se search set show star start stop

submodule tag test un uninstall unlink unpublish

unstar up update version view whoami

使用 npm help command可以查詢指令的詳細用法例如

npm help list

接下來本節要介紹開發過程常用的 npm指令

使用 list可以列出已安裝套件

npm list

48 5 NPM套件管理工具

Nodejs中文電子書

執行結果(範例)

-- coffee-script120

- express256

- connect185

| -- formidable108

-- mime124

-- mkdirp007

-- qs041

檢視某個套件的詳細資訊例如

npm show express

升級所有套件(如果該套件已發佈更新版本)

npm update

升級指定的套件

npm update express

移除指定的套件

npm uninstall express

54 使用 packagejson

對於正式的 Nodejs專案可以建立一個命名為 packagejson的設

定檔(純文字格式)檔案內容參考範例如下

54 使用 packagejson 49

Nodejs中文電子書

packagejson(範例)

rdquonamerdquo rdquoapplication-namerdquo

rdquoversionrdquo rdquo001rdquo

rdquoprivaterdquo true

rdquodependenciesrdquo

rdquoexpressrdquo rdquo255rdquo

rdquocoffee-scriptrdquo rdquolatestrdquo

rdquomongooserdquo rdquogt= 253rdquo

其中 name與 version依照專案的需求設置

需要注意的是 dependencies的設定它用於指定專案相依的套件名

稱及版本

bull rdquoexpressrdquo rdquo255rdquo

代表此專案相依版本 255的 express套件

bull rdquocoffee-scriptrdquo rdquolatestrdquo

使用最新版的 coffee-script套件(每次更新都會檢查新版)

bull rdquomongooserdquo rdquogt= 253rdquo

使用版本大於 253的 mongoose套件

假設某個套件的新版可能造成專案無法正常運作就必須指定套件的

版本避免專案的程式碼來不及更新以相容新版套件通常在開發初期的

專案需要盡可能維持新套件的相容性(以取得套件的更新或修正)可以

用「gt=」設定最低相容的版本或是使用「latest」設定永遠保持最新

套件

50 5 NPM套件管理工具

6

Express介紹

在前面的 nodejs基礎當中介紹許多許多開設 http的使用方法及介紹以及許多基本的 nodejs基本應用

接下來要介紹一個套件稱為 express [Express](httpexpressjscom)這個套件主要幫忙解決許多 nodejs http server所需要的基本服務讓開發 httpservice變得更為容易不需要像之前需要透過層層模組(module)才有辦法開始編寫自己的程式

這個套件是由 TJ Holowaychuk 製作而成的套件裡面包含基本的路由處理 (route)http資料處理(GETPOSTPUT)另外還與樣板套件(jshtml template engine)搭配同時也可以處理許多複雜化的問題

61 Express安裝

安裝方式十分簡單只要透過之前介紹的 NPM就可以使用簡單的指令安裝指令如下

這邊建議需要將此套件安裝成為全域模組方便日後使用

51

Nodejs中文電子書

62 Express基本操作

express的使用也十分簡單先來建立一個基本的 hello world

var app = require(rsquoexpressrsquo)createServer()

port = 1337

applisten(port)

appget(rsquorsquo function(req res)

ressend(rsquohello worldrsquo)

)

consolelog(rsquostart express servernrsquo)

可以從上面的程式碼發現基本操作與 nodejs http的建立方式沒有太大差異主要差在當我們設定路由時可以直接透過 appget方式設定回應與接受方式

63 Express路由處理

Express對於 http服務上有許多包裝讓開發者使用及設定上更為方便例如有幾個路由設定那我們就統一藉由 appget來處理

Create http server

appget(rsquorsquo function(req res)

ressend(rsquohello worldrsquo)

)

appget(rsquotestrsquo function(req res)

ressend(rsquotest renderrsquo)

)

appget(rsquouserrsquo function(req res)

ressend(rsquouser pagersquo)

)

52 6 Express介紹

Nodejs中文電子書

如上面的程式碼所表示appget可以帶入兩個參數第一個是路徑名稱設定第二個為回應函式 (call back function)回應函式裡面就如同之前的 createServer方法裡面包含 requestresponse兩個物件可供使用使用者就可以透過瀏覽器輸入不同的 url切換到不同的頁面顯示不同的結果

路由設定上也有基本的配對方式讓使用者從瀏覽器輸入的網址可以

是一個變數只要符合型態就可以有對應的頁面產出例如

Create http server

appget(rsquouseridrsquo function(req res)

ressend(rsquouser rsquo + reqparamsid)

)

appget(rsquonumberrsquo function(req res)

ressend(rsquonumber rsquo + reqparamsnumber)

)

裡 面 使 用 到number 從 網 址 輸 入 之 後 就 可 以 直 接 使 用reqparamsnumber 取得所輸入的資料變成 url 參數使用當然前面也是可以加上路徑的設定userid在瀏覽器上路徑必須符合userxxx透

過 reqparamsid就可以取到 xxx這個字串值

另外express參數處理也提供了路由參數配對處理也可以透過正規表示法作為參數設定

var app = require(rsquoexpressrsquo)createServer()

port = 1337

applisten(port)

appget(^ip((d23)((d23))((d23))((d23))) function(req res)

ressend(reqparams)

)

上面程式碼可以發現後面路由設定的型態是正規表示法裡面設定

格式為ip之後必須要加上 ip型態才會符合資料格式同時取得 ip資料已經由正規表示法將資料做分群因此可以取得 ip的四個數字

此程式執行之後可以透過瀏覽器測試輸入網址為 localhost3000ip25525510010可以從頁面獲得資料

63 Express路由處理 53

Nodejs中文電子書

此章節全部範例程式碼如下

overview

author Caesar Chi

blog clonnblogspotcom

version 20120226

create server

var app = require(rsquoexpressrsquo)createServer()

port = 1337

applisten(port)

normal style

appget(rsquorsquo function(req res)

ressend(rsquohello worldrsquo)

)

appget(rsquotestrsquo function(req res)

ressend(rsquotest renderrsquo)

)

parameter style

appget(rsquouseridrsquo function(req res)

ressend(rsquouser rsquo + reqparamsid)

)

appget(rsquonumberrsquo function(req res)

ressend(rsquonumber rsquo + reqparamsnumber)

)

REGX style

appget(^ip((d23)((d23))((d23))) function(req res)

ressend(reqparams)

)

appget(rsquorsquo function(req res)

ressend(rsquoPage not foundrsquo 404)

54 6 Express介紹

Nodejs中文電子書

)

consolelog(rsquostart express servernrsquo)

64 Express middleware

Express 裡面有一個十分好用的應用概念稱為 middleware可以透過middleware做出複雜的效果同時上面也有介紹 next方法參數傳遞就是靠 middleware的概念來傳遞參數讓開發者可以明確的控制程式邏輯

create http server

appuse(expressbodyParser())

appuse(expressmethodOverride())

appuse(expresssession())

上面都是一種 middleware的使用方式透過 appuse方式裡面載入函式執行方法回應函式會包含三個基本參數responserequestnext其中next表示下一個 middleware執行函式同時會自動將預設三個參數繼續帶往下個函式執行底下有個實驗

上面的片段程式執行後開啟瀏覽器連結上 localhost1337會發現伺服器回應結果順序如下

first middle ware

second middle ware

execute middle ware

end middleware function

從上面的結果可以得知剛才設定的 middleware都生效了在 appuse設定的 middleware是所有 url皆會執行方法如果有指定特定方法就可以使用 appget的 middleware設定在 appget函式的第二個參數就可以帶入函式或者是匿名函式只要函式裡面最後會接受 request response next這三個參數同時也有正確指定 next函式的執行時機最後都會執行到最後一個方法當然開發者也可以評估程式邏輯要執行到哪一個階段讓邏輯

可以更為分明

64 Express middleware 55

Nodejs中文電子書

65 Express路由應用

在實際開發上可能會遇到需要使用參數等方式混和變數一起使用

express裡面提供了一個很棒的處理方法 appall這個方式可以先採用基本路由配對再將設定為每個不同的處理方式開發者可以透過這個方式簡

化自己的程式邏輯

overview

author

version 20120226

create server

var app = require(rsquoexpressrsquo)createServer()

port = 1337

users = [

name rsquoClonnrsquo

name rsquoChirsquo

]

applisten(port)

appall(rsquouseridoprsquo function(req res next)

requser = users[reqparamsid]

if (requser)

next()

else

next(new Error(rsquocannot find user rsquo + reqparamsid))

)

appget(rsquouseridrsquo function(req res)

ressend(rsquoviewing rsquo + requsername)

)

appget(rsquouserideditrsquo function(req res)

ressend(rsquoediting rsquo + requsername)

)

56 6 Express介紹

Nodejs中文電子書

appget(rsquouseriddeletersquo function(req res)

ressend(rsquodeleting rsquo + requsername)

)

appget(rsquorsquo function(req res)

ressend(rsquoPage not foundrsquo 404)

)

consolelog(rsquostart express servernrsquo)

內部宣告一組預設的使用者分別給予名稱設定藉由 appall這個方法可以先將路由雛形建立再接下來設定 appget的路徑格式只要符合格式就會分配進入對應的方法中像上面的程式當中如果使用者輸入路徑為user0除了執行 appall程式之後執行 next方法就會對應到路徑設定為userid的這個方法當中如果使用者輸入路徑為user0edit就會執行到useridedit的對應方法

66 Express GET應用範例

我們準備一個使用 GET方法傳送資料的表單

ltDOCTYPE htmlgt

lthtmlgt

ltheadgt

ltmeta http-equiv=rdquoContent-Typerdquo content=rdquotexthtml charset=utf8rdquogt

lttitlegtNodejs 菜鳥筆記 (1)lttitlegt

ltlink rel=rdquostylesheetrdquo href=rdquocssstylecssrdquo type=rdquotextcssrdquo media=rdquoallrdquo gt

ltheadgt

ltbodygt

lth1gtNodejs 菜鳥筆記-註冊lth1gt

ltform id=rdquosignuprdquo method=rdquoGETrdquo action=rdquohttplocalhost3000Signuprdquogt

ltlabelgt 使用者名稱ltlabelgtltinput type=rdquotextrdquo id=rdquousernamerdquo name=rdquousernamerdquo gtltbrgt

ltlabelgt 電子郵件ltlabelgtltinput type=rdquotextrdquo id=rdquoemailrdquo name=rdquoemailrdquo gtltbrgt

ltinput type=rdquosubmitrdquo value=rdquo 註冊我的帳號rdquo gtltbrgt

ltformgt

ltbodygt

lthtmlgt

66 Express GET應用範例 57

Nodejs中文電子書

這個表單沒有什麼特別的地方我們只需要看第 9 行form 使用的method是 GET然後 action是rdquohttplocalhost3000Signupldquo等一下我們要來撰寫Signup這個 URL Path的處理程式

處理 Signup行為

我們知道所謂的 GET方法會透過 URL來把表單的值給帶過去以上面的表單來說到時候 URL會以這樣的形式傳遞

httplocalhost3000Signupusername=xxxampemail=xxx

所以要能處理這樣的資料必須有以下功能

bull 解析 URL

bull 辨別動作是 Signup

bull 解析出 username和 email

一旦能取得 username和 email的值程式就能加以應用了

處理 Signup的程式碼雛形

load module

var url = require(rsquourlrsquo)

urlData = urlparse(requrltrue)

action = urlDatapathname

reswriteHead(200 rdquoContent-Typerdquordquotexthtml charset=utf-8rdquo)

if (action === rdquoSignuprdquo)

user = urlDataquery

resend(rdquolth1gtrdquo + userusername + rdquo 歡迎您的加入 lth1gtltpgt 我們已經將會員啟用信寄至rdquo + useremail + rdquoltpgtrdquo)

首先需要加載 url module它是用來協助我們解析 URL的模組接著使用 urlparse方法第一個傳入 url字串變數也就是 requrl另外第二個參數的用意是設為 ture則引進 querystring模組來協助處理預設是 false它影響到的是 urlDataquery設為 true會傳回物件不然就只是一般的字串urlparse會將字串內容整理成一個物件我們把它指定給 urlData

action變數作為記錄 pathname這是我們稍後要來判斷目前網頁的動作是什麼接著先將 html表頭資訊 (Header)準備好再來判斷路徑邏輯如

58 6 Express介紹

Nodejs中文電子書

果是 Signup這個動作就把 urlDataquery裡的資料指定給 user然後輸出userusername和 useremail把使用者從表單註冊的資料顯示於頁面中

最後進行程式測試啟動 nodejs主程式之後開啟瀏覽器就會看到表單填寫完畢按下送出就可以看到結果了

完整 nodejs程式碼如下

var http = require(rsquohttprsquo)

url = require(rsquourlrsquo)

fs = require(rdquofsrdquo)

server

server = httpcreateServer(function (reqres)

var urlData

encode = rdquoutf8rdquo

filePath = rdquoviewexpress_get_example_formhtmlrdquo

action

urlData = urlparse(requrltrue)

action = urlDatapathname

reswriteHead(200 rdquoContent-Typerdquordquotexthtml charset=utf-8rdquo)

if (action === rdquoSignuprdquo)

user = urlDataquery

resend(rdquolth1gtrdquo + userusername + rdquo 歡迎您的加入 lth1gtltpgt 我們已經將會員啟用信寄至rdquo + useremail + rdquoltpgtrdquo)

else

fsreadFile(filePath encode function(err file)

reswrite(file)

resend()

)

)

serverlisten(3000)

consolelog(rsquoServer 跑起來了現在時間是rsquo + new Date())

66 Express GET應用範例 59

Nodejs中文電子書

67 Express POST應用範例

一開始準備基本的 html表單傳送內容以 POST方式form的 action屬性設定為 POST其餘 html內容與前一個範例應用相同

ltDOCTYPE htmlgt

lthtmlgt

ltheadgt

ltmeta http-equiv=rdquoContent-Typerdquo content=rdquotexthtml charset=utf8rdquogt

lttitlegtNodejs 菜鳥筆記 (1)lttitlegt

ltlink rel=rdquostylesheetrdquo href=rdquocssstylecssrdquo type=rdquotextcssrdquo media=rdquoallrdquo gt

ltheadgt

ltbodygt

lth1gtNodejs 菜鳥筆記-註冊lth1gt

ltform id=rdquosignuprdquo method=rdquoPOSTrdquo action=rdquohttplocalhost3000Signuprdquogt

ltlabelgt 使用者名稱ltlabelgtltinput type=rdquotextrdquo id=rdquousernamerdquo name=rdquousernamerdquo gtltbrgt

ltlabelgt 電子郵件ltlabelgtltinput type=rdquotextrdquo id=rdquoemailrdquo name=rdquoemailrdquo gtltbrgt

ltinput type=rdquosubmitrdquo value=rdquo 註冊我的帳號rdquo gtltbrgt

ltformgt

ltbodygt

lthtmlgt

nodejs的程式處理邏輯與前面 GET範例類似部分程式碼如下

qs = require(rsquoquerystringrsquo)

if (action === rdquoSignuprdquo)

formData = rsquorsquo

reqon(rdquodatardquo function (data)

formData += data

)

reqon(rdquoendrdquo function ()

user = qsparse(formData)

resend(rdquolth1gtrdquo + userusername + rdquo 歡迎您的加入 lth1gtltpgt 我們已經將會員啟用信寄至rdquo + useremail + rdquoltpgtrdquo)

)

60 6 Express介紹

Nodejs中文電子書

主要加入了rsquoquerystringrsquo這個moduel方便我們等一下解析由表單 POST回來的資料另外加入一個 formData的變數用來搜集待等一下表單回傳的資料前面的 GET範例我們只從 req拿出 url的資料這次要在利用req身上的事件處理

JavaScript 在訂閱事件時使用 addEventListener而 nodejs 使用的則是on這邊加上了監聽 data的事件會在瀏覽器傳送資料到Web Server時被執行參數是它所接收到的資料型態是字串

接著再增加 end的事件當瀏覽器的請求事件結束時它就會動作

由於瀏覽器使用 POST在上傳資料時會將資料一塊塊地上傳因為我們在監聽 data事件時透過 formData變數將它累加起來lt不過由於我們

上傳的資料很少一次就結束不過如果日後需要傳的是資料比較大的檔

案這個累加動作就很重要

當資料傳完就進到 end 事件中會用到 qsparse 來解析 formDataformData的內容是字串內容是

username=wordsmithampemail=wordsmith40somewhere

而 qsparse可以幫我們把這個 querystring轉成物件的格式也就是

username=wordsmithampemail=wordsmith40somewhere

一旦轉成物件並指定給 user之後其他的事情就和 GET方法時操作的一樣寫 response的表頭將內容回傳並將 userusername和 useremail代入到內容中

修改完成後接著執行 nodejs程式啟動 web server開啟瀏覽器進入表單測試看看POST的方式能否順利運作

完整程式碼如下

var http = require(rsquohttprsquo)

url = require(rsquourlrsquo)

fs = require(rdquofsrdquo)

qs = require(rsquoquerystringrsquo)

server

server = httpcreateServer(function (reqres)

var urlData

67 Express POST應用範例 61

Nodejs中文電子書

encode = rdquoutf8rdquo

filePath = rdquoviewexpress_post_example_formhtmlrdquo

formData

action

urlData = urlparse(requrltrue)

action = urlDatapathname

reswriteHead(200 rdquoContent-Typerdquordquotexthtml charset=utf-8rdquo)

if (action === rdquoSignuprdquo)

formData = rsquorsquo

reqon(rdquodatardquo function (data)

formData += data

)

reqon(rdquoendrdquo function ()

user = qsparse(formData)

resend(rdquolth1gtrdquo + userusername + rdquo 歡迎您的加入 lth1gtltpgt 我們已經將會員啟用信寄至rdquo + useremail + rdquoltpgtrdquo)

)

else

fsreadFile(filePath encode function(err file)

reswrite(file)

resend()

)

)

serverlisten(3000)

consolelog(rsquoServer 跑起來了現在時間是rsquo + new Date())

62 6 Express介紹

Nodejs中文電子書

68 Express AJAX應用範例

在 Nodejs要使用 Ajax傳送資料並且與之互動在接受資料的部份沒有太大的差別client端不是用 GET就是用 POST來傳資料重點在處理完後用 JSON格式回傳當然 Ajax不見得只傳 JSON格式有時是回傳一段 HTML碼不過後者對伺服器來說基本上就和前兩篇沒有差別了所以我們還是以回傳 JSON做為這一回的主題

這一回其實大多數的工作都會落在前端 Ajax上面前端要負責發送與接收資料並在接收資料後撤掉原先發送資料的表單並將取得的資料

改成 HTML格式之後放上頁面

首先先準備 HTML靜態頁面資料

ltDOCTYPE htmlgt

lthtmlgt

ltheadgt

lttitlegtNodejs 菜鳥筆記 (3)lttitlegt

ltlink rel=rdquostylesheetrdquo href=rdquocssstylecssrdquo type=rdquotextcssrdquo media=rdquoallrdquo gt

ltscript src=rdquohttpsajaxgoogleapiscomajaxlibsjquery171jqueryminjsrdquogtltscriptgt

ltheadgt

ltbodygt

lth1gtNodejs 菜鳥筆記-註冊 (用 Ajax)lth1gt

ltform id=rdquosignuprdquo action=rdquoSignuprdquo method=rdquoPOSTrdquogt

ltlabelgt 使用者名稱ltlabelgtltinput type=rdquotextrdquo id=rdquousernamerdquo name=rdquousernamerdquo gtltbrgt

ltlabelgt 電子郵件ltlabelgtltinput type=rdquotextrdquo id=rdquoemailrdquo name=rdquoemailrdquo gtltbrgt

ltinput type=rdquosubmitrdquo value=rdquo 註冊我的帳號rdquo gtltbrgt

ltformgt

ltdiv id=rdquoresultrdquogt

ltdivgt

ltscript type=rdquotextjavascriptrdquogt

$(function()$(rsquosignup input[type=rdquosubmitrdquo]rsquo)click(function(e)

var user =

userusername = $(rdquousernamerdquo)val()useremail = $(rdquoemailrdquo)val()

$post(rdquoSignuprdquouserfunction(data)greet(data)

)

68 Express AJAX應用範例 63

Nodejs中文電子書

epreventDefault()

)

var greet = function(msg)

var resultNode = $(rsquoresultrsquo)greeting = $(rsquolth2gtHirsquo+msgusername+rsquo 你的會員 id 是rsquo+ msgid +rsquo 我們會將會員啟動信寄至rsquo+msgemail+rsquolth2gtrsquo)

$(rsquosignuprsquo)hide()resultNodehtml(rdquordquo)

resultNodeappend(greeting)

)

ltscriptgt

ltbodygt

lthtmlgt

HTML頁面上準備了一個表單用來傳送註冊資料接著直接引用了Google CDN來載入 jQuery用來幫我們處理 Ajax的工作這次要傳送和接收的工作很大的變動都在 HTML頁面上的 JavaScript當中我們要做的事有 (相關 jQuery處理這邊不多做贅述指提起主要功能解說)

bull 用 jQUery取得 submit按鈕綁定它的 click動作

bull 取得表單 username和 email的值存放在 user這個物件中

bull 用 jQuery的 $post方法將 user的資料傳到 Server

bull 一旦成功取得資料後透過 greet這個 function組成回報給 user的訊息

bull 清空原本給使用者填資料的表單

bull 將 Server回傳的 usernameemail和 id這 3個資料組成回應的訊息

bull 將訊息放到原本表單的位置

經過以上的處理後一個 Ajax的表單的基本功能已經完成

接著進行 nodejs主要程式的編輯部分程式碼如下

var fs = require(rdquofsrdquo)

64 6 Express介紹

Nodejs中文電子書

qs = require(rsquoquerystringrsquo)

if (action === rdquoSignuprdquo)

formData = rsquorsquo

reqon(rdquodatardquo function (data)

formData += data

)

reqon(rdquoendrdquo function ()

var msg

user = qsparse(formData)

userid = rdquo123456rdquo

msg = JSONstringify(user)

reswriteHead(200 rdquoContent-Typerdquordquoapplicationjson charset=utf-8rdquordquoContent-Lengthrdquomsglength)

resend(msg)

)

這裡的程式和前面 POST範例基本上大同小異差別在

bull 幫 user的資料加上 id隨意存放一些文字進去讓 Server回傳的資料多於 Client端傳上來的不然會覺得 Server都沒做事

bull 增加了 msg 這個變數存放將 user 物件 JSON 文字化的結果JSONstringify這個轉換函式是 V8引擎所提供的如果你好奇的話

bull 大重點來了我們要告訴 Client端這次回傳的資料格式是 JSON所在 Content-type和 Content-Length要提供給 Client

Server很輕鬆就完成任務了最後進行程式測試啟動 nodejs主程式之後開啟瀏覽器就會看到表單填寫完畢按下送出就可以看到結果了

最後 nodejs本篇範例程式碼如下

var http = require(rsquohttprsquo)

url = require(rsquourlrsquo)

fs = require(rdquofsrdquo)

qs = require(rsquoquerystringrsquo)

server

68 Express AJAX應用範例 65

Nodejs中文電子書

server = httpcreateServer(function (reqres)

var urlData

encode = rdquoutf8rdquo

filePath = rdquoviewexpress_ajax_example_formhtmlrdquo

formData

action

urlData = urlparse(requrltrue)

action = urlDatapathname

if (action === rdquoSignuprdquo)

formData = rsquorsquo

reqon(rdquodatardquo function (data)

formData += data

)

reqon(rdquoendrdquo function ()

var msg

user = qsparse(formData)

userid = rdquo123456rdquo

msg = JSONstringify(user)

reswriteHead(200 rdquoContent-TyperdquordquoapplicationjsonrdquordquoContent-Lengthrdquomsglength)

resend(msg)

)

else

fsreadFile(filePath encode function(err file)

reswriteHead(200 rdquoContent-Typerdquordquotexthtml charset=utf-8rdquo)

reswrite(file)

resend()

)

)

serverlisten(3000)

66 6 Express介紹

Nodejs中文電子書

consolelog(rsquoServer 跑起來了現在時間是rsquo + new Date())

69 原始資料提供

bull [NodeJS初學者筆記 (1)-用 GET傳送資料] (httpithelpithomecomtwquestion10087402)

bull [NodeJS初學者筆記 (2)-用 POST傳送資料] (httpithelpithomecomtwquestion10087489)

bull [NodeJS 初學者筆記 (3)-用 Ajax 傳送資料] (httpithelpithomecomtwquestion10087627)

69 原始資料提供 67

Nodejs中文電子書

68 6 Express介紹

7

CoffeeScript

bull Spine

bull Hem

bull Spineapp

Letrsquos Have a Cup of CoffeeScript

bull es5-shim ECMAScript 5 compatibility shims for legacy JavaScript engines

ESnext

node-iform - A middleware for node-validator httpsgithubcomguileennode-iform

69

Nodejs中文電子書

70 7 CoffeeScript

8

製作一個 Hubot的 Plurk Adapter

81 應用事項提醒

此應用範例以CoffeeScript編寫主要程式架構採用Github釋放的Hubot專案以下有幾個主要注意事項請讀者注意

bull Hubot 一開始的架構除了內建的兩個 Adapter 之外其餘都要以Nodejs的Module方式才能運作

bull Hubot的 binhubot裡面寫著 npm install所以不管你怎麼改原始碼也不能改變第一點的狀況

其實上述都在討論同一件事情NodeJS的模組而且模組不能設定相對路徑之類的來安裝一定要包裝成 npm相符和格式才有辦法正確執行

82 建立 Adapter

首先需要準備兩個檔案

bull packagejson

bull plurkcoffee

71

Nodejs中文電子書

有這兩個就足以變成 NodeJS的模組了在開始 Coding之前先來設定一下 packagejson相依性

rdquonamerdquo rdquohubot-plurkrdquo

rdquoversionrdquo rdquo011rdquo

rdquomainrdquo rdquoplurkrdquo

rdquodependenciesrdquo

rdquohubotrdquo rdquogt=205rdquo

rdquooauthrdquo rdquordquo

rdquocronrdquordquordquo

這邊會使用到NodeJS的 cron模組(Module)而登入 Plurk需要OAuth標準授權才行所以也需要加載 OAuth模組

注意Hubot在讀取非內建模組時會自動在前面加上 hubot-的前置

83 建立 Robot跟 API

本篇應用基本上程式碼大部分參考 Hubot - Twitter Adapter來製作主要差異只有在使用 OAuth這個模組Twitter有 Streaming可用而 Plurk則得用 Comet方式來達到即時讀取

plurkcoffee程式碼

Robot = require(rdquohubotrdquo)robot()

Adapter = require(rdquohubtordquo)adapter()

EventEmitter = require(rdquoeventsrdquo)EventEmitter

oauth = require(rdquooauthrdquo)

cronJob = require(rdquocronrdquo)CronJob

class Plurk exntends Adapter

class PlurkStreaming exnteds EventEmitter

72 8 製作一個 Hubot的 Plurk Adapter

Nodejs中文電子書

先弄個基本架構主要 Class皆參考 Twitter Adapter命名方式接著再將 Plurk這個 Class增加幾個Method基本上只要有 run send reply就夠了而 run用來做初始化的部分

class Plurk entends Adapter

send (plurk id strings⋯) -gt

reply (plurk id strings⋯) -gt

run -gt

看起來有點東西接著來處理主要的 Plurk API結合部分

class PlurkStreaming extends EventEmitter

consuctor (options) -gt

plurk (callback) -gt

觀察河道

getChannel -gt

取得 Comet 網址

reply (plurk id message) -gt

回噗

acceptFriends -gt

接受好友

get (path callback) -gt

GET 請求

post (path body callback)-gt

POST 請求(其實是裝飾)

request (method path body callback)-gt

主要的 OAuth 請求

comet (server callback)-gt

噗浪的 Comet 傳回是 JavaScript Callback 要另外處理後才會變成 JSON

然後把注意力集中到 constructor上先把建構子弄好

constructor (options) -gt

super()

if optionskey and optionssecret and optionstoken and optionstoken secret

key = optionskey

secret = optionssecret

token = optionstoken

token secret = optionstoken secret

83 建立 Robot跟 API 73

Nodejs中文電子書

建立 OAuth 連接

consumer = new oauthOAuth(

rdquohttpwwwplurkcomOAuthrequest tokenrdquo

rdquohttpwwwplurkcomOAuthaccess tokenrdquo

key

secret

rdquo10rdquo

rdquohttpwwwplurkcomOAuthauthorizerdquo

rdquoHMAC-SHA1rdquo

)

domain = rdquowwwplurkcomrdquo

初始化取得 Comet 網址

do getChannel

else

throw new Error(rdquo 參數不足需要 Key Secret Token Token Secretrdquo)

接著來處理 request這個 method

request (method path body callback) -gt

記錄一下這次的 Request

consolelog(rdquohttpdomainpathrdquo)

Callback 這邊先不丟進去要用另一種方式處理

request = consumerget(rdquohttpdomainpathrdquo token token secret null)

requeston rdquoresponserdquo (res) -gt

reson rdquodatardquo (chunk) -gt

parseResponse(chunk+rsquorsquo callback)

reson rdquoendrdquo (data) -gt

consolelog rdquoEnd Request pathrdquo

reson rdquoerrorrdquo (data) -gt

consolelog rdquoError rdquo + data

requestend()

處理資料

parseResponse = (data callback) -gt

if datalength gt 0

用 TryCatch 避免處理 JSON 出錯導致整個中斷

try

callback null JSONparse(data)

catch err

74 8 製作一個 Hubot的 Plurk Adapter

Nodejs中文電子書

consolelog(rdquoError Parse JSONrdquo + data err)

繼續執行

callback null data ||

大致上就是這樣上面程式的架構已經將整個 Hubot Plurk Adapter完成因為在測試時竟然因為噗浪 Lag而沒讀到完整的 Comet資料然後造成程式異常為了避免這個問題發生因此需要加上為了完美呈現需要再

加上 Comet的處理所以要使用到 EventEmitter的功能

comet (server callback) -gt

在 Callback 裡面會找不到自身所以設定區域變數

self =

記錄一下這次的 Request

consolelog(rdquo[Comet] serverrdquo)

Callback 這邊先不丟進去要用另一種方式處理

request = consumerget(rdquohttpdomainpathrdquo token token secret null)

requeston rdquoresponserdquo (res) -gt

reson rdquodatardquo (chunk) -gt

parseResponse(chunk+rsquorsquo callback)

reson rdquoendrdquo (data) -gt

consolelog rdquoEnd Request pathrdquo

請求結束發出事件通知可以進行下一次請求

selfemit rdquonextPlurkrdquo

reson rdquoerrorrdquo (data) -gt

consolelog rdquoError rdquo + data

requestend()

處理資料

parseResponse = (data callback) -gt

if datalength gt 0

用 trycatch 避免失敗中斷

try

去掉 JavaScript 的 Callback

data = datamatch(CometChannelscriptCallback((+))s)

jsonData = rdquordquo

if data

83 建立 Robot跟 API 75

Nodejs中文電子書

jsonData = JSONparse(data[1])

else

如果沒有任何 Match 嘗試直接 parse

jsonData = JSONparse(data)

catch err

consolelog(rdquo[Comet] Errorrdquo data err)

用 TryCatch 避免處理 JSON 出錯導致整個中斷

try

只傳入 json 的 data 部分

callback null jsonDatadata

catch err

consolelog(rdquo[Comet]Error Parse JSONrdquo + data err)

繼續執行

callback null data ||

後面的 get跟 post就簡單多了

get (path callback) -gt

request(rdquoGETrdquo path null callback)

post (path body callback) -gt

request(rdquoPOSTrdquo path body callback)

接著處理取的 Comet網址的 getChannel

getChannel -gt

self =

get rdquoAPPRealtimegetUserChannelrdquo (error data) -gt

if error

檢查是否有 comet server

if datacomet server

selfchannel = datacomet server

如果沒有 Channel Ready 就嘗試連接會失敗

selfemit(rsquochannel readyrsquo)

那麼先來處理 Plurk Adaper好處理的部份

send (plruk id strings⋯)-gt

跟 Reply 一樣直接交給 reply 做

reply plurk id strings⋯

76 8 製作一個 Hubot的 Plurk Adapter

Nodejs中文電子書

reply (plurk id strings⋯) -gt

stringsforEach (message) =gt

botreply(plruk id message)

接著把 run處理好就可以上線運作摟

run -gt

self =

options =

key processenvHUBOT PLURK KEY

secret processenvHUBOT PLURK SECRET

token processenvHUBOT PLURK TOKEN

token secret processenvHUBOT PLURK TOKEN SECRET

創建剛剛的 API

bot = new PlurkStreaming(options)

依照 Twitter 的 new RobotTextMessage 會沒有反應所以參考 hubot-minecraft 的方式

r = robotconstructor

處理噗浪河道訊息

doPlurk = (data)-gt

檢查是否為回噗

if dataresponse

datacontent raw = dataresponsecontent raw

datauser id = dataresponseuser id

確定有噗浪 ID 跟訊息

if dataplurk id and datacontent raw

selfreceive new rTextMessage(dataplurk id datacontent raw)

取得 Comet Server 完成開始第一次 Comet 連接

boton rdquochannel readyrdquo () -gt

botplurk selfdoPlurk

上一次 Comet 完成繼續 Polling

boton rdquonextPlurkrdquo ()-gt

botplurk selfdoPlurk

定時接受好友邀請

do botacceptFriends

bot = bot

83 建立 Robot跟 API 77

Nodejs中文電子書

終於完成 AdapterHubot專案裡面的 scripts資料夾內是互動部分不需要像 Adapter如此大費周章處理只新增檔案並且設計好對白之後就會回噗了我開發用的機器人在此大家可以去跟他玩玩[httpplurkcomelct9620 bot](httpplurkcomelct9620 bot)

84 原始資料提供

bull [製 作 一 個 Hubot 的 噗 浪 Adapter](http revo-skillfrosttw blog20120318create-a-hubot-plurk-adapter)

78 8 製作一個 Hubot的 Plurk Adapter

9

Nodejs好用工具介紹

91 logio

Real-time log monitoring in your browser

httplogioorg

nodejs (gt=0412 lt=0611)

npm config set unsafe-perm true

npm install -g --prefix=usrlocal logio

logio server start

httplocalhost8998

79

Nodejs中文電子書

80 9 Nodejs好用工具介紹

10

精選文章

101 Express

bull 使用 NodeJS + Express從 GETPOST Request取值 [邀稿中]

bull link2 [邀稿中]

bull link3 [邀稿中]

81

Nodejs中文電子書

82 10 精選文章

11

參考資源

111 Nodejs書籍

bull Programming Nodejs April 2012 (est) Travis Swicegood Pragmatic Bookshelf

bull e Little Book on CoffeeScript January 2012 Alex MacCaw OrsquoReilly Media

bull Node for Front-End Developers January 2012 Garann Means OrsquoReilly Media

bull Building Hypermedia APIs with HTML5 and Node November 2011 MikeAmundsen OrsquoReilly Media

bull Node Web Development August 2011 David Herron PacktPub

bull CoffeeScript Accelerated JavaScript Development July 2011 Trevor BurnhamPragmatic Bookshelf

bull Getting Started with GEO CouchDB and Nodejs July 2011 Mick omp-son OrsquoReilly Media

bull What Is Node July 2011 Brett McLaughlin OrsquoReilly Media

bull Node Up and Running May 2011 Tom Hughes-Croucher Mike WilsonOrsquoReilly Media

83

Nodejs中文電子書

bull Hands-on Nodejs e Nodejs introduction and API reference May 2011 Pe-dro Teixeira LeanPub

bull e Node Beginner Book Manuel Kiessling

bull Mastering Nodejs visionmedia

112 Nodejs影音教學

bull e Node Sessions e Best of OSCON 2011 August 2011 OrsquoReilly Media(Video)

bull Tom Hughes-Croucher on Node March 2011 OrsquoReilly Media (Video)

113 Nodejs教學網站

bull Node Tuts

114 Nodejs課程

bull Introduction To Nodejs Van Nguyen CodeLesson

84 11 參考資源

Page 4: Nodejs Wiki

3 Nodejs安裝與設定 25

31 Ubuntu Linux 2532 Other Linux 2633 Windows 26

4 Nodejs基礎 29

41 nodejs http伺服器建立 2942 nodejs http路徑建立 3143 nodejs檔案讀取 3444 nodejs http靜態檔案輸出 3645 nodejs http GET資料擷取 3846 本章結語 40

5 NPM套件管理工具 41

51 安裝 NPM 4152 使用 NPM安裝套件 4553 套件的更新及維護 4854 使用 packagejson 49

6 Express介紹 51

61 Express安裝 5162 Express基本操作 5263 Express路由處理 5264 Express middleware 5565 Express路由應用 5666 Express GET應用範例 5767 Express POST應用範例 6068 Express AJAX應用範例 6369 原始資料提供 67

7 CoffeeScript 69

8 製作一個 Hubot的 Plurk Adapter 71

81 應用事項提醒 7182 建立 Adapter 71

ii

83 建立 Robot跟 API 7284 原始資料提供 78

9 Nodejs好用工具介紹 79

91 logio 79

10 精選文章 81

101 Express 81

11 參考資源 83

111 Nodejs書籍 83112 Nodejs影音教學 84113 Nodejs教學網站 84114 Nodejs課程 84

iii

iv

關於本書

這是一本關於 Nodejs技術的開放源碼電子書我們使用 GitHub維護電子書內容並交由 ContPub(Continuous Publishing)系統自動線上發佈本書提供 PDFEPUBMOBI及 HTML等格式您除了可以在網站檢視本書所有內容也可以將電子書下載至閱讀器保存

本書的線上閱讀網址與 GitHub資料同步更新

httpbooknodejstw

如果您想要取得本書的其他格式可以從 ContPub的電子書專頁下載最新版本

httpcontpuborgreadnodejs-wiki-book

本書適合 Nodejs初學者至進階開發者也歡迎您在學習時一起參與本書內容撰寫

授權

Nodejs台灣社群協作電子書採用創用 CC姓名標示 -非商業性授權您不必為本書付費

Nodejs Wiki Book book is licensed under the Attribution-NonCommercial 30Unported license You should not have paid for this book

您可以複製散佈及修改本書內容但請勿將本書用於商業用途

您可以在以下網址取得授權條款全文

1

Nodejs中文電子書

httpcreativecommonsorglicensesby-nc30legalcode

作者

本書由 Nodejs Taiwan社群成員協作以下名單依照字母排序

bull Caesar Chi (clonn)

bull Fillano Feng ( llano)

bull Kyle Lin (lyhcode)

Nodejs Taiwan是一個自由開放的技術學習社群我們歡迎您加入一起學習研究及分享

下載電子書

線上閱讀本書

httpbooknodejstw

PDF格式適合一般電腦及 7吋以上平板電腦閱讀

httpcontpuborgdownloadnodejs-wiki-bookpdf

EPUB格式適合 iPadiPhone行動裝置閱讀

httpcontpuborgdownloadnodejs-wiki-bookepub

MOBI格式適合 Kindle電子書閱讀器

httpcontpuborgdownloadnodejs-wiki-bookmobi

原始碼

本書最新的原始碼(中文版)網址如下

httpgithubcomnodejs-twnodejs-wiki-book

2 關於本書

Nodejs中文電子書

01 精選文章收錄流程

精選文章的用意是鼓勵作者在自已的網誌發表 Nodejs 教學再由Nodejs Taiwan社群挑選系列文章列入電子書的精選文集

1 將文章標題及連結貼到「精選文章」分類

2 社群工作小組以 E-Mail通知作者文章列入精選並邀請將內文授權給Nodejs Taiwan電子書分享

3 作者同意後由工作小組負責整理圖文發佈至電子書

4 以 E-Mail寄出感謝函通知原作者文章已收錄依作者意願調整文章內容

5 於 Nodejs Taiwan首頁及粉絲專頁推薦作者的文章

01 精選文章收錄流程 3

Nodejs中文電子書

4 關於本書

前言

Nodejs是 JavaScript程式語言的開發框架由 04x至目前 v060版本核心上已經有許多變更當然目的只有一個就是讓開發變得更簡單速

度能夠更快這是所有人奮鬥的目標而 nodeJS 華文維基平台主力由nodeJStw一群喜好 javascript開發者共同主筆內文以中文為主希望能夠降低開發者學習門檻藉由我們拋磚引玉讓更多華人開發者共同學習

討論

CoffeeScript好好玩

5

Nodejs中文電子書

6 前言

1

Nodejs簡介

Nodejs是一個高效能易擴充的網站應用程式開發框架 (Web Applica-tion Framework)它誕生的原因是為了讓開發者能夠更容易開發高延展性的網路服務不需要經過太多複雜的調校效能調整及程式修改就能

滿足網路服務在不同發展階段對效能的要求

Ryan Dahl是NodeJS的催生者目前任職於 Joyent (主機託管服務公司)他開發 NodeJS的目的就是希望能解決 Apache在連線數量過高時緩衝區 (buffer)和系統資源會很快被耗盡的問題希望能建立一個新的開發框架以解決這個問題因此嘗試使用效能十分優秀的 V8 JavaScript Engine讓網站開發人員熟悉的 JavaScript程式語言也能應用於後端服務程式的開發並且具有出色的執行效能

JavaScript是功能強大的物件導向程式語言但是在 JavaScript的官方規格中主要是定義網頁 (以瀏覽器為基礎) 應用程式需要的應用程式介面(API)對 JavaScript程式的應用範圍有所侷限為使 JavaScript能夠在更多用途發展CommonJS規範一組標準函式庫 (standard library)使 JavaScript的應用範圍能夠和 RubyPython及 Java等語言同樣豐富撰寫 NodeJS的JavaScript程式碼符合 CommonJS規範可以使用 CommonJS API為基礎開發程式並且在不同的 CommonJS兼容 (compliant) JavaScript執行環境中程式碼具有可攜性

7

Nodejs中文電子書

瀏覽器的 JavaScript與實現 CommonJS規範的 NodeJS有何不同呢瀏覽器的 JavaScript 提供 XMLHttpRequest 讓程式可以和網頁伺服器建立資料傳輸連線但這通常只能適用於網站開發的需求因為我們只能用

XMLHttpRequest與網頁伺服器通訊卻無法利用它建立其他類型如 Telnet FTP NTP的伺服器通訊如果我們想開發網路服務程式例如 SMTP電子郵件伺服器就必須使用 Sockets建立 TCP (某些服務則用 UDP)監聽及連線其他程式語言如 PHPJavaPythonPerl及 Ruby等在標準開發環境中皆有提供 Sockets API而瀏覽器的 JavaScript基於安全及貼近網站設計需求的考量下並未將 Sockets列入標準函式庫之中CommonJS的規範填補這種基礎函式庫功能的空缺遵循 CommonJS規範的 NodeJS可以直接使用 Sockets API建立各種網路服務程式

JavaScript語言本身支援 Lambda的特性因此一個匿名函式 (anonymousfunction)可以被儲存成一個變數並當作參數傳遞給另一個函式

var proc1 = function(op x) return op(x)

var op1 = function(x) return x+1 var op2 = function(x) return xx

proc1(op1 3) result is 4 proc1(op2 5) result is 25

另一個 JavaScript開發者必須掌握的語言特性稱為 Closure

NodeJS符合 CommonJS的規範使得 Callback方式易於實現也能夠讓更多同好基於 JavaScript開發符合 NodeJS的外掛模組 (Module)

回想以前要寫一個能夠同時容納上百人的上線的網路服務需要花費

多大的苦工可能 10人多就需要經過一次程式調整而 NodeJS就是為了解決這個困境NodeJS因此誕生它是一種利用 V8 Javascript編譯器所開發的產品利用 V8編譯器的高效能與 Javascript的程式開發特性所產生的網路程式

開發人員所編寫出來的 Javascript腳本程式怎麼可能會比其他語言寫出來的網路程式還要快上許多呢以前的網路程式原理是將使用者每次的

連線 (connection)都開啟一個執行緒 (thread)當連線爆增的時候將會快速耗盡系統效能並且容易產生阻塞 (block)的發生

8 1 Nodejs簡介

Nodejs中文電子書

NodeJS對於資源的調校有所不同當程式接收到一筆連線 (connection)會通知作業系統透過 epoll kqueue devpoll或 select將連線保留並且放入heap中配置先讓連線進入休眠 (Sleep)狀態當系統通知時才會觸發連線的 callback這種處理連線方式只會佔用掉記憶體並不會使用到 CPU資源另外因為採用 Javascript語言的特性每個 request都會有一個 callback如此可以避免發生 Block的狀況發生

基於 Callback特性目前NodeJS大多應用於 Comet(long pulling) RequestServer或者是高連線數量的網路服務上目前也有許多公司將 NodeJS設為內部核心網路服務之一在 NodeJS也提供了外掛管理 (Node packagemanagement)讓愛好 NodeJS輕易開發更多有趣的服務外掛並且提供到 npm讓全世界使用者快速安裝使用

本書最後執行測試版本為 nodejs v068相關 API文件可查詢 lsquohttpnodejsorg lthttpnodejsorggtlsquo本書所有範例均可於 linux windows上執行如遇到任何問題歡迎至 lsquohttpnodejstw lthttpnodejstwgtlsquo詢問對於nodejs相關問題

9

Nodejs中文電子書

10 1 Nodejs簡介

2

JavaScript與 NodeJS

其實使用 JavaScript 在網頁端與伺服器端的差距並不大但是為了使NodeJS可以發揮他最強大的能力有一些知識還是必要的所以還是針對這些主題介紹一下其中 Event LoopScope以及 Callback其實是比較需要了解的基本知識cpscurrying ow control是更進階的技巧與應用

21 Event Loop

可能很多人在寫 Javascript 時並不知道他是怎麼被執行的這個時候可以參考一下 jQuery作者 John Resig一篇好文章介紹事件及 timer怎麼在瀏覽器中執行How JavaScript Timers Work通常在網頁中所有的Javascript執行完畢後(這部份全部都在 global scope跑除非執行函數)接下來就是如 John Resig解釋的這樣所有的事件處理函數以及 timer執行的函數會排在一個 queue結構中利用一個無窮迴圈不斷從 queue中取出函數來執行這個就是 event loop

(除了 John Resig的那篇文章Nicholas C Zakas的 ldquoProfessional Javascriptfor Web Developer 2nd editionrdquo 有一個試閱本httpyuiblogcomassetspdfzakas-projs-2ed-ch18pdf598頁剛好也有簡短的說明)

所以在 Javascript中雖然有非同步但是他並不是使用執行緒所有

11

Nodejs中文電子書

的事件或是非同步執行的函數都是在同一個執行緒中利用 event loop的方式在執行至於一些比較慢的動作例如 IO網頁 render re ow等實際動作會在其他執行緒跑等到有結果時才利用事件來觸發處理函數來處理

這樣的模型有幾個好處沒有執行緒的額外成本所以反應速度很快不會

有任何程式同時用到同一個變數不必考慮 lock也不會產生 dead lock所以程式撰寫很簡單但是也有一些潛在問題任一個函數執行時間較長都

會讓其他函數更慢執行(因為一個跑完才會跑另一個)在多核心硬體普遍

的現在無法用單一的應用程式 instance發揮所有的硬體能力用 NodeJS撰寫伺服器程式碰到的也是一樣的狀況要讓系統發揮 event loop的效能就要盡量利用事件的方式來組織程式架構另外對於一些有可能較為耗

時的操作可以考慮使用 processnextTick函數來讓他以非同步的方式執行避免在同一個函數中執行太久擋住所有函數的執行

如果想要測試 event loop怎樣在「瀏覽器」中運行可以在函數中呼叫alert()這樣會讓所有 Javascript的執行停下來尤其會干擾所有使用 timer的函數執行有一個簡單的例子這是一個會依照設定的時間間隔嚴格執

行動作的動畫如果時間過了就會跳過要執行的動作點按圖片以後人

物會快速旋轉但是在旋轉執行完畢前按下「delay」按鈕讓 alert訊息等久一點接下來的動畫就完全不會出現了

22 Scope與 Closure

要快速理解 JavaScript的 Scope(變數作用範圍)原理只要記住他是Lexical Scope就差不多了簡單地說變數作用範圍是依照程式定義時(或者叫做程式文本)的上下文決定而不是執行時的上下文決定

為了維護程式執行時所依賴的變數即使執行時程式運行在原本的

scope之外他的變數作用範圍仍然維持不變這時程式依賴的自由變數(定義時不是 local的而是在上一層 scope定義的變數)一樣可以使用就好像被關閉起來所以叫做 Closure用程式看比較好懂

function outter(arg1)

arg1 及 free_variable1 對 inner 函數來說都是自由變數

var free_variable1 = 3

return function inner(arg2)

12 2 JavaScript與 NodeJS

Nodejs中文電子書

var local_variable1 =2arg2 及 local_variable1 對 inner 函數來說都是本地變數

return arg1 + arg2 + free_variable + local_variable1

var a = outter(1)變數 a就是 outter函數執行後返回的 inner函數 var b =a(4)執行 inner函數執行時上下文已經在 outter函數之外但是仍然能正常執行而且可以使用定義在 outter函數裡面的 arg1及 free variable1變數 consolelog(b)結果 10

在 Javascript中scope最主要的單位是函數(另外有 global及 eval)所以有可能製造出 closure的狀況通常在形式上都是有巢狀的函數定義而且內側的函數使用到定義在外側函數裡面的變數

Closure有可能會造成記憶體洩漏主要是因為被參考的變數無法被垃圾收集機制處理造成佔用的資源無法釋放所以使用上必須考慮清楚

不要造成意外的記憶體洩漏(在上面的例子中如果 a一直未執行使用到的記憶體就不會被釋放)

跟透過函數的參數把變數傳給函數比較起來Javascript Engine會比較難對 Closure進行最佳化如果有效能上的考量這一點也需要注意

23 Callback

要介紹 Callback之前要先提到 JavaScript的特色

JavaScript是一種函數式語言(functional language)所有 Javascript語言內的函數都是高階函數 (higher order function這是數學名詞計算機用語好像是 rst class function意指函數使用沒有任何限制與其他物件一樣)也就是說函數可以作為函數的參數傳給函數也可以當作函數的返回值這個特性讓 Javascript的函數使用上非常有彈性而且功能強大

callback在形式上其實就是把函數傳給函數然後在適當的時機呼叫傳入的函數Javascript使用的事件系統通常就是使用這種形式NodeJS中有一個物件叫做 EventEmitter這是 NodeJS事件處理的核心物件所有會使用事件處理的函數都會「繼承」這個物件(這裡說的繼承實

作上應該像是 mixin)他的使用很簡單可以使用物件on(事件名稱callback

23 Callback 13

Nodejs中文電子書

函數) 或是物件addListener(事件名稱callback 函數) 把你想要處理事件的函數傳入在物件中可以使用物件emit(事件名稱 參數) 呼叫傳入的callback函數這是 Observer Pattern的簡單實作而且跟在網頁中使用 DOM的 addEventListener使用上很類似也很容易上手不過 NodeJS是大量使用非同步方式執行的應用所以程式邏輯幾乎都是寫在 callback函數中當邏輯比較複雜時大量的 callback會讓程式看起來很複雜也比較難單元測試舉例來說

var p client = new Db(lsquointegration tests 20rsquo new Server(ldquo127001rdquo 27017) lsquopkrsquoCustomPKFactory) p clientopen(function(err p client)

p clientdropDatabase(function(err done)

p clientcreateCollection(lsquotest custom keyrsquo function(err collection)

collectioninsert(lsquoarsquo1 function(err docs)

collection nd(lsquo idrsquonew ObjectID(ldquoaaaaaaaaaaaardquo) function(err cursor)

cursortoArray(function(err items) testassertEquals(1 itemslength) p clientclose()

)

)

)

)

)

)

這是在網路上看到的一段操作 mongodb的程式碼為了循序操作所以必須在一個 callback裡面呼叫下一個動作要使用的函數這個函數裡面還是會使用 callback最後就形成一個非常深的巢狀

這樣的程式碼會比較難進行單元測試有一個簡單的解決方式是

盡量不要使用匿名函數來當作 callback或是 event handler透過這樣的方式

14 2 JavaScript與 NodeJS

Nodejs中文電子書

就可以對各個 handler做單元測試了例如

var http = require(lsquohttprsquo) var tools =

cookieParser function(request response) if(requestheaders[rsquoCookiersquo]) do parsing

var server = httpcreateServer(function(request response)

thisemit(lsquoinitrsquo request response)

) serveron(lsquoinitrsquo toolscookieParser) serverlisten(8080 lsquo127001rsquo)

更進一步可以把 tools改成外部 module例如叫做 toolsjs

moduleexports = cookieParser function(request response) if(requestheaders[rsquoCookiersquo]) do parsing

然後把程式改成

var http = require(lsquohttprsquo)

var server = httpcreateServer(function(request response) thisemit(lsquoinitrsquo re-quest response)

) serveron(lsquoinitrsquo require(lsquo toolsrsquo)cookieParser) serverlisten(8080lsquo127001rsquo)

這樣就可以單元測試 cookieParser了例如使用 nodeunit時可以這樣寫

var testCase = require(lsquonodeunitrsquo)testCase moduleexports = testCase(

ldquosetUprdquo function(cb) thisrequest = headers Cookielsquoname1val1 name2val2rsquo thisresponse = thisresult= name1rsquoval1rsquoname2rsquoval2rsquo

cb()

ldquotearDownrdquo function(cb)

cb()

23 Callback 15

Nodejs中文電子書

ldquonormal caserdquo function(test)

testexpect(1) var obj = require(lsquotoolsrsquo)cookieParser(thisrequest thisresponse)testdeepEqual(obj thisresult) testdone()

)

善於利用模組可以讓程式更好維護與測試

24 CPS(Continuation-Passing Style)

cps 是 callback 使用上的特例形式上就是在函數最後呼叫 callback這樣就好像把函數執行後把結果交給 callback 繼續運行所以稱作continuation-passing style利用 cps可以在非同步執行的情況下透過傳給 callback的這個 cps callback來獲知 callback執行完畢或是取得執行結果例如

lthtmlgt ltbodygt ltdiv id=rdquopanelrdquo style=rdquovisibilityhiddenrdquogtlt divgtlt bodygt lt htmlgt ltscriptgt var request = new XMLHttpRequest() re-questopen(lsquoGETrsquo lsquotest749txt timestamp=rsquo+new Date()getTime() true) re-questaddEventListener(lsquoreadystatechangersquo function(next)

return function() if(thisreadyState===4ampampthisstatus===200) next(thisresponseText)lt== 傳入的 cps callback 在動作完成時執行並取得結果進一步處理

(function(str)lt==這個匿名函數就是 cps callback

documentgetElementById(lsquopanelrsquo)innerHTML=str docu-mentgetElementById(lsquopanelrsquo)stylevisibility = lsquovisiblersquo

) false) requestsend() ltscriptgt

進一步的應用也可以參考 2-6流程控制

16 2 JavaScript與 NodeJS

Nodejs中文電子書

25 函數返回函數與 Currying

前面的 cps範例裡面使用了函數返回函數這是為了把 cps callback傳遞給 onreadystatechange事件處理函數的方法(因為這個事件處理函數並沒有設計好會傳送接收這樣的參數)實際會執行的事件處理函數其實是內層返回的那個函數之外包覆的這個函數主要是為了利用 Closure把 next傳給內層的事件處理函數這個方法更常使用的地方是為了解決一些

scope問題例如

ltscriptgt var accu=0count=10 for(var i=0 iltcount i++)

setTimeout(

function() countndash accu+=i if(countlt=0)

consolelog(accu)

50)

ltscriptgt

最後得出的結果會是 100而不是想像中的 45這是因為等到 setTime-out指定的函數執行時變數 i已經變成 10而離開迴圈了要解決這個問題就需要透過 Closure來保存變數 i

ltscriptgt var accu=0count=10 for(var i=0 iltcount i++)

setTimeout(

function(i) return function() countndash

accu+=i if(countlt=0)

consolelog(accu)

(i)

50)

25 函數返回函數與 Currying 17

Nodejs中文電子書

淺藍色底色的部份是跟上面例子不一樣的地方 ltscriptgt

函數返回函數的另外一個用途是可以暫緩函數執行例如

function add(m n) return m+n

var a = add(20 10) consolelog(a)

add這個函數必須同時輸入兩個參數才有辦法執行如果我希望這個函數可以先給它一個參數等一些處理過後再給一個參數然後得到結

果就必須用函數返回函數的方式做修改

function add(m)

return function(n) return m+n

var wait another arg = add(20)先給一個參數 var a = function(arr)

var ret=0 for(var i=0iltarrlengthi++) ret+=arr[i] return ret

([1234])計算一下另一個參數 var b = wait another arg(a)然後再繼續執行 consolelog(b)

像這樣利用函數返回函數使得原本接受多個參數的函數可以一次

接受一個參數直到參數接收完成才執行得到結果的方式有一個學名就

叫做Currying

綜合以上許多奇技淫巧就可以透過用函數來處理函數的方式調整

程式流程接下來看看

26 流程控制

(以 sync方式使用 async函數避開巢狀 callback循序呼叫 async callback等奇技淫巧)

建議參考

bull httphowtonodeorgcontrol- ow

bull httphowtonodeorgcontrol- ow-part-ii

18 2 JavaScript與 NodeJS

Nodejs中文電子書

bull httphowtonodeorgcontrol- ow-part-iii

bull httpblogmixunet20110202essential-node-js-patterns-and-snippets

這幾篇都是非常經典的 NodeJSJavascript流程控制好文章(阿mixu是在介紹一些 pattern時提到這方面的主題)不過我還是用幾個簡單的程式介紹一下做法跟概念

並發與等待

下面的程式參考了 mixu文章中的做法

var wait = function(callbacks done) consolelog(lsquowait startrsquo) var counter = call-backslength var results = [] var next = function(result) 接收函數執行結果並判斷是否結束執行 resultspush(result) if(ndashcounter == 0) done(results)如果結束執行就把所有執行結果傳給指定的 callback處理 for(var i = 0 i lt callbackslength i++) 依次呼叫所有要執行的函數 callbacks[i](next) consolelog(lsquowait endrsquo)

wait( [ function(next) setTimeout(function() consolelog(lsquodone arsquo) var result =500 next(result) 500) function(next) setTimeout(function() con-solelog(lsquodone brsquo) var result = 1000 next(result) 1000) function(next)setTimeout(function() consolelog(lsquodone crsquo) var result = 1500 next(1500) 1500) ] function(results) var ret = 0 i=0 for( iltresultslength i++) ret+= results[i] consolelog(lsquodone all result lsquo+ret)

)

執行結果wait start wait end done a done b done c done all result 3000

可以看出來其實 wait並不是真的等到所有函數執行完才結束執行而是在所有傳給他的函數執行完畢後(不論同步非同步)才執行處理結

果的函數(也就是 done())

不過這樣的寫法還不夠實用因為沒辦法實際讓函數可以等待執行

完畢又能當作事件處理函數來實際使用上面參考到的 Tim Caswell的文

26 流程控制 19

Nodejs中文電子書

章裡面有一種解法不過還需要額外包裝(在他的例子中)NodeJS核心的 fs物件把一些函數(例如 readFile)用 Currying處理類似像這樣

var fs = require(lsquofsrsquo) var readFile = function(path)

return function(callback errback)

fsreadFile(path function(err data)

if(err) errback()

else callback(data)

)

其他部份可以參考 Tim Caswell的文章他的 Doparallel跟上面的 wait差不多意思這裡只提示一下他沒說到的地方

另外一種做法是去修飾一下 callback當他作為事件處理函數執行後再用 cps的方式取得結果

ltscriptgt function Wait(fns done)

var count = 0 var results = [] thisgetCallback = function(index)

count++ return (function(waitback)

return function() var i=0args=[]for(iltargumentslengthi++)

argspush(arguments[i])

argspush(waitback) fns[index]apply(thisargs)

)(function(result) resultspush(result) if(ndashcount == 0)

20 2 JavaScript與 NodeJS

Nodejs中文電子書

done(results)

)

var a = new Wait(

[ function(waitback) consolelog(lsquodone arsquo) var result = 500 wait-back(result) function(waitback) consolelog(lsquodone brsquo) var result =1000 waitback(result) function(waitback) consolelog(lsquodone crsquo) varresult = 1500 waitback(result) ] function(results) var ret = 0 i=0for( iltresultslength i++) ret += results[i] consolelog(lsquodone allresult lsquo+ret)

) var callbacks = [agetCallback(0)agetCallback(1)agetCallback(0)agetCallback(2)]一次取出要使用的 callbacks避免結果提早送出 setTimeout(callbacks[0]500) setTimeout(callbacks[1] 1000) setTimeout(callbacks[2] 1500) setTime-out(callbacks[3] 2000) 當所有取出的 callbacks執行完畢就呼叫 done()來處理結果 ltscriptgt

執行結果

done a done b done a done c done all result 3500

上面只是一些小實驗更成熟的作品是 Tim Caswell 的 stephttpsgithubcomcreationixstep

如果希望真正使用同步的方式寫非同步則需要使用 Promisejs這一類的 library來轉換非同步函數不過他結構比較複雜 XD(見仁見智不過有些人認為 Promise有點過頭了)httpblogsmsdncombrbucktonarchive20110815promise-js-2-0-promise-framework-for-javascriptaspx

如果想不透過其他 Library做轉換又能直接用同步方式執行非同步函數大概就要使用一些需要額外 compile原始程式碼的方法了例如 BrunoJouhier的 streamlinejshttpsgithubcomSagestreamlinejs

26 流程控制 21

Nodejs中文電子書

循序執行

循序執行可以協助把非常深的巢狀 callback結構攤平例如用這樣的簡單模組來做(serialjs)

moduleexports = function(funs) var c = 0 if(isArrayOfFunctions(funs))

throw(lsquoArgument type was not matched Should be array of func-tionsrsquo)

return function()

var args = Arrayprototypeslicecall(arguments 0) if((cgt=funslength))

c++ return funs[c-1]apply(this args)

function isArrayOfFunctions(f ) if(typeof f == lsquoobjectrsquo) return false if(flength)return false if(fconcat) return false if(fsplice) return false var i = 0 for(iltflength i++)

if(typeof f[i] == lsquofunctionrsquo) return false

return true

簡單的測試範例(testSerialjs)使用 fs 模組確定某個 path 是檔案然後讀取印出檔案內容這樣會用到兩層的 callback所以測試中有使用serial的版本與 nested callbacks的版本做對照

var serial = require(lsquoserialrsquo) fs = require(lsquofsrsquo) path = lsquodclientjsrsquo cb = serial([ func-tion(err data)

if(err)

if(dataisFile) fsreadFile(path cb)

22 2 JavaScript與 NodeJS

Nodejs中文電子書

else consolelog(err)

function(err data)

if(err) consolelog(lsquo[ attened by searial]rsquo) con-solelog(datatoString(lsquoutf8rsquo))

else consolelog(err)

]) fsstat(path cb)

fsstat(path function(err data) 第一層 callback if(err)

if(dataisFile)

fsreadFile(path function(err data) 第二層 callback if(err)

consolelog(lsquo[nested callbacks]rsquo) con-solelog(datatoString(lsquoutf8rsquo))

else consolelog(err)

)

else consolelog(err)

)

關鍵在於這些 callback的執行是有順序性的所以利用 serial返回的一個函數 cb來取代這些 callback然後在 cb中控制每次會循序呼叫的函數

26 流程控制 23

Nodejs中文電子書

就可以把巢狀的 callback攤平成循序的 function陣列(就是傳給 serial函數的參數)

測試中的dclientjs 是一個簡單的 dnode 測試程式放在跟 testSerialjs同一個目錄

var dnode = require(lsquodnodersquo)

dnodeconnect(8000 lsquolocalhostrsquo function(remote)

remoterestart(function(str) consolelog(str) processexit()

)

)

執行測試程式後出現結果

[ attened by searial] var dnode = require(lsquodnodersquo)

dnodeconnect(8000 lsquolocalhostrsquo function(remote)

remoterestart(function(str) consolelog(str) processexit()

)

)

[nested callbacks] var dnode = require(lsquodnodersquo)

dnodeconnect(8000 lsquolocalhostrsquo function(remote)

remoterestart(function(str) consolelog(str) processexit()

)

)

對照起來看兩種寫法的結果其實是一樣的但是利用 serialjs巢狀的 callback結構就會消失

不過這樣也只限於順序單純的狀況如果函數執行的順序比較複雜

(不只是一直線)還是需要用功能更完整的流程控制模組比較好例如

httpsgithubcomcaolanasync

24 2 JavaScript與 NodeJS

3

Nodejs安裝與設定

本篇將講解如何在各個不同 OS建立 NodeJS環境目前 NodeJS 048版本環境架設方式需依賴 Linux指令才可編譯完成當然在不同作業系統中也已經有 NodeJS package可以直接使用指令快速架設以下各不同作業系統解說如何安裝 NodeJS

31 Ubuntu Linux

更新推薦使用 nvm

git clone gitgithubcomcreationixnvmgit ~nvm

echo rdquo ~nvmnvmshrdquo gtgt ~bashrc

nvm install v0614

nvm alias default v0614

以 上 可 參 考 http dreamerslabcom blog tw how-to-setup-a-node-js-development-environment-on-ubuntu-11-04

使用 APT套件管理工具是常見的方法以下是使用社群提供的 PPA安裝方式

sudo apt-get install python-software-properties

sudo add-apt-repository ppachris-leanodejs-devel

25

Nodejs中文電子書

sudo apt-get update

sudo apt-get install nodejs

32 Other Linux

Linux很適合作為 NodeJS的伺服器作業系統及開發環境安裝前請先確認以下套件已正確安裝

bull curl (wget)用來下載檔案的工具

bull git先進的版本控制工具

bull g++ GNU C++軟體編譯工具

bull make GNU軟體專案建置工具

安裝指令如下如設有權限問題請在指令前面加上 sudo

git clone httpsgithubcomjoyentnodegit

cd node

git checkout v067

configure

make

sudo make install

接著測試 nodeJS是否正常執行

node --version

出現版本訊息即表示安裝成功

33 Windows

nodeJS 在 v060 版本之後開始正式支援 windows native直接使用nodeexe 就可以執行程式支援性完全與 linux 相同更棒的部份就是不需經過編譯經過下載之後簡單設定完成立即開發 node程式

26 3 Nodejs安裝與設定

Nodejs中文電子書

下載 nodejs安裝檔案 lthttpnodejsorgdownloadgt

如此完成 windows native nodeexe安裝接著可以進入 command line執行測試在 command line輸入rdquonoderdquo會出現 nodejs版本訊息畫面表示安裝完成

33 Windows 27

Nodejs中文電子書

28 3 Nodejs安裝與設定

4

Nodejs基礎

前篇文章已經由介紹安裝至設定都有完整介紹nodeJS 內部除了javascript常用的函式 (function)物件 (object)之外也有許多不同的自訂物件nodeJS預設建立這些物件為核心物件是為了要讓開發流程更為這些資料在官方文件已經具有許多具體說明接下來將會介紹在開發 nodeJS程式時常見的物件特性與使用方法

41 nodejs http伺服器建立

在 lsquonodejs官方網站 lthttpnodejsorggtlsquo裡面有舉一個最簡單的 HTTP伺服器建立一開始初步就是建立一個伺服器平台讓 nodejs可以與瀏覽器互相行為每種語言一開始的程式建立都是以 Hello world開始最初也從 Hello world帶各位進入 nodejs的世界

輸入以下程式碼儲存檔案為 node basic http hello worldjs

var server

ip = rdquo127001rdquo

port = 1337

http = require(rsquohttprsquo)

server = httpcreateServer(function (req res)

29

Nodejs中文電子書

reswriteHead(200 rsquoContent-Typersquo rsquotextplainrsquo)

resend(rsquoHello Worldnrsquo)

)

serverlisten(port ip)

consolelog(rdquoServer running at httprdquo + ip + rdquordquo + port)

程式碼解講一開始需要有幾個基本的變數

bull ip 機器本身的 ip位置因為使用本地端因此設定為 127001

bull port 需要開通的阜號通常設定為 http port 80因範例不希望與基本 port相衝隨意設定為 1337

在 nodejs 的程式中有許多預設的模組可以使用因此需要使用require方法將模組引入在這邊我們需要使用 http這個模組因此將 http載入Http模組裡面內建有許多方法可以使用這邊採用 createServer創建一個基本的 http伺服器再將 http伺服器給予一個 server變數裡面的回呼函式 (call back function)可以載入 http伺服器的資料與回應方法 (requestresponse)在程式裡面就可以看到我們直接回應給瀏覽器端所需的 Header回應內容

reswriteHead(200 rsquoContent-Typersquo rsquotextplainrsquo)

resend(rsquoHello Worldnrsquo)

Http伺服器需要設定 port ip在最後需要設定 Http監聽需要使用到listen事件監聽所有 Http伺服器行為

httplisten(port ip)

所有事情都完成之後需要確認伺服器正確執行因此使用 console在javascript裡就有這個原生物件console所印出的資料都會顯示於 nodejs伺服器頁面這邊印出的資料並不會傳送到使用者頁面上之後許多除壞

(debug)都會用到 console物件

consolelog(rdquoServer running at httprdquo + ip + rdquordquo + port)

30 4 Nodejs基礎

Nodejs中文電子書

42 nodejs http路徑建立

前面已經介紹如何建立一個簡單的 http伺服器接下來本章節將會介紹如何處理伺服器路徑 (rout)問題在 http的協定下所有從瀏覽器發出的要求 (request)都需要經過處理路徑上的建立也是如此

路徑就是指伺服器 ip位置或者是網域名稱之後對於伺服器給予的要求修改剛才的 hello world檔案修改如下

server = httpcreateServer(function (req res)

consolelog(requrl)

reswriteHead(200 rsquoContent-Typersquo rsquotextplainrsquo)

resend(rsquohello worldnrsquo)

)

重新啟動 nodejs程式後在瀏覽器端測試一下路徑行為結果如下圖

當在瀏覽器輸入 http1270011337test 在伺服器端會收到兩個要求一個是我們輸入的test要求另外一個則是faviconicotest的路徑要求http伺服器本身需要經過程式設定才有辦法回應給瀏覽器端所需要的回應在伺服器中所有的路徑要求都是需要被解析才有辦法取得資料從

42 nodejs http路徑建立 31

Nodejs中文電子書

上面解說可以了解到在 nodejs當中所有的路徑都需要經過設定未經過設定的路由會讓瀏覽器無法取得任何資料導致錯誤頁面的發生底下將會解

說如何設定路由同時避免發生錯誤情形先前 nodejs程式需要增加一些修改才能讓使用者透過瀏覽器在不同路徑時有不同的結果根據剛才

的程式做如下的修改

var server

ip = rdquo127001rdquo

port = 1337

http = require(rsquohttprsquo)

url = require(rsquourlrsquo)

path

server = httpcreateServer(function (req res)

path = urlparse(requrl)

reswriteHead(200 rsquoContent-Typersquo rsquotextplainrsquo)

switch (pathpathname)

case rdquoindexrdquo

resend(rsquoI am indexnrsquo)

break

case rdquotestrdquo

resend(rsquothis is test pagenrsquo)

break

default

resend(rsquodefault pagenrsquo)

break

)

serverlisten(port ip)

consolelog(rdquoServer running at httprdquo + ip + rdquordquo + port)

程式做了片段的修改首先載入 url模組另外增加一個 path變數url模組就跟如同他的命名一般專門處理 url字串處理裡面提供了許多方法來解決路徑上的問題因為從瀏覽器發出的要求路徑可能會帶有多種需求

或者 GET參數組合等因此我們需要將路徑單純化取用路徑部分的資料即可例如使用者可能會送出 http1270011337testsend=1如果直接

32 4 Nodejs基礎

Nodejs中文電子書

信任 requrl就會收到結果為testsend=1所以需要透過 url模組的方法將路徑資料過濾

在這邊使用 urlparse的方法裡面帶入網址格式資料會回傳路徑資料為了後需方便使用將回傳的資料設定到 path變數當中在回傳的路徑資料裡面包含資訊如下圖

這邊只需要使用單純的路徑要求直接取用 pathpathname就可以達到我們的目的

最後要做路徑的判別在不同的路徑可以指定不同的輸出在範例中

有三個可能結果第一個從瀏覽器輸入index就會顯示 index結果test 就會呈現出 test頁面最後如果都不符合預期的輸入會直接顯示 default的頁面最後的預防可以讓瀏覽器不會出現非預期結果讓程式的可靠性提昇

底下為測試結果

42 nodejs http路徑建立 33

Nodejs中文電子書

43 nodejs檔案讀取

前面已經介紹如何使用路由(rount)做出不同的回應實際應用只有在瀏覽器只有輸出幾個文字資料總是不夠的在本章節中將介紹如何使

用檔案讀取輸出檔案資料讓使用者在前端瀏覽器也可以讀取到完整的

html css javascript檔案輸出

檔案管理最重要的部分就是 lsquoFile system lthttpnodejsorgdocslatestapifshtmlgtlsquo這個模組此模組可以針對檔案做管理監控讀取等行為裡面有許多預設的方法底下是檔案輸出的基本範例底下會有兩個檔案

第一個是靜態 html檔案

ltDOCTYPE htmlgt

lthtml xmlns=rdquohttpwwww3org1999xhtmlrdquo xmllang=rdquozh-CNrdquo lang=rdquozh-CNrdquogt

ltheadgt

ltmeta http-equiv=rdquoContent-Typerdquo content=rdquotexthtml charset=UTF-7rdquo gt

lttitlegtnodejs index html filelttitlegt

ltheadgt

ltbodygt

34 4 Nodejs基礎

Nodejs中文電子書

lth1gtnodejs index html filelth1gt

ltbodygt

lthtmlgt

另一個為 nodejs程式

var fs = require(rdquofsrdquo)

filename = rdquostaticindexhtmlrdquo

encode = rdquoutf8rdquo

fsreadFile(filename encode function(err file)

consolelog(file)

)

一開始直接載入 le system模組 載入名稱為 fs讀取檔案主要使

用的方法為 readFile裡面以三個參數路徑 ( le path) 編碼方式 (encoding)

回應函式 (callback)路徑必須要設定為靜態 html所在位置才能指定到正確的檔案靜態檔案的編碼方式也必須正確這邊使用靜態檔案的編

碼為 utf8如果編碼設定錯誤nodejs讀取出來檔案結果會使用 byte raw格式輸出如果錯誤編碼格式會導致輸出資料為 byte raw

回應函式中裡面會使用兩個變數error為錯誤資訊如果讀取的檔案不存在或者發生錯誤error數值會是 true如果成功讀取資料 error將會是 falsecontent則是檔案內容資料讀取後將會把資料全數丟到 content這個變數當中

最後程式的輸出結果畫面如下

43 nodejs檔案讀取 35

Nodejs中文電子書

44 nodejs http靜態檔案輸出

前面已經了解如何讀取本地端檔案接下來將配合 http伺服器路由讓每個路由都能夠輸出相對應的靜態 html檔案首先新增加幾個靜態 html檔案

ltDOCTYPE htmlgt

lthtml xmlns=rdquohttpwwww3org1999xhtmlrdquo xmllang=rdquozh-CNrdquo lang=rdquozh-CNrdquogt

ltheadgt

ltmeta http-equiv=rdquoContent-Typerdquo content=rdquotexthtml charset=UTF-7rdquo gt

lttitlegtnodejs index html filelttitlegt

ltheadgt

ltbodygt

lth1gtnodejs index html filelth1gt

ltbodygt

lthtmlgt

ltDOCTYPE htmlgt

lthtml xmlns=rdquohttpwwww3org1999xhtmlrdquo xmllang=rdquozh-CNrdquo lang=rdquozh-CNrdquogt

ltheadgt

ltmeta http-equiv=rdquoContent-Typerdquo content=rdquotexthtml charset=UTF-7rdquo gt

lttitlegtnodejs test html filelttitlegt

ltheadgt

ltbodygt

lth1gtnodejs test html filelth1gt

ltbodygt

lthtmlgt

ltDOCTYPE htmlgt

lthtml xmlns=rdquohttpwwww3org1999xhtmlrdquo xmllang=rdquozh-CNrdquo lang=rdquozh-CNrdquogt

ltheadgt

ltmeta http-equiv=rdquoContent-Typerdquo content=rdquotexthtml charset=UTF-7rdquo gt

lttitlegtnodejs static html filelttitlegt

ltheadgt

ltbodygt

lth1gtnodejs static html filelth1gt

ltbodygt

lthtmlgt

36 4 Nodejs基礎

Nodejs中文電子書

準備一個包含基本路由功能的 http伺服器

var server

ip = rdquo127001rdquo

port = 1337

http = require(rsquohttprsquo)

url = require(rsquourlrsquo)

server = httpcreateServer(function (req res)

var path = urlparse(requrl)

)

serverlisten(port ip)

consolelog(rdquoServer running at httprdquo + ip + rdquordquo + port)

加入 le system 模組使用 readFile 的功能將這一段程式放置於

createServer的回應函式中

fsreadFile(filePath encode function(err file)

)

readFile的回應函式裡面加入頁面輸出讓瀏覽器可以正確讀到檔案在這邊我們設定讀取的檔案為 html 靜態檔案所以 Content-type 設定為texthtml讀取到檔案的內容將會正確輸出成 html靜態檔案

fsreadFile(filePath encode function(err file)

reswriteHead(200 rsquoContent-Typersquo rsquotexthtmlrsquo)

reswrite(file)

resend()

)

到這邊為止基本的程式內容都已經完成剩下一些細節的調整首先

路徑上必須做調整目前的靜態檔案全部都放置於 static資料夾底下設定一個變數來記住資料夾位置

接著將瀏覽器發出要求路徑與資料夾組合讀取正確 html靜態檔案使用者有可能會輸入錯誤路徑所以在讀取檔案的時候要加入錯誤處理

同時回應 404伺服器無法正確回應的 http header格式

加入這些細節的修改一個基本的 http 靜態 html輸出伺服器就完成了完整程式碼如下

44 nodejs http靜態檔案輸出 37

Nodejs中文電子書

var server

ip = rdquo127001rdquo

port = 1337

http = require(rsquohttprsquo)

fs = require(rdquofsrdquo)

folderPath = rdquostaticrdquo

url = require(rsquourlrsquo)

path

filePath

encode = rdquoutf8rdquo

server = httpcreateServer(function (req res)

path = urlparse(requrl)

filePath = folderPath + pathpathname

fsreadFile(filePath encode function(err file)

if (err)

reswriteHead(404 rsquoContent-Typersquo rsquotextplainrsquo)

resend()

return

reswriteHead(200 rsquoContent-Typersquo rsquotextapplicationrsquo)

reswrite(file)

resend()

)

)

serverlisten(port ip)

consolelog(rdquoServer running at httprdquo + ip + rdquordquo + port)

45 nodejs http GET資料擷取

http伺服器中除了路由之外另一個最常使用的方法就是擷取 GET資料本單元將會介紹如何透過基本 http伺服器擷取瀏覽器傳來的要求擷取 GET資料

在 http協定中GET參數都是藉由 URL從瀏覽器發出要求送至伺服器

38 4 Nodejs基礎

Nodejs中文電子書

端基本的傳送網址格式可能如下

http127001testsend=1amptest=2

上面這段網址裡面的 GET參數就是 send而這個變數的數值就為 1如果想要在 http伺服器取得 GET資料需要在瀏覽器給予的要求 (request)做處理

首先需要載入 query string這個模組這個模組主要是用來將字串資料

過濾後轉換成javascript物件

qs = require(rsquoquerystringrsquo)

接著在第一階段利用 url模組過濾瀏覽器發出的 URL資料後將回應的物件裡面的 query這個變數是一個字串值資料過濾後如下

send=1amptest=2

透過 query string使用 parse這個方法將資料轉換成 javascript物件就表示 GET的資料已經被伺服器端正式擷取下來

path = urlparse(requrl)

parameter = qsparse(pathquery)

整個 nodejs http GET參數完整擷取程式碼如下

var server

ip = rdquo127001rdquo

port = 1337

http = require(rsquohttprsquo)

qs = require(rsquoquerystringrsquo)

url = require(rsquourlrsquo)

server = httpcreateServer(function (req res)

var path = urlparse(requrl)

parameter = qsparse(pathquery)

consoledir(parameter)

reswriteHead(200 rsquoContent-Typersquo rsquotextplainrsquo)

reswrite(rsquoBrowser test GET parameternrsquo)

resend()

)

45 nodejs http GET資料擷取 39

Nodejs中文電子書

serverlisten(port ip)

consolelog(rdquoServer running at httprdquo + ip + rdquordquo + port)

程式運作之後由瀏覽器輸入要求網址之後nodejs伺服器端回應資料為

Server running at http1270011337

send rsquo1rsquo test rsquo1rsquo

46 本章結語

前面所解說的部份一大部分主要是處理 http伺服器基本問題雖然在某些部分有牽扯到 http 伺服器基本運作原理主要還是希望可以藉由這些基本範例練習 nodejs練習回應函式與語法串接的特點習慣編寫javascript風格程式當然直接這樣開發 nodejs是非常辛苦的接下來在模組實戰開發的部份將會介紹特定的模組一步一步帶領各位從無到有進行

nodejs應用程式開發

40 4 Nodejs基礎

5

NPM套件管理工具

npm全名為 Node Package Manager是 Nodejs的套件(package)管理工具類似 Perl的 ppm或 PHP的 PEAR等安裝 npm後使用npm install

module name指令即可安裝新套件維護管理套件的工作會更加輕鬆

npm可以讓Nodejs的開發者直接利用擴充線上的套件庫(packagesregistry)加速軟體專案的開發npm提供很友善的搜尋功能可以快速找到安裝需要的套件當這些套件發行新版本時npm也可以協助開發者自動更新這些套件

npm不僅可用於安裝新的套件它也支援搜尋列出已安裝模組及更新的功能

51 安裝 NPM

Nodejs在 063版本開始內建 npm讀者安裝的版本若是此版本或更新的版本就可以略過以下安裝說明

若要檢查 npm是否正確安裝可以使用以下的指令

npm -v

41

Nodejs中文電子書

執行結果說明

若 npm正確安裝執行 npm -v將會看到類似 110-2的版本訊息

若讀者安裝的 Nodejs版本比較舊或是有興趣嘗試自己動手安裝 npm工具則可以參考以下的說明

安裝於Windows系統

Nodejs for Windows 於 062 版開始內建 npm使用 nodejsorg 官方提供的安裝程式不需要進一步的設定就可以立即使用 npm指令對於Windows的開發者來說大幅降低環境設定的問題與門檻

除了使用 Nodejs內建的 npm讀者也可以從 npm官方提供的以下網址

httpnpmjsorgdist

這是由 npm提供的 Fancy Windows Install版本請下載壓縮檔(例如npm-110-3zip)並將壓縮檔內容解壓縮至 Nodejs的安裝路徑(例如CProgram Filesnodejs)

解壓縮後在 Nodejs的安裝路徑下應該有以下的檔案及資料夾

bull npmcmd(檔案)

bull node modules(資料夾)

安裝於 Linux系統

Ubuntu Linux的使用者可以加入 NPM Unoffcial PPA這個 repository即可使用 apt-get完成 npm安裝

42 5 NPM套件管理工具

Nodejs中文電子書

Ubuntu Linux使用 apt-get安裝 npm

sudo apt-get install python-software-properties

sudo add-apt-repository ppagias-kay-leenpm

sudo apt-get update

sudo apt-get install npm

npm官方提供的安裝程式 installsh可以適用於大多數的 Linux系統使用這個安裝程式請先確認

1 系統已安裝 curl工具(請使用 curl --version查看版本訊息)

2 已安裝 Nodejs並且 PATH正確設置

3 Nodejs的版本必須大於 04x

以下為 npm提供的安裝指令

curl httpnpmjsorginstallsh | sh

安裝成功會看到如下訊息

installsh安裝成功的訊息

npm10105 homeUSERNAMElocalnodelibnode_modulesnpm

It worked

安裝於Mac OS X

建議採用與 Nodejs相同的方式進行 npm的安裝例如使用MacPorts安裝 Nodejs就同樣使用MacPorts安裝 npm這樣對日後的維護才會更方便容易

使用 MacPorts安裝 npm是本書比較建議的方式它可以讓 npm的安裝移除及更新工作自動化將會幫助開發者節省寶貴時間

51 安裝 NPM 43

Nodejs中文電子書

安裝MacPorts的提示

在MacPorts網站可以取得 OS X系統版本對應的安裝程式(例如 106或 107)httpwwwmacportsorg安裝過程會詢問系統管理者密碼使用預設的選項完成安裝即可安

裝MacPorts之後在終端機執行 port -v將會看到MacPorts的版本訊息

安裝 npm之前先更新MacPorts的套件清單以確保安裝的 npm是最新版本

sudo port -d selfupdate

接著安裝 npm

sudo port install npm

若讀者的 Nodejs並非使用MacPorts安裝則不建議使用MacPorts安裝npm因為 MacPorts會自動檢查並安裝相依套件而 npm相依 nodejs所以MacPorts也會一併將 nodejs套件安裝造成先前讀者使用其它方式安裝的 nodejs被覆蓋

讀者可以先使用 MacPorts安裝 curl(sudo port install curl)

再參考 Linux的 installsh安裝方式即可使用 npm官方提供的安裝程式

NPM安裝後測試

npm是指令列工具(command-line tool)使用時請先打開系統的文字終端機工具

測試 npm安裝與設定是否正確請輸入指令如下

npm -v

或是

npm --version

如果 npm已經正確安裝設定就會顯示版本訊息

44 5 NPM套件管理工具

Nodejs中文電子書

執行結果(範例)

110-2

52 使用 NPM安裝套件

npm目前擁有超過 6000種套件(packages)可以在 npm registry使用關鍵字搜尋套件

httpsearchnpmjsorg

舉例來說在關鍵字欄位輸入「coffee-script」下方的清單就會自動列出包含 coffee-script關鍵字的套件

接著我們回到終端機模式的操作npm的指令工具本身就可以完成套

件搜尋的任務

例如以下的指令同樣可以找出 coffee-script相關套件

npm search coffee-script

以下是搜尋結果的參考畫面

52 使用 NPM安裝套件 45

Nodejs中文電子書

找到需要的套件後(例如 express)即可使用以下指令安裝

npm install coffee-script

值得注意的一點是使用 npm install會將指定的套件安裝在工

作目錄(Working Directory)的 node modules資料夾下

以Windows為例如果執行 npm install的目錄位於

Cproject1

那麼 npm將會自動建立一個 node modules的子目錄(如果不存在)

Cproject1node modules

並且將下載的套件放置於這個子目錄例如

Cproject1node modulescoffee-script

這個設計讓專案可以個別管理相依的套件並且可以在專案佈署或發

行時將這些套件(位於 node modules)一併打包方便其它專案的使用者不必再重新下載套件

這個 npm install的預設安裝模式為 local(本地)只會變更當前專案的資料夾不會影響系統

另一種安裝模式稱為 global(全域)這種模式會將套件安裝到系統資

料夾也就是 npm安裝路徑的 node modules資料夾例如

CProgram Filesnodejsnode modules

46 5 NPM套件管理工具

Nodejs中文電子書

是否要使用全域安裝可以依照套件是否提供新指令來判斷舉例來說express 套件提供 express 這個指令而 coffee-script 則提供 coffee

指令

在 local 安 裝 模 式 中這 些 指 令 的 程 式 檔 案會 被 安 裝 到node modules的 bin這個隱藏資料夾下除非將bin的路徑加入 PATH環境變數否則要執行這些指令將會相當不便

為了方便指令的執行我們可以在 npm install 加上 -g 或 --

global參數啟用 global安裝模式例如

npm install -g coffee-script

npm install -g express

使用 global安裝模式需要注意執行權限的問題若權限不足可能會出現類似以下的錯誤訊息

npm ERR Error EACCES permission denied rsquorsquo

npm ERR

npm ERR Please try running this command again as rootAdministrator

要獲得足夠得執行權限請參考以下說明

bull Windows 7 或 2008 以上在「命令提示字元」的捷徑按右鍵選擇「以系統管理員身分執行」執行 npm指令時就會具有 Administrator身分

bull Mac OS X或 Linux系統可以使用 sudo指令例如

sudo npm install -g express

bull Linux系統可以使用 root權限登入或是以「sudo su -」切換成 root身分(使用 root權限操作系統相當危險因此並不建議使用這種方式)

若加上 -g參數使用 npm install -g coffee-script完成安裝

後就可以在終端機執行 coffee指令例如

coffee -v

52 使用 NPM安裝套件 47

Nodejs中文電子書

執行結果(範例)

CoffeeScript version 120

53 套件的更新及維護

除了前一節說明的 search 及 install 用法npm 還提供其他許多指令(commands)

使用 npm help可以查詢可用的指令

npm help

執行結果(部分)

where ltcommandgt is one of

adduser apihelp author bin bugs c cache completion

config deprecate docs edit explore faq find get

help help-search home i info init install la link

list ll ln login ls outdated owner pack prefix

prune publish r rb rebuild remove restart rm root

run-script s se search set show star start stop

submodule tag test un uninstall unlink unpublish

unstar up update version view whoami

使用 npm help command可以查詢指令的詳細用法例如

npm help list

接下來本節要介紹開發過程常用的 npm指令

使用 list可以列出已安裝套件

npm list

48 5 NPM套件管理工具

Nodejs中文電子書

執行結果(範例)

-- coffee-script120

- express256

- connect185

| -- formidable108

-- mime124

-- mkdirp007

-- qs041

檢視某個套件的詳細資訊例如

npm show express

升級所有套件(如果該套件已發佈更新版本)

npm update

升級指定的套件

npm update express

移除指定的套件

npm uninstall express

54 使用 packagejson

對於正式的 Nodejs專案可以建立一個命名為 packagejson的設

定檔(純文字格式)檔案內容參考範例如下

54 使用 packagejson 49

Nodejs中文電子書

packagejson(範例)

rdquonamerdquo rdquoapplication-namerdquo

rdquoversionrdquo rdquo001rdquo

rdquoprivaterdquo true

rdquodependenciesrdquo

rdquoexpressrdquo rdquo255rdquo

rdquocoffee-scriptrdquo rdquolatestrdquo

rdquomongooserdquo rdquogt= 253rdquo

其中 name與 version依照專案的需求設置

需要注意的是 dependencies的設定它用於指定專案相依的套件名

稱及版本

bull rdquoexpressrdquo rdquo255rdquo

代表此專案相依版本 255的 express套件

bull rdquocoffee-scriptrdquo rdquolatestrdquo

使用最新版的 coffee-script套件(每次更新都會檢查新版)

bull rdquomongooserdquo rdquogt= 253rdquo

使用版本大於 253的 mongoose套件

假設某個套件的新版可能造成專案無法正常運作就必須指定套件的

版本避免專案的程式碼來不及更新以相容新版套件通常在開發初期的

專案需要盡可能維持新套件的相容性(以取得套件的更新或修正)可以

用「gt=」設定最低相容的版本或是使用「latest」設定永遠保持最新

套件

50 5 NPM套件管理工具

6

Express介紹

在前面的 nodejs基礎當中介紹許多許多開設 http的使用方法及介紹以及許多基本的 nodejs基本應用

接下來要介紹一個套件稱為 express [Express](httpexpressjscom)這個套件主要幫忙解決許多 nodejs http server所需要的基本服務讓開發 httpservice變得更為容易不需要像之前需要透過層層模組(module)才有辦法開始編寫自己的程式

這個套件是由 TJ Holowaychuk 製作而成的套件裡面包含基本的路由處理 (route)http資料處理(GETPOSTPUT)另外還與樣板套件(jshtml template engine)搭配同時也可以處理許多複雜化的問題

61 Express安裝

安裝方式十分簡單只要透過之前介紹的 NPM就可以使用簡單的指令安裝指令如下

這邊建議需要將此套件安裝成為全域模組方便日後使用

51

Nodejs中文電子書

62 Express基本操作

express的使用也十分簡單先來建立一個基本的 hello world

var app = require(rsquoexpressrsquo)createServer()

port = 1337

applisten(port)

appget(rsquorsquo function(req res)

ressend(rsquohello worldrsquo)

)

consolelog(rsquostart express servernrsquo)

可以從上面的程式碼發現基本操作與 nodejs http的建立方式沒有太大差異主要差在當我們設定路由時可以直接透過 appget方式設定回應與接受方式

63 Express路由處理

Express對於 http服務上有許多包裝讓開發者使用及設定上更為方便例如有幾個路由設定那我們就統一藉由 appget來處理

Create http server

appget(rsquorsquo function(req res)

ressend(rsquohello worldrsquo)

)

appget(rsquotestrsquo function(req res)

ressend(rsquotest renderrsquo)

)

appget(rsquouserrsquo function(req res)

ressend(rsquouser pagersquo)

)

52 6 Express介紹

Nodejs中文電子書

如上面的程式碼所表示appget可以帶入兩個參數第一個是路徑名稱設定第二個為回應函式 (call back function)回應函式裡面就如同之前的 createServer方法裡面包含 requestresponse兩個物件可供使用使用者就可以透過瀏覽器輸入不同的 url切換到不同的頁面顯示不同的結果

路由設定上也有基本的配對方式讓使用者從瀏覽器輸入的網址可以

是一個變數只要符合型態就可以有對應的頁面產出例如

Create http server

appget(rsquouseridrsquo function(req res)

ressend(rsquouser rsquo + reqparamsid)

)

appget(rsquonumberrsquo function(req res)

ressend(rsquonumber rsquo + reqparamsnumber)

)

裡 面 使 用 到number 從 網 址 輸 入 之 後 就 可 以 直 接 使 用reqparamsnumber 取得所輸入的資料變成 url 參數使用當然前面也是可以加上路徑的設定userid在瀏覽器上路徑必須符合userxxx透

過 reqparamsid就可以取到 xxx這個字串值

另外express參數處理也提供了路由參數配對處理也可以透過正規表示法作為參數設定

var app = require(rsquoexpressrsquo)createServer()

port = 1337

applisten(port)

appget(^ip((d23)((d23))((d23))((d23))) function(req res)

ressend(reqparams)

)

上面程式碼可以發現後面路由設定的型態是正規表示法裡面設定

格式為ip之後必須要加上 ip型態才會符合資料格式同時取得 ip資料已經由正規表示法將資料做分群因此可以取得 ip的四個數字

此程式執行之後可以透過瀏覽器測試輸入網址為 localhost3000ip25525510010可以從頁面獲得資料

63 Express路由處理 53

Nodejs中文電子書

此章節全部範例程式碼如下

overview

author Caesar Chi

blog clonnblogspotcom

version 20120226

create server

var app = require(rsquoexpressrsquo)createServer()

port = 1337

applisten(port)

normal style

appget(rsquorsquo function(req res)

ressend(rsquohello worldrsquo)

)

appget(rsquotestrsquo function(req res)

ressend(rsquotest renderrsquo)

)

parameter style

appget(rsquouseridrsquo function(req res)

ressend(rsquouser rsquo + reqparamsid)

)

appget(rsquonumberrsquo function(req res)

ressend(rsquonumber rsquo + reqparamsnumber)

)

REGX style

appget(^ip((d23)((d23))((d23))) function(req res)

ressend(reqparams)

)

appget(rsquorsquo function(req res)

ressend(rsquoPage not foundrsquo 404)

54 6 Express介紹

Nodejs中文電子書

)

consolelog(rsquostart express servernrsquo)

64 Express middleware

Express 裡面有一個十分好用的應用概念稱為 middleware可以透過middleware做出複雜的效果同時上面也有介紹 next方法參數傳遞就是靠 middleware的概念來傳遞參數讓開發者可以明確的控制程式邏輯

create http server

appuse(expressbodyParser())

appuse(expressmethodOverride())

appuse(expresssession())

上面都是一種 middleware的使用方式透過 appuse方式裡面載入函式執行方法回應函式會包含三個基本參數responserequestnext其中next表示下一個 middleware執行函式同時會自動將預設三個參數繼續帶往下個函式執行底下有個實驗

上面的片段程式執行後開啟瀏覽器連結上 localhost1337會發現伺服器回應結果順序如下

first middle ware

second middle ware

execute middle ware

end middleware function

從上面的結果可以得知剛才設定的 middleware都生效了在 appuse設定的 middleware是所有 url皆會執行方法如果有指定特定方法就可以使用 appget的 middleware設定在 appget函式的第二個參數就可以帶入函式或者是匿名函式只要函式裡面最後會接受 request response next這三個參數同時也有正確指定 next函式的執行時機最後都會執行到最後一個方法當然開發者也可以評估程式邏輯要執行到哪一個階段讓邏輯

可以更為分明

64 Express middleware 55

Nodejs中文電子書

65 Express路由應用

在實際開發上可能會遇到需要使用參數等方式混和變數一起使用

express裡面提供了一個很棒的處理方法 appall這個方式可以先採用基本路由配對再將設定為每個不同的處理方式開發者可以透過這個方式簡

化自己的程式邏輯

overview

author

version 20120226

create server

var app = require(rsquoexpressrsquo)createServer()

port = 1337

users = [

name rsquoClonnrsquo

name rsquoChirsquo

]

applisten(port)

appall(rsquouseridoprsquo function(req res next)

requser = users[reqparamsid]

if (requser)

next()

else

next(new Error(rsquocannot find user rsquo + reqparamsid))

)

appget(rsquouseridrsquo function(req res)

ressend(rsquoviewing rsquo + requsername)

)

appget(rsquouserideditrsquo function(req res)

ressend(rsquoediting rsquo + requsername)

)

56 6 Express介紹

Nodejs中文電子書

appget(rsquouseriddeletersquo function(req res)

ressend(rsquodeleting rsquo + requsername)

)

appget(rsquorsquo function(req res)

ressend(rsquoPage not foundrsquo 404)

)

consolelog(rsquostart express servernrsquo)

內部宣告一組預設的使用者分別給予名稱設定藉由 appall這個方法可以先將路由雛形建立再接下來設定 appget的路徑格式只要符合格式就會分配進入對應的方法中像上面的程式當中如果使用者輸入路徑為user0除了執行 appall程式之後執行 next方法就會對應到路徑設定為userid的這個方法當中如果使用者輸入路徑為user0edit就會執行到useridedit的對應方法

66 Express GET應用範例

我們準備一個使用 GET方法傳送資料的表單

ltDOCTYPE htmlgt

lthtmlgt

ltheadgt

ltmeta http-equiv=rdquoContent-Typerdquo content=rdquotexthtml charset=utf8rdquogt

lttitlegtNodejs 菜鳥筆記 (1)lttitlegt

ltlink rel=rdquostylesheetrdquo href=rdquocssstylecssrdquo type=rdquotextcssrdquo media=rdquoallrdquo gt

ltheadgt

ltbodygt

lth1gtNodejs 菜鳥筆記-註冊lth1gt

ltform id=rdquosignuprdquo method=rdquoGETrdquo action=rdquohttplocalhost3000Signuprdquogt

ltlabelgt 使用者名稱ltlabelgtltinput type=rdquotextrdquo id=rdquousernamerdquo name=rdquousernamerdquo gtltbrgt

ltlabelgt 電子郵件ltlabelgtltinput type=rdquotextrdquo id=rdquoemailrdquo name=rdquoemailrdquo gtltbrgt

ltinput type=rdquosubmitrdquo value=rdquo 註冊我的帳號rdquo gtltbrgt

ltformgt

ltbodygt

lthtmlgt

66 Express GET應用範例 57

Nodejs中文電子書

這個表單沒有什麼特別的地方我們只需要看第 9 行form 使用的method是 GET然後 action是rdquohttplocalhost3000Signupldquo等一下我們要來撰寫Signup這個 URL Path的處理程式

處理 Signup行為

我們知道所謂的 GET方法會透過 URL來把表單的值給帶過去以上面的表單來說到時候 URL會以這樣的形式傳遞

httplocalhost3000Signupusername=xxxampemail=xxx

所以要能處理這樣的資料必須有以下功能

bull 解析 URL

bull 辨別動作是 Signup

bull 解析出 username和 email

一旦能取得 username和 email的值程式就能加以應用了

處理 Signup的程式碼雛形

load module

var url = require(rsquourlrsquo)

urlData = urlparse(requrltrue)

action = urlDatapathname

reswriteHead(200 rdquoContent-Typerdquordquotexthtml charset=utf-8rdquo)

if (action === rdquoSignuprdquo)

user = urlDataquery

resend(rdquolth1gtrdquo + userusername + rdquo 歡迎您的加入 lth1gtltpgt 我們已經將會員啟用信寄至rdquo + useremail + rdquoltpgtrdquo)

首先需要加載 url module它是用來協助我們解析 URL的模組接著使用 urlparse方法第一個傳入 url字串變數也就是 requrl另外第二個參數的用意是設為 ture則引進 querystring模組來協助處理預設是 false它影響到的是 urlDataquery設為 true會傳回物件不然就只是一般的字串urlparse會將字串內容整理成一個物件我們把它指定給 urlData

action變數作為記錄 pathname這是我們稍後要來判斷目前網頁的動作是什麼接著先將 html表頭資訊 (Header)準備好再來判斷路徑邏輯如

58 6 Express介紹

Nodejs中文電子書

果是 Signup這個動作就把 urlDataquery裡的資料指定給 user然後輸出userusername和 useremail把使用者從表單註冊的資料顯示於頁面中

最後進行程式測試啟動 nodejs主程式之後開啟瀏覽器就會看到表單填寫完畢按下送出就可以看到結果了

完整 nodejs程式碼如下

var http = require(rsquohttprsquo)

url = require(rsquourlrsquo)

fs = require(rdquofsrdquo)

server

server = httpcreateServer(function (reqres)

var urlData

encode = rdquoutf8rdquo

filePath = rdquoviewexpress_get_example_formhtmlrdquo

action

urlData = urlparse(requrltrue)

action = urlDatapathname

reswriteHead(200 rdquoContent-Typerdquordquotexthtml charset=utf-8rdquo)

if (action === rdquoSignuprdquo)

user = urlDataquery

resend(rdquolth1gtrdquo + userusername + rdquo 歡迎您的加入 lth1gtltpgt 我們已經將會員啟用信寄至rdquo + useremail + rdquoltpgtrdquo)

else

fsreadFile(filePath encode function(err file)

reswrite(file)

resend()

)

)

serverlisten(3000)

consolelog(rsquoServer 跑起來了現在時間是rsquo + new Date())

66 Express GET應用範例 59

Nodejs中文電子書

67 Express POST應用範例

一開始準備基本的 html表單傳送內容以 POST方式form的 action屬性設定為 POST其餘 html內容與前一個範例應用相同

ltDOCTYPE htmlgt

lthtmlgt

ltheadgt

ltmeta http-equiv=rdquoContent-Typerdquo content=rdquotexthtml charset=utf8rdquogt

lttitlegtNodejs 菜鳥筆記 (1)lttitlegt

ltlink rel=rdquostylesheetrdquo href=rdquocssstylecssrdquo type=rdquotextcssrdquo media=rdquoallrdquo gt

ltheadgt

ltbodygt

lth1gtNodejs 菜鳥筆記-註冊lth1gt

ltform id=rdquosignuprdquo method=rdquoPOSTrdquo action=rdquohttplocalhost3000Signuprdquogt

ltlabelgt 使用者名稱ltlabelgtltinput type=rdquotextrdquo id=rdquousernamerdquo name=rdquousernamerdquo gtltbrgt

ltlabelgt 電子郵件ltlabelgtltinput type=rdquotextrdquo id=rdquoemailrdquo name=rdquoemailrdquo gtltbrgt

ltinput type=rdquosubmitrdquo value=rdquo 註冊我的帳號rdquo gtltbrgt

ltformgt

ltbodygt

lthtmlgt

nodejs的程式處理邏輯與前面 GET範例類似部分程式碼如下

qs = require(rsquoquerystringrsquo)

if (action === rdquoSignuprdquo)

formData = rsquorsquo

reqon(rdquodatardquo function (data)

formData += data

)

reqon(rdquoendrdquo function ()

user = qsparse(formData)

resend(rdquolth1gtrdquo + userusername + rdquo 歡迎您的加入 lth1gtltpgt 我們已經將會員啟用信寄至rdquo + useremail + rdquoltpgtrdquo)

)

60 6 Express介紹

Nodejs中文電子書

主要加入了rsquoquerystringrsquo這個moduel方便我們等一下解析由表單 POST回來的資料另外加入一個 formData的變數用來搜集待等一下表單回傳的資料前面的 GET範例我們只從 req拿出 url的資料這次要在利用req身上的事件處理

JavaScript 在訂閱事件時使用 addEventListener而 nodejs 使用的則是on這邊加上了監聽 data的事件會在瀏覽器傳送資料到Web Server時被執行參數是它所接收到的資料型態是字串

接著再增加 end的事件當瀏覽器的請求事件結束時它就會動作

由於瀏覽器使用 POST在上傳資料時會將資料一塊塊地上傳因為我們在監聽 data事件時透過 formData變數將它累加起來lt不過由於我們

上傳的資料很少一次就結束不過如果日後需要傳的是資料比較大的檔

案這個累加動作就很重要

當資料傳完就進到 end 事件中會用到 qsparse 來解析 formDataformData的內容是字串內容是

username=wordsmithampemail=wordsmith40somewhere

而 qsparse可以幫我們把這個 querystring轉成物件的格式也就是

username=wordsmithampemail=wordsmith40somewhere

一旦轉成物件並指定給 user之後其他的事情就和 GET方法時操作的一樣寫 response的表頭將內容回傳並將 userusername和 useremail代入到內容中

修改完成後接著執行 nodejs程式啟動 web server開啟瀏覽器進入表單測試看看POST的方式能否順利運作

完整程式碼如下

var http = require(rsquohttprsquo)

url = require(rsquourlrsquo)

fs = require(rdquofsrdquo)

qs = require(rsquoquerystringrsquo)

server

server = httpcreateServer(function (reqres)

var urlData

67 Express POST應用範例 61

Nodejs中文電子書

encode = rdquoutf8rdquo

filePath = rdquoviewexpress_post_example_formhtmlrdquo

formData

action

urlData = urlparse(requrltrue)

action = urlDatapathname

reswriteHead(200 rdquoContent-Typerdquordquotexthtml charset=utf-8rdquo)

if (action === rdquoSignuprdquo)

formData = rsquorsquo

reqon(rdquodatardquo function (data)

formData += data

)

reqon(rdquoendrdquo function ()

user = qsparse(formData)

resend(rdquolth1gtrdquo + userusername + rdquo 歡迎您的加入 lth1gtltpgt 我們已經將會員啟用信寄至rdquo + useremail + rdquoltpgtrdquo)

)

else

fsreadFile(filePath encode function(err file)

reswrite(file)

resend()

)

)

serverlisten(3000)

consolelog(rsquoServer 跑起來了現在時間是rsquo + new Date())

62 6 Express介紹

Nodejs中文電子書

68 Express AJAX應用範例

在 Nodejs要使用 Ajax傳送資料並且與之互動在接受資料的部份沒有太大的差別client端不是用 GET就是用 POST來傳資料重點在處理完後用 JSON格式回傳當然 Ajax不見得只傳 JSON格式有時是回傳一段 HTML碼不過後者對伺服器來說基本上就和前兩篇沒有差別了所以我們還是以回傳 JSON做為這一回的主題

這一回其實大多數的工作都會落在前端 Ajax上面前端要負責發送與接收資料並在接收資料後撤掉原先發送資料的表單並將取得的資料

改成 HTML格式之後放上頁面

首先先準備 HTML靜態頁面資料

ltDOCTYPE htmlgt

lthtmlgt

ltheadgt

lttitlegtNodejs 菜鳥筆記 (3)lttitlegt

ltlink rel=rdquostylesheetrdquo href=rdquocssstylecssrdquo type=rdquotextcssrdquo media=rdquoallrdquo gt

ltscript src=rdquohttpsajaxgoogleapiscomajaxlibsjquery171jqueryminjsrdquogtltscriptgt

ltheadgt

ltbodygt

lth1gtNodejs 菜鳥筆記-註冊 (用 Ajax)lth1gt

ltform id=rdquosignuprdquo action=rdquoSignuprdquo method=rdquoPOSTrdquogt

ltlabelgt 使用者名稱ltlabelgtltinput type=rdquotextrdquo id=rdquousernamerdquo name=rdquousernamerdquo gtltbrgt

ltlabelgt 電子郵件ltlabelgtltinput type=rdquotextrdquo id=rdquoemailrdquo name=rdquoemailrdquo gtltbrgt

ltinput type=rdquosubmitrdquo value=rdquo 註冊我的帳號rdquo gtltbrgt

ltformgt

ltdiv id=rdquoresultrdquogt

ltdivgt

ltscript type=rdquotextjavascriptrdquogt

$(function()$(rsquosignup input[type=rdquosubmitrdquo]rsquo)click(function(e)

var user =

userusername = $(rdquousernamerdquo)val()useremail = $(rdquoemailrdquo)val()

$post(rdquoSignuprdquouserfunction(data)greet(data)

)

68 Express AJAX應用範例 63

Nodejs中文電子書

epreventDefault()

)

var greet = function(msg)

var resultNode = $(rsquoresultrsquo)greeting = $(rsquolth2gtHirsquo+msgusername+rsquo 你的會員 id 是rsquo+ msgid +rsquo 我們會將會員啟動信寄至rsquo+msgemail+rsquolth2gtrsquo)

$(rsquosignuprsquo)hide()resultNodehtml(rdquordquo)

resultNodeappend(greeting)

)

ltscriptgt

ltbodygt

lthtmlgt

HTML頁面上準備了一個表單用來傳送註冊資料接著直接引用了Google CDN來載入 jQuery用來幫我們處理 Ajax的工作這次要傳送和接收的工作很大的變動都在 HTML頁面上的 JavaScript當中我們要做的事有 (相關 jQuery處理這邊不多做贅述指提起主要功能解說)

bull 用 jQUery取得 submit按鈕綁定它的 click動作

bull 取得表單 username和 email的值存放在 user這個物件中

bull 用 jQuery的 $post方法將 user的資料傳到 Server

bull 一旦成功取得資料後透過 greet這個 function組成回報給 user的訊息

bull 清空原本給使用者填資料的表單

bull 將 Server回傳的 usernameemail和 id這 3個資料組成回應的訊息

bull 將訊息放到原本表單的位置

經過以上的處理後一個 Ajax的表單的基本功能已經完成

接著進行 nodejs主要程式的編輯部分程式碼如下

var fs = require(rdquofsrdquo)

64 6 Express介紹

Nodejs中文電子書

qs = require(rsquoquerystringrsquo)

if (action === rdquoSignuprdquo)

formData = rsquorsquo

reqon(rdquodatardquo function (data)

formData += data

)

reqon(rdquoendrdquo function ()

var msg

user = qsparse(formData)

userid = rdquo123456rdquo

msg = JSONstringify(user)

reswriteHead(200 rdquoContent-Typerdquordquoapplicationjson charset=utf-8rdquordquoContent-Lengthrdquomsglength)

resend(msg)

)

這裡的程式和前面 POST範例基本上大同小異差別在

bull 幫 user的資料加上 id隨意存放一些文字進去讓 Server回傳的資料多於 Client端傳上來的不然會覺得 Server都沒做事

bull 增加了 msg 這個變數存放將 user 物件 JSON 文字化的結果JSONstringify這個轉換函式是 V8引擎所提供的如果你好奇的話

bull 大重點來了我們要告訴 Client端這次回傳的資料格式是 JSON所在 Content-type和 Content-Length要提供給 Client

Server很輕鬆就完成任務了最後進行程式測試啟動 nodejs主程式之後開啟瀏覽器就會看到表單填寫完畢按下送出就可以看到結果了

最後 nodejs本篇範例程式碼如下

var http = require(rsquohttprsquo)

url = require(rsquourlrsquo)

fs = require(rdquofsrdquo)

qs = require(rsquoquerystringrsquo)

server

68 Express AJAX應用範例 65

Nodejs中文電子書

server = httpcreateServer(function (reqres)

var urlData

encode = rdquoutf8rdquo

filePath = rdquoviewexpress_ajax_example_formhtmlrdquo

formData

action

urlData = urlparse(requrltrue)

action = urlDatapathname

if (action === rdquoSignuprdquo)

formData = rsquorsquo

reqon(rdquodatardquo function (data)

formData += data

)

reqon(rdquoendrdquo function ()

var msg

user = qsparse(formData)

userid = rdquo123456rdquo

msg = JSONstringify(user)

reswriteHead(200 rdquoContent-TyperdquordquoapplicationjsonrdquordquoContent-Lengthrdquomsglength)

resend(msg)

)

else

fsreadFile(filePath encode function(err file)

reswriteHead(200 rdquoContent-Typerdquordquotexthtml charset=utf-8rdquo)

reswrite(file)

resend()

)

)

serverlisten(3000)

66 6 Express介紹

Nodejs中文電子書

consolelog(rsquoServer 跑起來了現在時間是rsquo + new Date())

69 原始資料提供

bull [NodeJS初學者筆記 (1)-用 GET傳送資料] (httpithelpithomecomtwquestion10087402)

bull [NodeJS初學者筆記 (2)-用 POST傳送資料] (httpithelpithomecomtwquestion10087489)

bull [NodeJS 初學者筆記 (3)-用 Ajax 傳送資料] (httpithelpithomecomtwquestion10087627)

69 原始資料提供 67

Nodejs中文電子書

68 6 Express介紹

7

CoffeeScript

bull Spine

bull Hem

bull Spineapp

Letrsquos Have a Cup of CoffeeScript

bull es5-shim ECMAScript 5 compatibility shims for legacy JavaScript engines

ESnext

node-iform - A middleware for node-validator httpsgithubcomguileennode-iform

69

Nodejs中文電子書

70 7 CoffeeScript

8

製作一個 Hubot的 Plurk Adapter

81 應用事項提醒

此應用範例以CoffeeScript編寫主要程式架構採用Github釋放的Hubot專案以下有幾個主要注意事項請讀者注意

bull Hubot 一開始的架構除了內建的兩個 Adapter 之外其餘都要以Nodejs的Module方式才能運作

bull Hubot的 binhubot裡面寫著 npm install所以不管你怎麼改原始碼也不能改變第一點的狀況

其實上述都在討論同一件事情NodeJS的模組而且模組不能設定相對路徑之類的來安裝一定要包裝成 npm相符和格式才有辦法正確執行

82 建立 Adapter

首先需要準備兩個檔案

bull packagejson

bull plurkcoffee

71

Nodejs中文電子書

有這兩個就足以變成 NodeJS的模組了在開始 Coding之前先來設定一下 packagejson相依性

rdquonamerdquo rdquohubot-plurkrdquo

rdquoversionrdquo rdquo011rdquo

rdquomainrdquo rdquoplurkrdquo

rdquodependenciesrdquo

rdquohubotrdquo rdquogt=205rdquo

rdquooauthrdquo rdquordquo

rdquocronrdquordquordquo

這邊會使用到NodeJS的 cron模組(Module)而登入 Plurk需要OAuth標準授權才行所以也需要加載 OAuth模組

注意Hubot在讀取非內建模組時會自動在前面加上 hubot-的前置

83 建立 Robot跟 API

本篇應用基本上程式碼大部分參考 Hubot - Twitter Adapter來製作主要差異只有在使用 OAuth這個模組Twitter有 Streaming可用而 Plurk則得用 Comet方式來達到即時讀取

plurkcoffee程式碼

Robot = require(rdquohubotrdquo)robot()

Adapter = require(rdquohubtordquo)adapter()

EventEmitter = require(rdquoeventsrdquo)EventEmitter

oauth = require(rdquooauthrdquo)

cronJob = require(rdquocronrdquo)CronJob

class Plurk exntends Adapter

class PlurkStreaming exnteds EventEmitter

72 8 製作一個 Hubot的 Plurk Adapter

Nodejs中文電子書

先弄個基本架構主要 Class皆參考 Twitter Adapter命名方式接著再將 Plurk這個 Class增加幾個Method基本上只要有 run send reply就夠了而 run用來做初始化的部分

class Plurk entends Adapter

send (plurk id strings⋯) -gt

reply (plurk id strings⋯) -gt

run -gt

看起來有點東西接著來處理主要的 Plurk API結合部分

class PlurkStreaming extends EventEmitter

consuctor (options) -gt

plurk (callback) -gt

觀察河道

getChannel -gt

取得 Comet 網址

reply (plurk id message) -gt

回噗

acceptFriends -gt

接受好友

get (path callback) -gt

GET 請求

post (path body callback)-gt

POST 請求(其實是裝飾)

request (method path body callback)-gt

主要的 OAuth 請求

comet (server callback)-gt

噗浪的 Comet 傳回是 JavaScript Callback 要另外處理後才會變成 JSON

然後把注意力集中到 constructor上先把建構子弄好

constructor (options) -gt

super()

if optionskey and optionssecret and optionstoken and optionstoken secret

key = optionskey

secret = optionssecret

token = optionstoken

token secret = optionstoken secret

83 建立 Robot跟 API 73

Nodejs中文電子書

建立 OAuth 連接

consumer = new oauthOAuth(

rdquohttpwwwplurkcomOAuthrequest tokenrdquo

rdquohttpwwwplurkcomOAuthaccess tokenrdquo

key

secret

rdquo10rdquo

rdquohttpwwwplurkcomOAuthauthorizerdquo

rdquoHMAC-SHA1rdquo

)

domain = rdquowwwplurkcomrdquo

初始化取得 Comet 網址

do getChannel

else

throw new Error(rdquo 參數不足需要 Key Secret Token Token Secretrdquo)

接著來處理 request這個 method

request (method path body callback) -gt

記錄一下這次的 Request

consolelog(rdquohttpdomainpathrdquo)

Callback 這邊先不丟進去要用另一種方式處理

request = consumerget(rdquohttpdomainpathrdquo token token secret null)

requeston rdquoresponserdquo (res) -gt

reson rdquodatardquo (chunk) -gt

parseResponse(chunk+rsquorsquo callback)

reson rdquoendrdquo (data) -gt

consolelog rdquoEnd Request pathrdquo

reson rdquoerrorrdquo (data) -gt

consolelog rdquoError rdquo + data

requestend()

處理資料

parseResponse = (data callback) -gt

if datalength gt 0

用 TryCatch 避免處理 JSON 出錯導致整個中斷

try

callback null JSONparse(data)

catch err

74 8 製作一個 Hubot的 Plurk Adapter

Nodejs中文電子書

consolelog(rdquoError Parse JSONrdquo + data err)

繼續執行

callback null data ||

大致上就是這樣上面程式的架構已經將整個 Hubot Plurk Adapter完成因為在測試時竟然因為噗浪 Lag而沒讀到完整的 Comet資料然後造成程式異常為了避免這個問題發生因此需要加上為了完美呈現需要再

加上 Comet的處理所以要使用到 EventEmitter的功能

comet (server callback) -gt

在 Callback 裡面會找不到自身所以設定區域變數

self =

記錄一下這次的 Request

consolelog(rdquo[Comet] serverrdquo)

Callback 這邊先不丟進去要用另一種方式處理

request = consumerget(rdquohttpdomainpathrdquo token token secret null)

requeston rdquoresponserdquo (res) -gt

reson rdquodatardquo (chunk) -gt

parseResponse(chunk+rsquorsquo callback)

reson rdquoendrdquo (data) -gt

consolelog rdquoEnd Request pathrdquo

請求結束發出事件通知可以進行下一次請求

selfemit rdquonextPlurkrdquo

reson rdquoerrorrdquo (data) -gt

consolelog rdquoError rdquo + data

requestend()

處理資料

parseResponse = (data callback) -gt

if datalength gt 0

用 trycatch 避免失敗中斷

try

去掉 JavaScript 的 Callback

data = datamatch(CometChannelscriptCallback((+))s)

jsonData = rdquordquo

if data

83 建立 Robot跟 API 75

Nodejs中文電子書

jsonData = JSONparse(data[1])

else

如果沒有任何 Match 嘗試直接 parse

jsonData = JSONparse(data)

catch err

consolelog(rdquo[Comet] Errorrdquo data err)

用 TryCatch 避免處理 JSON 出錯導致整個中斷

try

只傳入 json 的 data 部分

callback null jsonDatadata

catch err

consolelog(rdquo[Comet]Error Parse JSONrdquo + data err)

繼續執行

callback null data ||

後面的 get跟 post就簡單多了

get (path callback) -gt

request(rdquoGETrdquo path null callback)

post (path body callback) -gt

request(rdquoPOSTrdquo path body callback)

接著處理取的 Comet網址的 getChannel

getChannel -gt

self =

get rdquoAPPRealtimegetUserChannelrdquo (error data) -gt

if error

檢查是否有 comet server

if datacomet server

selfchannel = datacomet server

如果沒有 Channel Ready 就嘗試連接會失敗

selfemit(rsquochannel readyrsquo)

那麼先來處理 Plurk Adaper好處理的部份

send (plruk id strings⋯)-gt

跟 Reply 一樣直接交給 reply 做

reply plurk id strings⋯

76 8 製作一個 Hubot的 Plurk Adapter

Nodejs中文電子書

reply (plurk id strings⋯) -gt

stringsforEach (message) =gt

botreply(plruk id message)

接著把 run處理好就可以上線運作摟

run -gt

self =

options =

key processenvHUBOT PLURK KEY

secret processenvHUBOT PLURK SECRET

token processenvHUBOT PLURK TOKEN

token secret processenvHUBOT PLURK TOKEN SECRET

創建剛剛的 API

bot = new PlurkStreaming(options)

依照 Twitter 的 new RobotTextMessage 會沒有反應所以參考 hubot-minecraft 的方式

r = robotconstructor

處理噗浪河道訊息

doPlurk = (data)-gt

檢查是否為回噗

if dataresponse

datacontent raw = dataresponsecontent raw

datauser id = dataresponseuser id

確定有噗浪 ID 跟訊息

if dataplurk id and datacontent raw

selfreceive new rTextMessage(dataplurk id datacontent raw)

取得 Comet Server 完成開始第一次 Comet 連接

boton rdquochannel readyrdquo () -gt

botplurk selfdoPlurk

上一次 Comet 完成繼續 Polling

boton rdquonextPlurkrdquo ()-gt

botplurk selfdoPlurk

定時接受好友邀請

do botacceptFriends

bot = bot

83 建立 Robot跟 API 77

Nodejs中文電子書

終於完成 AdapterHubot專案裡面的 scripts資料夾內是互動部分不需要像 Adapter如此大費周章處理只新增檔案並且設計好對白之後就會回噗了我開發用的機器人在此大家可以去跟他玩玩[httpplurkcomelct9620 bot](httpplurkcomelct9620 bot)

84 原始資料提供

bull [製 作 一 個 Hubot 的 噗 浪 Adapter](http revo-skillfrosttw blog20120318create-a-hubot-plurk-adapter)

78 8 製作一個 Hubot的 Plurk Adapter

9

Nodejs好用工具介紹

91 logio

Real-time log monitoring in your browser

httplogioorg

nodejs (gt=0412 lt=0611)

npm config set unsafe-perm true

npm install -g --prefix=usrlocal logio

logio server start

httplocalhost8998

79

Nodejs中文電子書

80 9 Nodejs好用工具介紹

10

精選文章

101 Express

bull 使用 NodeJS + Express從 GETPOST Request取值 [邀稿中]

bull link2 [邀稿中]

bull link3 [邀稿中]

81

Nodejs中文電子書

82 10 精選文章

11

參考資源

111 Nodejs書籍

bull Programming Nodejs April 2012 (est) Travis Swicegood Pragmatic Bookshelf

bull e Little Book on CoffeeScript January 2012 Alex MacCaw OrsquoReilly Media

bull Node for Front-End Developers January 2012 Garann Means OrsquoReilly Media

bull Building Hypermedia APIs with HTML5 and Node November 2011 MikeAmundsen OrsquoReilly Media

bull Node Web Development August 2011 David Herron PacktPub

bull CoffeeScript Accelerated JavaScript Development July 2011 Trevor BurnhamPragmatic Bookshelf

bull Getting Started with GEO CouchDB and Nodejs July 2011 Mick omp-son OrsquoReilly Media

bull What Is Node July 2011 Brett McLaughlin OrsquoReilly Media

bull Node Up and Running May 2011 Tom Hughes-Croucher Mike WilsonOrsquoReilly Media

83

Nodejs中文電子書

bull Hands-on Nodejs e Nodejs introduction and API reference May 2011 Pe-dro Teixeira LeanPub

bull e Node Beginner Book Manuel Kiessling

bull Mastering Nodejs visionmedia

112 Nodejs影音教學

bull e Node Sessions e Best of OSCON 2011 August 2011 OrsquoReilly Media(Video)

bull Tom Hughes-Croucher on Node March 2011 OrsquoReilly Media (Video)

113 Nodejs教學網站

bull Node Tuts

114 Nodejs課程

bull Introduction To Nodejs Van Nguyen CodeLesson

84 11 參考資源

Page 5: Nodejs Wiki

83 建立 Robot跟 API 7284 原始資料提供 78

9 Nodejs好用工具介紹 79

91 logio 79

10 精選文章 81

101 Express 81

11 參考資源 83

111 Nodejs書籍 83112 Nodejs影音教學 84113 Nodejs教學網站 84114 Nodejs課程 84

iii

iv

關於本書

這是一本關於 Nodejs技術的開放源碼電子書我們使用 GitHub維護電子書內容並交由 ContPub(Continuous Publishing)系統自動線上發佈本書提供 PDFEPUBMOBI及 HTML等格式您除了可以在網站檢視本書所有內容也可以將電子書下載至閱讀器保存

本書的線上閱讀網址與 GitHub資料同步更新

httpbooknodejstw

如果您想要取得本書的其他格式可以從 ContPub的電子書專頁下載最新版本

httpcontpuborgreadnodejs-wiki-book

本書適合 Nodejs初學者至進階開發者也歡迎您在學習時一起參與本書內容撰寫

授權

Nodejs台灣社群協作電子書採用創用 CC姓名標示 -非商業性授權您不必為本書付費

Nodejs Wiki Book book is licensed under the Attribution-NonCommercial 30Unported license You should not have paid for this book

您可以複製散佈及修改本書內容但請勿將本書用於商業用途

您可以在以下網址取得授權條款全文

1

Nodejs中文電子書

httpcreativecommonsorglicensesby-nc30legalcode

作者

本書由 Nodejs Taiwan社群成員協作以下名單依照字母排序

bull Caesar Chi (clonn)

bull Fillano Feng ( llano)

bull Kyle Lin (lyhcode)

Nodejs Taiwan是一個自由開放的技術學習社群我們歡迎您加入一起學習研究及分享

下載電子書

線上閱讀本書

httpbooknodejstw

PDF格式適合一般電腦及 7吋以上平板電腦閱讀

httpcontpuborgdownloadnodejs-wiki-bookpdf

EPUB格式適合 iPadiPhone行動裝置閱讀

httpcontpuborgdownloadnodejs-wiki-bookepub

MOBI格式適合 Kindle電子書閱讀器

httpcontpuborgdownloadnodejs-wiki-bookmobi

原始碼

本書最新的原始碼(中文版)網址如下

httpgithubcomnodejs-twnodejs-wiki-book

2 關於本書

Nodejs中文電子書

01 精選文章收錄流程

精選文章的用意是鼓勵作者在自已的網誌發表 Nodejs 教學再由Nodejs Taiwan社群挑選系列文章列入電子書的精選文集

1 將文章標題及連結貼到「精選文章」分類

2 社群工作小組以 E-Mail通知作者文章列入精選並邀請將內文授權給Nodejs Taiwan電子書分享

3 作者同意後由工作小組負責整理圖文發佈至電子書

4 以 E-Mail寄出感謝函通知原作者文章已收錄依作者意願調整文章內容

5 於 Nodejs Taiwan首頁及粉絲專頁推薦作者的文章

01 精選文章收錄流程 3

Nodejs中文電子書

4 關於本書

前言

Nodejs是 JavaScript程式語言的開發框架由 04x至目前 v060版本核心上已經有許多變更當然目的只有一個就是讓開發變得更簡單速

度能夠更快這是所有人奮鬥的目標而 nodeJS 華文維基平台主力由nodeJStw一群喜好 javascript開發者共同主筆內文以中文為主希望能夠降低開發者學習門檻藉由我們拋磚引玉讓更多華人開發者共同學習

討論

CoffeeScript好好玩

5

Nodejs中文電子書

6 前言

1

Nodejs簡介

Nodejs是一個高效能易擴充的網站應用程式開發框架 (Web Applica-tion Framework)它誕生的原因是為了讓開發者能夠更容易開發高延展性的網路服務不需要經過太多複雜的調校效能調整及程式修改就能

滿足網路服務在不同發展階段對效能的要求

Ryan Dahl是NodeJS的催生者目前任職於 Joyent (主機託管服務公司)他開發 NodeJS的目的就是希望能解決 Apache在連線數量過高時緩衝區 (buffer)和系統資源會很快被耗盡的問題希望能建立一個新的開發框架以解決這個問題因此嘗試使用效能十分優秀的 V8 JavaScript Engine讓網站開發人員熟悉的 JavaScript程式語言也能應用於後端服務程式的開發並且具有出色的執行效能

JavaScript是功能強大的物件導向程式語言但是在 JavaScript的官方規格中主要是定義網頁 (以瀏覽器為基礎) 應用程式需要的應用程式介面(API)對 JavaScript程式的應用範圍有所侷限為使 JavaScript能夠在更多用途發展CommonJS規範一組標準函式庫 (standard library)使 JavaScript的應用範圍能夠和 RubyPython及 Java等語言同樣豐富撰寫 NodeJS的JavaScript程式碼符合 CommonJS規範可以使用 CommonJS API為基礎開發程式並且在不同的 CommonJS兼容 (compliant) JavaScript執行環境中程式碼具有可攜性

7

Nodejs中文電子書

瀏覽器的 JavaScript與實現 CommonJS規範的 NodeJS有何不同呢瀏覽器的 JavaScript 提供 XMLHttpRequest 讓程式可以和網頁伺服器建立資料傳輸連線但這通常只能適用於網站開發的需求因為我們只能用

XMLHttpRequest與網頁伺服器通訊卻無法利用它建立其他類型如 Telnet FTP NTP的伺服器通訊如果我們想開發網路服務程式例如 SMTP電子郵件伺服器就必須使用 Sockets建立 TCP (某些服務則用 UDP)監聽及連線其他程式語言如 PHPJavaPythonPerl及 Ruby等在標準開發環境中皆有提供 Sockets API而瀏覽器的 JavaScript基於安全及貼近網站設計需求的考量下並未將 Sockets列入標準函式庫之中CommonJS的規範填補這種基礎函式庫功能的空缺遵循 CommonJS規範的 NodeJS可以直接使用 Sockets API建立各種網路服務程式

JavaScript語言本身支援 Lambda的特性因此一個匿名函式 (anonymousfunction)可以被儲存成一個變數並當作參數傳遞給另一個函式

var proc1 = function(op x) return op(x)

var op1 = function(x) return x+1 var op2 = function(x) return xx

proc1(op1 3) result is 4 proc1(op2 5) result is 25

另一個 JavaScript開發者必須掌握的語言特性稱為 Closure

NodeJS符合 CommonJS的規範使得 Callback方式易於實現也能夠讓更多同好基於 JavaScript開發符合 NodeJS的外掛模組 (Module)

回想以前要寫一個能夠同時容納上百人的上線的網路服務需要花費

多大的苦工可能 10人多就需要經過一次程式調整而 NodeJS就是為了解決這個困境NodeJS因此誕生它是一種利用 V8 Javascript編譯器所開發的產品利用 V8編譯器的高效能與 Javascript的程式開發特性所產生的網路程式

開發人員所編寫出來的 Javascript腳本程式怎麼可能會比其他語言寫出來的網路程式還要快上許多呢以前的網路程式原理是將使用者每次的

連線 (connection)都開啟一個執行緒 (thread)當連線爆增的時候將會快速耗盡系統效能並且容易產生阻塞 (block)的發生

8 1 Nodejs簡介

Nodejs中文電子書

NodeJS對於資源的調校有所不同當程式接收到一筆連線 (connection)會通知作業系統透過 epoll kqueue devpoll或 select將連線保留並且放入heap中配置先讓連線進入休眠 (Sleep)狀態當系統通知時才會觸發連線的 callback這種處理連線方式只會佔用掉記憶體並不會使用到 CPU資源另外因為採用 Javascript語言的特性每個 request都會有一個 callback如此可以避免發生 Block的狀況發生

基於 Callback特性目前NodeJS大多應用於 Comet(long pulling) RequestServer或者是高連線數量的網路服務上目前也有許多公司將 NodeJS設為內部核心網路服務之一在 NodeJS也提供了外掛管理 (Node packagemanagement)讓愛好 NodeJS輕易開發更多有趣的服務外掛並且提供到 npm讓全世界使用者快速安裝使用

本書最後執行測試版本為 nodejs v068相關 API文件可查詢 lsquohttpnodejsorg lthttpnodejsorggtlsquo本書所有範例均可於 linux windows上執行如遇到任何問題歡迎至 lsquohttpnodejstw lthttpnodejstwgtlsquo詢問對於nodejs相關問題

9

Nodejs中文電子書

10 1 Nodejs簡介

2

JavaScript與 NodeJS

其實使用 JavaScript 在網頁端與伺服器端的差距並不大但是為了使NodeJS可以發揮他最強大的能力有一些知識還是必要的所以還是針對這些主題介紹一下其中 Event LoopScope以及 Callback其實是比較需要了解的基本知識cpscurrying ow control是更進階的技巧與應用

21 Event Loop

可能很多人在寫 Javascript 時並不知道他是怎麼被執行的這個時候可以參考一下 jQuery作者 John Resig一篇好文章介紹事件及 timer怎麼在瀏覽器中執行How JavaScript Timers Work通常在網頁中所有的Javascript執行完畢後(這部份全部都在 global scope跑除非執行函數)接下來就是如 John Resig解釋的這樣所有的事件處理函數以及 timer執行的函數會排在一個 queue結構中利用一個無窮迴圈不斷從 queue中取出函數來執行這個就是 event loop

(除了 John Resig的那篇文章Nicholas C Zakas的 ldquoProfessional Javascriptfor Web Developer 2nd editionrdquo 有一個試閱本httpyuiblogcomassetspdfzakas-projs-2ed-ch18pdf598頁剛好也有簡短的說明)

所以在 Javascript中雖然有非同步但是他並不是使用執行緒所有

11

Nodejs中文電子書

的事件或是非同步執行的函數都是在同一個執行緒中利用 event loop的方式在執行至於一些比較慢的動作例如 IO網頁 render re ow等實際動作會在其他執行緒跑等到有結果時才利用事件來觸發處理函數來處理

這樣的模型有幾個好處沒有執行緒的額外成本所以反應速度很快不會

有任何程式同時用到同一個變數不必考慮 lock也不會產生 dead lock所以程式撰寫很簡單但是也有一些潛在問題任一個函數執行時間較長都

會讓其他函數更慢執行(因為一個跑完才會跑另一個)在多核心硬體普遍

的現在無法用單一的應用程式 instance發揮所有的硬體能力用 NodeJS撰寫伺服器程式碰到的也是一樣的狀況要讓系統發揮 event loop的效能就要盡量利用事件的方式來組織程式架構另外對於一些有可能較為耗

時的操作可以考慮使用 processnextTick函數來讓他以非同步的方式執行避免在同一個函數中執行太久擋住所有函數的執行

如果想要測試 event loop怎樣在「瀏覽器」中運行可以在函數中呼叫alert()這樣會讓所有 Javascript的執行停下來尤其會干擾所有使用 timer的函數執行有一個簡單的例子這是一個會依照設定的時間間隔嚴格執

行動作的動畫如果時間過了就會跳過要執行的動作點按圖片以後人

物會快速旋轉但是在旋轉執行完畢前按下「delay」按鈕讓 alert訊息等久一點接下來的動畫就完全不會出現了

22 Scope與 Closure

要快速理解 JavaScript的 Scope(變數作用範圍)原理只要記住他是Lexical Scope就差不多了簡單地說變數作用範圍是依照程式定義時(或者叫做程式文本)的上下文決定而不是執行時的上下文決定

為了維護程式執行時所依賴的變數即使執行時程式運行在原本的

scope之外他的變數作用範圍仍然維持不變這時程式依賴的自由變數(定義時不是 local的而是在上一層 scope定義的變數)一樣可以使用就好像被關閉起來所以叫做 Closure用程式看比較好懂

function outter(arg1)

arg1 及 free_variable1 對 inner 函數來說都是自由變數

var free_variable1 = 3

return function inner(arg2)

12 2 JavaScript與 NodeJS

Nodejs中文電子書

var local_variable1 =2arg2 及 local_variable1 對 inner 函數來說都是本地變數

return arg1 + arg2 + free_variable + local_variable1

var a = outter(1)變數 a就是 outter函數執行後返回的 inner函數 var b =a(4)執行 inner函數執行時上下文已經在 outter函數之外但是仍然能正常執行而且可以使用定義在 outter函數裡面的 arg1及 free variable1變數 consolelog(b)結果 10

在 Javascript中scope最主要的單位是函數(另外有 global及 eval)所以有可能製造出 closure的狀況通常在形式上都是有巢狀的函數定義而且內側的函數使用到定義在外側函數裡面的變數

Closure有可能會造成記憶體洩漏主要是因為被參考的變數無法被垃圾收集機制處理造成佔用的資源無法釋放所以使用上必須考慮清楚

不要造成意外的記憶體洩漏(在上面的例子中如果 a一直未執行使用到的記憶體就不會被釋放)

跟透過函數的參數把變數傳給函數比較起來Javascript Engine會比較難對 Closure進行最佳化如果有效能上的考量這一點也需要注意

23 Callback

要介紹 Callback之前要先提到 JavaScript的特色

JavaScript是一種函數式語言(functional language)所有 Javascript語言內的函數都是高階函數 (higher order function這是數學名詞計算機用語好像是 rst class function意指函數使用沒有任何限制與其他物件一樣)也就是說函數可以作為函數的參數傳給函數也可以當作函數的返回值這個特性讓 Javascript的函數使用上非常有彈性而且功能強大

callback在形式上其實就是把函數傳給函數然後在適當的時機呼叫傳入的函數Javascript使用的事件系統通常就是使用這種形式NodeJS中有一個物件叫做 EventEmitter這是 NodeJS事件處理的核心物件所有會使用事件處理的函數都會「繼承」這個物件(這裡說的繼承實

作上應該像是 mixin)他的使用很簡單可以使用物件on(事件名稱callback

23 Callback 13

Nodejs中文電子書

函數) 或是物件addListener(事件名稱callback 函數) 把你想要處理事件的函數傳入在物件中可以使用物件emit(事件名稱 參數) 呼叫傳入的callback函數這是 Observer Pattern的簡單實作而且跟在網頁中使用 DOM的 addEventListener使用上很類似也很容易上手不過 NodeJS是大量使用非同步方式執行的應用所以程式邏輯幾乎都是寫在 callback函數中當邏輯比較複雜時大量的 callback會讓程式看起來很複雜也比較難單元測試舉例來說

var p client = new Db(lsquointegration tests 20rsquo new Server(ldquo127001rdquo 27017) lsquopkrsquoCustomPKFactory) p clientopen(function(err p client)

p clientdropDatabase(function(err done)

p clientcreateCollection(lsquotest custom keyrsquo function(err collection)

collectioninsert(lsquoarsquo1 function(err docs)

collection nd(lsquo idrsquonew ObjectID(ldquoaaaaaaaaaaaardquo) function(err cursor)

cursortoArray(function(err items) testassertEquals(1 itemslength) p clientclose()

)

)

)

)

)

)

這是在網路上看到的一段操作 mongodb的程式碼為了循序操作所以必須在一個 callback裡面呼叫下一個動作要使用的函數這個函數裡面還是會使用 callback最後就形成一個非常深的巢狀

這樣的程式碼會比較難進行單元測試有一個簡單的解決方式是

盡量不要使用匿名函數來當作 callback或是 event handler透過這樣的方式

14 2 JavaScript與 NodeJS

Nodejs中文電子書

就可以對各個 handler做單元測試了例如

var http = require(lsquohttprsquo) var tools =

cookieParser function(request response) if(requestheaders[rsquoCookiersquo]) do parsing

var server = httpcreateServer(function(request response)

thisemit(lsquoinitrsquo request response)

) serveron(lsquoinitrsquo toolscookieParser) serverlisten(8080 lsquo127001rsquo)

更進一步可以把 tools改成外部 module例如叫做 toolsjs

moduleexports = cookieParser function(request response) if(requestheaders[rsquoCookiersquo]) do parsing

然後把程式改成

var http = require(lsquohttprsquo)

var server = httpcreateServer(function(request response) thisemit(lsquoinitrsquo re-quest response)

) serveron(lsquoinitrsquo require(lsquo toolsrsquo)cookieParser) serverlisten(8080lsquo127001rsquo)

這樣就可以單元測試 cookieParser了例如使用 nodeunit時可以這樣寫

var testCase = require(lsquonodeunitrsquo)testCase moduleexports = testCase(

ldquosetUprdquo function(cb) thisrequest = headers Cookielsquoname1val1 name2val2rsquo thisresponse = thisresult= name1rsquoval1rsquoname2rsquoval2rsquo

cb()

ldquotearDownrdquo function(cb)

cb()

23 Callback 15

Nodejs中文電子書

ldquonormal caserdquo function(test)

testexpect(1) var obj = require(lsquotoolsrsquo)cookieParser(thisrequest thisresponse)testdeepEqual(obj thisresult) testdone()

)

善於利用模組可以讓程式更好維護與測試

24 CPS(Continuation-Passing Style)

cps 是 callback 使用上的特例形式上就是在函數最後呼叫 callback這樣就好像把函數執行後把結果交給 callback 繼續運行所以稱作continuation-passing style利用 cps可以在非同步執行的情況下透過傳給 callback的這個 cps callback來獲知 callback執行完畢或是取得執行結果例如

lthtmlgt ltbodygt ltdiv id=rdquopanelrdquo style=rdquovisibilityhiddenrdquogtlt divgtlt bodygt lt htmlgt ltscriptgt var request = new XMLHttpRequest() re-questopen(lsquoGETrsquo lsquotest749txt timestamp=rsquo+new Date()getTime() true) re-questaddEventListener(lsquoreadystatechangersquo function(next)

return function() if(thisreadyState===4ampampthisstatus===200) next(thisresponseText)lt== 傳入的 cps callback 在動作完成時執行並取得結果進一步處理

(function(str)lt==這個匿名函數就是 cps callback

documentgetElementById(lsquopanelrsquo)innerHTML=str docu-mentgetElementById(lsquopanelrsquo)stylevisibility = lsquovisiblersquo

) false) requestsend() ltscriptgt

進一步的應用也可以參考 2-6流程控制

16 2 JavaScript與 NodeJS

Nodejs中文電子書

25 函數返回函數與 Currying

前面的 cps範例裡面使用了函數返回函數這是為了把 cps callback傳遞給 onreadystatechange事件處理函數的方法(因為這個事件處理函數並沒有設計好會傳送接收這樣的參數)實際會執行的事件處理函數其實是內層返回的那個函數之外包覆的這個函數主要是為了利用 Closure把 next傳給內層的事件處理函數這個方法更常使用的地方是為了解決一些

scope問題例如

ltscriptgt var accu=0count=10 for(var i=0 iltcount i++)

setTimeout(

function() countndash accu+=i if(countlt=0)

consolelog(accu)

50)

ltscriptgt

最後得出的結果會是 100而不是想像中的 45這是因為等到 setTime-out指定的函數執行時變數 i已經變成 10而離開迴圈了要解決這個問題就需要透過 Closure來保存變數 i

ltscriptgt var accu=0count=10 for(var i=0 iltcount i++)

setTimeout(

function(i) return function() countndash

accu+=i if(countlt=0)

consolelog(accu)

(i)

50)

25 函數返回函數與 Currying 17

Nodejs中文電子書

淺藍色底色的部份是跟上面例子不一樣的地方 ltscriptgt

函數返回函數的另外一個用途是可以暫緩函數執行例如

function add(m n) return m+n

var a = add(20 10) consolelog(a)

add這個函數必須同時輸入兩個參數才有辦法執行如果我希望這個函數可以先給它一個參數等一些處理過後再給一個參數然後得到結

果就必須用函數返回函數的方式做修改

function add(m)

return function(n) return m+n

var wait another arg = add(20)先給一個參數 var a = function(arr)

var ret=0 for(var i=0iltarrlengthi++) ret+=arr[i] return ret

([1234])計算一下另一個參數 var b = wait another arg(a)然後再繼續執行 consolelog(b)

像這樣利用函數返回函數使得原本接受多個參數的函數可以一次

接受一個參數直到參數接收完成才執行得到結果的方式有一個學名就

叫做Currying

綜合以上許多奇技淫巧就可以透過用函數來處理函數的方式調整

程式流程接下來看看

26 流程控制

(以 sync方式使用 async函數避開巢狀 callback循序呼叫 async callback等奇技淫巧)

建議參考

bull httphowtonodeorgcontrol- ow

bull httphowtonodeorgcontrol- ow-part-ii

18 2 JavaScript與 NodeJS

Nodejs中文電子書

bull httphowtonodeorgcontrol- ow-part-iii

bull httpblogmixunet20110202essential-node-js-patterns-and-snippets

這幾篇都是非常經典的 NodeJSJavascript流程控制好文章(阿mixu是在介紹一些 pattern時提到這方面的主題)不過我還是用幾個簡單的程式介紹一下做法跟概念

並發與等待

下面的程式參考了 mixu文章中的做法

var wait = function(callbacks done) consolelog(lsquowait startrsquo) var counter = call-backslength var results = [] var next = function(result) 接收函數執行結果並判斷是否結束執行 resultspush(result) if(ndashcounter == 0) done(results)如果結束執行就把所有執行結果傳給指定的 callback處理 for(var i = 0 i lt callbackslength i++) 依次呼叫所有要執行的函數 callbacks[i](next) consolelog(lsquowait endrsquo)

wait( [ function(next) setTimeout(function() consolelog(lsquodone arsquo) var result =500 next(result) 500) function(next) setTimeout(function() con-solelog(lsquodone brsquo) var result = 1000 next(result) 1000) function(next)setTimeout(function() consolelog(lsquodone crsquo) var result = 1500 next(1500) 1500) ] function(results) var ret = 0 i=0 for( iltresultslength i++) ret+= results[i] consolelog(lsquodone all result lsquo+ret)

)

執行結果wait start wait end done a done b done c done all result 3000

可以看出來其實 wait並不是真的等到所有函數執行完才結束執行而是在所有傳給他的函數執行完畢後(不論同步非同步)才執行處理結

果的函數(也就是 done())

不過這樣的寫法還不夠實用因為沒辦法實際讓函數可以等待執行

完畢又能當作事件處理函數來實際使用上面參考到的 Tim Caswell的文

26 流程控制 19

Nodejs中文電子書

章裡面有一種解法不過還需要額外包裝(在他的例子中)NodeJS核心的 fs物件把一些函數(例如 readFile)用 Currying處理類似像這樣

var fs = require(lsquofsrsquo) var readFile = function(path)

return function(callback errback)

fsreadFile(path function(err data)

if(err) errback()

else callback(data)

)

其他部份可以參考 Tim Caswell的文章他的 Doparallel跟上面的 wait差不多意思這裡只提示一下他沒說到的地方

另外一種做法是去修飾一下 callback當他作為事件處理函數執行後再用 cps的方式取得結果

ltscriptgt function Wait(fns done)

var count = 0 var results = [] thisgetCallback = function(index)

count++ return (function(waitback)

return function() var i=0args=[]for(iltargumentslengthi++)

argspush(arguments[i])

argspush(waitback) fns[index]apply(thisargs)

)(function(result) resultspush(result) if(ndashcount == 0)

20 2 JavaScript與 NodeJS

Nodejs中文電子書

done(results)

)

var a = new Wait(

[ function(waitback) consolelog(lsquodone arsquo) var result = 500 wait-back(result) function(waitback) consolelog(lsquodone brsquo) var result =1000 waitback(result) function(waitback) consolelog(lsquodone crsquo) varresult = 1500 waitback(result) ] function(results) var ret = 0 i=0for( iltresultslength i++) ret += results[i] consolelog(lsquodone allresult lsquo+ret)

) var callbacks = [agetCallback(0)agetCallback(1)agetCallback(0)agetCallback(2)]一次取出要使用的 callbacks避免結果提早送出 setTimeout(callbacks[0]500) setTimeout(callbacks[1] 1000) setTimeout(callbacks[2] 1500) setTime-out(callbacks[3] 2000) 當所有取出的 callbacks執行完畢就呼叫 done()來處理結果 ltscriptgt

執行結果

done a done b done a done c done all result 3500

上面只是一些小實驗更成熟的作品是 Tim Caswell 的 stephttpsgithubcomcreationixstep

如果希望真正使用同步的方式寫非同步則需要使用 Promisejs這一類的 library來轉換非同步函數不過他結構比較複雜 XD(見仁見智不過有些人認為 Promise有點過頭了)httpblogsmsdncombrbucktonarchive20110815promise-js-2-0-promise-framework-for-javascriptaspx

如果想不透過其他 Library做轉換又能直接用同步方式執行非同步函數大概就要使用一些需要額外 compile原始程式碼的方法了例如 BrunoJouhier的 streamlinejshttpsgithubcomSagestreamlinejs

26 流程控制 21

Nodejs中文電子書

循序執行

循序執行可以協助把非常深的巢狀 callback結構攤平例如用這樣的簡單模組來做(serialjs)

moduleexports = function(funs) var c = 0 if(isArrayOfFunctions(funs))

throw(lsquoArgument type was not matched Should be array of func-tionsrsquo)

return function()

var args = Arrayprototypeslicecall(arguments 0) if((cgt=funslength))

c++ return funs[c-1]apply(this args)

function isArrayOfFunctions(f ) if(typeof f == lsquoobjectrsquo) return false if(flength)return false if(fconcat) return false if(fsplice) return false var i = 0 for(iltflength i++)

if(typeof f[i] == lsquofunctionrsquo) return false

return true

簡單的測試範例(testSerialjs)使用 fs 模組確定某個 path 是檔案然後讀取印出檔案內容這樣會用到兩層的 callback所以測試中有使用serial的版本與 nested callbacks的版本做對照

var serial = require(lsquoserialrsquo) fs = require(lsquofsrsquo) path = lsquodclientjsrsquo cb = serial([ func-tion(err data)

if(err)

if(dataisFile) fsreadFile(path cb)

22 2 JavaScript與 NodeJS

Nodejs中文電子書

else consolelog(err)

function(err data)

if(err) consolelog(lsquo[ attened by searial]rsquo) con-solelog(datatoString(lsquoutf8rsquo))

else consolelog(err)

]) fsstat(path cb)

fsstat(path function(err data) 第一層 callback if(err)

if(dataisFile)

fsreadFile(path function(err data) 第二層 callback if(err)

consolelog(lsquo[nested callbacks]rsquo) con-solelog(datatoString(lsquoutf8rsquo))

else consolelog(err)

)

else consolelog(err)

)

關鍵在於這些 callback的執行是有順序性的所以利用 serial返回的一個函數 cb來取代這些 callback然後在 cb中控制每次會循序呼叫的函數

26 流程控制 23

Nodejs中文電子書

就可以把巢狀的 callback攤平成循序的 function陣列(就是傳給 serial函數的參數)

測試中的dclientjs 是一個簡單的 dnode 測試程式放在跟 testSerialjs同一個目錄

var dnode = require(lsquodnodersquo)

dnodeconnect(8000 lsquolocalhostrsquo function(remote)

remoterestart(function(str) consolelog(str) processexit()

)

)

執行測試程式後出現結果

[ attened by searial] var dnode = require(lsquodnodersquo)

dnodeconnect(8000 lsquolocalhostrsquo function(remote)

remoterestart(function(str) consolelog(str) processexit()

)

)

[nested callbacks] var dnode = require(lsquodnodersquo)

dnodeconnect(8000 lsquolocalhostrsquo function(remote)

remoterestart(function(str) consolelog(str) processexit()

)

)

對照起來看兩種寫法的結果其實是一樣的但是利用 serialjs巢狀的 callback結構就會消失

不過這樣也只限於順序單純的狀況如果函數執行的順序比較複雜

(不只是一直線)還是需要用功能更完整的流程控制模組比較好例如

httpsgithubcomcaolanasync

24 2 JavaScript與 NodeJS

3

Nodejs安裝與設定

本篇將講解如何在各個不同 OS建立 NodeJS環境目前 NodeJS 048版本環境架設方式需依賴 Linux指令才可編譯完成當然在不同作業系統中也已經有 NodeJS package可以直接使用指令快速架設以下各不同作業系統解說如何安裝 NodeJS

31 Ubuntu Linux

更新推薦使用 nvm

git clone gitgithubcomcreationixnvmgit ~nvm

echo rdquo ~nvmnvmshrdquo gtgt ~bashrc

nvm install v0614

nvm alias default v0614

以 上 可 參 考 http dreamerslabcom blog tw how-to-setup-a-node-js-development-environment-on-ubuntu-11-04

使用 APT套件管理工具是常見的方法以下是使用社群提供的 PPA安裝方式

sudo apt-get install python-software-properties

sudo add-apt-repository ppachris-leanodejs-devel

25

Nodejs中文電子書

sudo apt-get update

sudo apt-get install nodejs

32 Other Linux

Linux很適合作為 NodeJS的伺服器作業系統及開發環境安裝前請先確認以下套件已正確安裝

bull curl (wget)用來下載檔案的工具

bull git先進的版本控制工具

bull g++ GNU C++軟體編譯工具

bull make GNU軟體專案建置工具

安裝指令如下如設有權限問題請在指令前面加上 sudo

git clone httpsgithubcomjoyentnodegit

cd node

git checkout v067

configure

make

sudo make install

接著測試 nodeJS是否正常執行

node --version

出現版本訊息即表示安裝成功

33 Windows

nodeJS 在 v060 版本之後開始正式支援 windows native直接使用nodeexe 就可以執行程式支援性完全與 linux 相同更棒的部份就是不需經過編譯經過下載之後簡單設定完成立即開發 node程式

26 3 Nodejs安裝與設定

Nodejs中文電子書

下載 nodejs安裝檔案 lthttpnodejsorgdownloadgt

如此完成 windows native nodeexe安裝接著可以進入 command line執行測試在 command line輸入rdquonoderdquo會出現 nodejs版本訊息畫面表示安裝完成

33 Windows 27

Nodejs中文電子書

28 3 Nodejs安裝與設定

4

Nodejs基礎

前篇文章已經由介紹安裝至設定都有完整介紹nodeJS 內部除了javascript常用的函式 (function)物件 (object)之外也有許多不同的自訂物件nodeJS預設建立這些物件為核心物件是為了要讓開發流程更為這些資料在官方文件已經具有許多具體說明接下來將會介紹在開發 nodeJS程式時常見的物件特性與使用方法

41 nodejs http伺服器建立

在 lsquonodejs官方網站 lthttpnodejsorggtlsquo裡面有舉一個最簡單的 HTTP伺服器建立一開始初步就是建立一個伺服器平台讓 nodejs可以與瀏覽器互相行為每種語言一開始的程式建立都是以 Hello world開始最初也從 Hello world帶各位進入 nodejs的世界

輸入以下程式碼儲存檔案為 node basic http hello worldjs

var server

ip = rdquo127001rdquo

port = 1337

http = require(rsquohttprsquo)

server = httpcreateServer(function (req res)

29

Nodejs中文電子書

reswriteHead(200 rsquoContent-Typersquo rsquotextplainrsquo)

resend(rsquoHello Worldnrsquo)

)

serverlisten(port ip)

consolelog(rdquoServer running at httprdquo + ip + rdquordquo + port)

程式碼解講一開始需要有幾個基本的變數

bull ip 機器本身的 ip位置因為使用本地端因此設定為 127001

bull port 需要開通的阜號通常設定為 http port 80因範例不希望與基本 port相衝隨意設定為 1337

在 nodejs 的程式中有許多預設的模組可以使用因此需要使用require方法將模組引入在這邊我們需要使用 http這個模組因此將 http載入Http模組裡面內建有許多方法可以使用這邊採用 createServer創建一個基本的 http伺服器再將 http伺服器給予一個 server變數裡面的回呼函式 (call back function)可以載入 http伺服器的資料與回應方法 (requestresponse)在程式裡面就可以看到我們直接回應給瀏覽器端所需的 Header回應內容

reswriteHead(200 rsquoContent-Typersquo rsquotextplainrsquo)

resend(rsquoHello Worldnrsquo)

Http伺服器需要設定 port ip在最後需要設定 Http監聽需要使用到listen事件監聽所有 Http伺服器行為

httplisten(port ip)

所有事情都完成之後需要確認伺服器正確執行因此使用 console在javascript裡就有這個原生物件console所印出的資料都會顯示於 nodejs伺服器頁面這邊印出的資料並不會傳送到使用者頁面上之後許多除壞

(debug)都會用到 console物件

consolelog(rdquoServer running at httprdquo + ip + rdquordquo + port)

30 4 Nodejs基礎

Nodejs中文電子書

42 nodejs http路徑建立

前面已經介紹如何建立一個簡單的 http伺服器接下來本章節將會介紹如何處理伺服器路徑 (rout)問題在 http的協定下所有從瀏覽器發出的要求 (request)都需要經過處理路徑上的建立也是如此

路徑就是指伺服器 ip位置或者是網域名稱之後對於伺服器給予的要求修改剛才的 hello world檔案修改如下

server = httpcreateServer(function (req res)

consolelog(requrl)

reswriteHead(200 rsquoContent-Typersquo rsquotextplainrsquo)

resend(rsquohello worldnrsquo)

)

重新啟動 nodejs程式後在瀏覽器端測試一下路徑行為結果如下圖

當在瀏覽器輸入 http1270011337test 在伺服器端會收到兩個要求一個是我們輸入的test要求另外一個則是faviconicotest的路徑要求http伺服器本身需要經過程式設定才有辦法回應給瀏覽器端所需要的回應在伺服器中所有的路徑要求都是需要被解析才有辦法取得資料從

42 nodejs http路徑建立 31

Nodejs中文電子書

上面解說可以了解到在 nodejs當中所有的路徑都需要經過設定未經過設定的路由會讓瀏覽器無法取得任何資料導致錯誤頁面的發生底下將會解

說如何設定路由同時避免發生錯誤情形先前 nodejs程式需要增加一些修改才能讓使用者透過瀏覽器在不同路徑時有不同的結果根據剛才

的程式做如下的修改

var server

ip = rdquo127001rdquo

port = 1337

http = require(rsquohttprsquo)

url = require(rsquourlrsquo)

path

server = httpcreateServer(function (req res)

path = urlparse(requrl)

reswriteHead(200 rsquoContent-Typersquo rsquotextplainrsquo)

switch (pathpathname)

case rdquoindexrdquo

resend(rsquoI am indexnrsquo)

break

case rdquotestrdquo

resend(rsquothis is test pagenrsquo)

break

default

resend(rsquodefault pagenrsquo)

break

)

serverlisten(port ip)

consolelog(rdquoServer running at httprdquo + ip + rdquordquo + port)

程式做了片段的修改首先載入 url模組另外增加一個 path變數url模組就跟如同他的命名一般專門處理 url字串處理裡面提供了許多方法來解決路徑上的問題因為從瀏覽器發出的要求路徑可能會帶有多種需求

或者 GET參數組合等因此我們需要將路徑單純化取用路徑部分的資料即可例如使用者可能會送出 http1270011337testsend=1如果直接

32 4 Nodejs基礎

Nodejs中文電子書

信任 requrl就會收到結果為testsend=1所以需要透過 url模組的方法將路徑資料過濾

在這邊使用 urlparse的方法裡面帶入網址格式資料會回傳路徑資料為了後需方便使用將回傳的資料設定到 path變數當中在回傳的路徑資料裡面包含資訊如下圖

這邊只需要使用單純的路徑要求直接取用 pathpathname就可以達到我們的目的

最後要做路徑的判別在不同的路徑可以指定不同的輸出在範例中

有三個可能結果第一個從瀏覽器輸入index就會顯示 index結果test 就會呈現出 test頁面最後如果都不符合預期的輸入會直接顯示 default的頁面最後的預防可以讓瀏覽器不會出現非預期結果讓程式的可靠性提昇

底下為測試結果

42 nodejs http路徑建立 33

Nodejs中文電子書

43 nodejs檔案讀取

前面已經介紹如何使用路由(rount)做出不同的回應實際應用只有在瀏覽器只有輸出幾個文字資料總是不夠的在本章節中將介紹如何使

用檔案讀取輸出檔案資料讓使用者在前端瀏覽器也可以讀取到完整的

html css javascript檔案輸出

檔案管理最重要的部分就是 lsquoFile system lthttpnodejsorgdocslatestapifshtmlgtlsquo這個模組此模組可以針對檔案做管理監控讀取等行為裡面有許多預設的方法底下是檔案輸出的基本範例底下會有兩個檔案

第一個是靜態 html檔案

ltDOCTYPE htmlgt

lthtml xmlns=rdquohttpwwww3org1999xhtmlrdquo xmllang=rdquozh-CNrdquo lang=rdquozh-CNrdquogt

ltheadgt

ltmeta http-equiv=rdquoContent-Typerdquo content=rdquotexthtml charset=UTF-7rdquo gt

lttitlegtnodejs index html filelttitlegt

ltheadgt

ltbodygt

34 4 Nodejs基礎

Nodejs中文電子書

lth1gtnodejs index html filelth1gt

ltbodygt

lthtmlgt

另一個為 nodejs程式

var fs = require(rdquofsrdquo)

filename = rdquostaticindexhtmlrdquo

encode = rdquoutf8rdquo

fsreadFile(filename encode function(err file)

consolelog(file)

)

一開始直接載入 le system模組 載入名稱為 fs讀取檔案主要使

用的方法為 readFile裡面以三個參數路徑 ( le path) 編碼方式 (encoding)

回應函式 (callback)路徑必須要設定為靜態 html所在位置才能指定到正確的檔案靜態檔案的編碼方式也必須正確這邊使用靜態檔案的編

碼為 utf8如果編碼設定錯誤nodejs讀取出來檔案結果會使用 byte raw格式輸出如果錯誤編碼格式會導致輸出資料為 byte raw

回應函式中裡面會使用兩個變數error為錯誤資訊如果讀取的檔案不存在或者發生錯誤error數值會是 true如果成功讀取資料 error將會是 falsecontent則是檔案內容資料讀取後將會把資料全數丟到 content這個變數當中

最後程式的輸出結果畫面如下

43 nodejs檔案讀取 35

Nodejs中文電子書

44 nodejs http靜態檔案輸出

前面已經了解如何讀取本地端檔案接下來將配合 http伺服器路由讓每個路由都能夠輸出相對應的靜態 html檔案首先新增加幾個靜態 html檔案

ltDOCTYPE htmlgt

lthtml xmlns=rdquohttpwwww3org1999xhtmlrdquo xmllang=rdquozh-CNrdquo lang=rdquozh-CNrdquogt

ltheadgt

ltmeta http-equiv=rdquoContent-Typerdquo content=rdquotexthtml charset=UTF-7rdquo gt

lttitlegtnodejs index html filelttitlegt

ltheadgt

ltbodygt

lth1gtnodejs index html filelth1gt

ltbodygt

lthtmlgt

ltDOCTYPE htmlgt

lthtml xmlns=rdquohttpwwww3org1999xhtmlrdquo xmllang=rdquozh-CNrdquo lang=rdquozh-CNrdquogt

ltheadgt

ltmeta http-equiv=rdquoContent-Typerdquo content=rdquotexthtml charset=UTF-7rdquo gt

lttitlegtnodejs test html filelttitlegt

ltheadgt

ltbodygt

lth1gtnodejs test html filelth1gt

ltbodygt

lthtmlgt

ltDOCTYPE htmlgt

lthtml xmlns=rdquohttpwwww3org1999xhtmlrdquo xmllang=rdquozh-CNrdquo lang=rdquozh-CNrdquogt

ltheadgt

ltmeta http-equiv=rdquoContent-Typerdquo content=rdquotexthtml charset=UTF-7rdquo gt

lttitlegtnodejs static html filelttitlegt

ltheadgt

ltbodygt

lth1gtnodejs static html filelth1gt

ltbodygt

lthtmlgt

36 4 Nodejs基礎

Nodejs中文電子書

準備一個包含基本路由功能的 http伺服器

var server

ip = rdquo127001rdquo

port = 1337

http = require(rsquohttprsquo)

url = require(rsquourlrsquo)

server = httpcreateServer(function (req res)

var path = urlparse(requrl)

)

serverlisten(port ip)

consolelog(rdquoServer running at httprdquo + ip + rdquordquo + port)

加入 le system 模組使用 readFile 的功能將這一段程式放置於

createServer的回應函式中

fsreadFile(filePath encode function(err file)

)

readFile的回應函式裡面加入頁面輸出讓瀏覽器可以正確讀到檔案在這邊我們設定讀取的檔案為 html 靜態檔案所以 Content-type 設定為texthtml讀取到檔案的內容將會正確輸出成 html靜態檔案

fsreadFile(filePath encode function(err file)

reswriteHead(200 rsquoContent-Typersquo rsquotexthtmlrsquo)

reswrite(file)

resend()

)

到這邊為止基本的程式內容都已經完成剩下一些細節的調整首先

路徑上必須做調整目前的靜態檔案全部都放置於 static資料夾底下設定一個變數來記住資料夾位置

接著將瀏覽器發出要求路徑與資料夾組合讀取正確 html靜態檔案使用者有可能會輸入錯誤路徑所以在讀取檔案的時候要加入錯誤處理

同時回應 404伺服器無法正確回應的 http header格式

加入這些細節的修改一個基本的 http 靜態 html輸出伺服器就完成了完整程式碼如下

44 nodejs http靜態檔案輸出 37

Nodejs中文電子書

var server

ip = rdquo127001rdquo

port = 1337

http = require(rsquohttprsquo)

fs = require(rdquofsrdquo)

folderPath = rdquostaticrdquo

url = require(rsquourlrsquo)

path

filePath

encode = rdquoutf8rdquo

server = httpcreateServer(function (req res)

path = urlparse(requrl)

filePath = folderPath + pathpathname

fsreadFile(filePath encode function(err file)

if (err)

reswriteHead(404 rsquoContent-Typersquo rsquotextplainrsquo)

resend()

return

reswriteHead(200 rsquoContent-Typersquo rsquotextapplicationrsquo)

reswrite(file)

resend()

)

)

serverlisten(port ip)

consolelog(rdquoServer running at httprdquo + ip + rdquordquo + port)

45 nodejs http GET資料擷取

http伺服器中除了路由之外另一個最常使用的方法就是擷取 GET資料本單元將會介紹如何透過基本 http伺服器擷取瀏覽器傳來的要求擷取 GET資料

在 http協定中GET參數都是藉由 URL從瀏覽器發出要求送至伺服器

38 4 Nodejs基礎

Nodejs中文電子書

端基本的傳送網址格式可能如下

http127001testsend=1amptest=2

上面這段網址裡面的 GET參數就是 send而這個變數的數值就為 1如果想要在 http伺服器取得 GET資料需要在瀏覽器給予的要求 (request)做處理

首先需要載入 query string這個模組這個模組主要是用來將字串資料

過濾後轉換成javascript物件

qs = require(rsquoquerystringrsquo)

接著在第一階段利用 url模組過濾瀏覽器發出的 URL資料後將回應的物件裡面的 query這個變數是一個字串值資料過濾後如下

send=1amptest=2

透過 query string使用 parse這個方法將資料轉換成 javascript物件就表示 GET的資料已經被伺服器端正式擷取下來

path = urlparse(requrl)

parameter = qsparse(pathquery)

整個 nodejs http GET參數完整擷取程式碼如下

var server

ip = rdquo127001rdquo

port = 1337

http = require(rsquohttprsquo)

qs = require(rsquoquerystringrsquo)

url = require(rsquourlrsquo)

server = httpcreateServer(function (req res)

var path = urlparse(requrl)

parameter = qsparse(pathquery)

consoledir(parameter)

reswriteHead(200 rsquoContent-Typersquo rsquotextplainrsquo)

reswrite(rsquoBrowser test GET parameternrsquo)

resend()

)

45 nodejs http GET資料擷取 39

Nodejs中文電子書

serverlisten(port ip)

consolelog(rdquoServer running at httprdquo + ip + rdquordquo + port)

程式運作之後由瀏覽器輸入要求網址之後nodejs伺服器端回應資料為

Server running at http1270011337

send rsquo1rsquo test rsquo1rsquo

46 本章結語

前面所解說的部份一大部分主要是處理 http伺服器基本問題雖然在某些部分有牽扯到 http 伺服器基本運作原理主要還是希望可以藉由這些基本範例練習 nodejs練習回應函式與語法串接的特點習慣編寫javascript風格程式當然直接這樣開發 nodejs是非常辛苦的接下來在模組實戰開發的部份將會介紹特定的模組一步一步帶領各位從無到有進行

nodejs應用程式開發

40 4 Nodejs基礎

5

NPM套件管理工具

npm全名為 Node Package Manager是 Nodejs的套件(package)管理工具類似 Perl的 ppm或 PHP的 PEAR等安裝 npm後使用npm install

module name指令即可安裝新套件維護管理套件的工作會更加輕鬆

npm可以讓Nodejs的開發者直接利用擴充線上的套件庫(packagesregistry)加速軟體專案的開發npm提供很友善的搜尋功能可以快速找到安裝需要的套件當這些套件發行新版本時npm也可以協助開發者自動更新這些套件

npm不僅可用於安裝新的套件它也支援搜尋列出已安裝模組及更新的功能

51 安裝 NPM

Nodejs在 063版本開始內建 npm讀者安裝的版本若是此版本或更新的版本就可以略過以下安裝說明

若要檢查 npm是否正確安裝可以使用以下的指令

npm -v

41

Nodejs中文電子書

執行結果說明

若 npm正確安裝執行 npm -v將會看到類似 110-2的版本訊息

若讀者安裝的 Nodejs版本比較舊或是有興趣嘗試自己動手安裝 npm工具則可以參考以下的說明

安裝於Windows系統

Nodejs for Windows 於 062 版開始內建 npm使用 nodejsorg 官方提供的安裝程式不需要進一步的設定就可以立即使用 npm指令對於Windows的開發者來說大幅降低環境設定的問題與門檻

除了使用 Nodejs內建的 npm讀者也可以從 npm官方提供的以下網址

httpnpmjsorgdist

這是由 npm提供的 Fancy Windows Install版本請下載壓縮檔(例如npm-110-3zip)並將壓縮檔內容解壓縮至 Nodejs的安裝路徑(例如CProgram Filesnodejs)

解壓縮後在 Nodejs的安裝路徑下應該有以下的檔案及資料夾

bull npmcmd(檔案)

bull node modules(資料夾)

安裝於 Linux系統

Ubuntu Linux的使用者可以加入 NPM Unoffcial PPA這個 repository即可使用 apt-get完成 npm安裝

42 5 NPM套件管理工具

Nodejs中文電子書

Ubuntu Linux使用 apt-get安裝 npm

sudo apt-get install python-software-properties

sudo add-apt-repository ppagias-kay-leenpm

sudo apt-get update

sudo apt-get install npm

npm官方提供的安裝程式 installsh可以適用於大多數的 Linux系統使用這個安裝程式請先確認

1 系統已安裝 curl工具(請使用 curl --version查看版本訊息)

2 已安裝 Nodejs並且 PATH正確設置

3 Nodejs的版本必須大於 04x

以下為 npm提供的安裝指令

curl httpnpmjsorginstallsh | sh

安裝成功會看到如下訊息

installsh安裝成功的訊息

npm10105 homeUSERNAMElocalnodelibnode_modulesnpm

It worked

安裝於Mac OS X

建議採用與 Nodejs相同的方式進行 npm的安裝例如使用MacPorts安裝 Nodejs就同樣使用MacPorts安裝 npm這樣對日後的維護才會更方便容易

使用 MacPorts安裝 npm是本書比較建議的方式它可以讓 npm的安裝移除及更新工作自動化將會幫助開發者節省寶貴時間

51 安裝 NPM 43

Nodejs中文電子書

安裝MacPorts的提示

在MacPorts網站可以取得 OS X系統版本對應的安裝程式(例如 106或 107)httpwwwmacportsorg安裝過程會詢問系統管理者密碼使用預設的選項完成安裝即可安

裝MacPorts之後在終端機執行 port -v將會看到MacPorts的版本訊息

安裝 npm之前先更新MacPorts的套件清單以確保安裝的 npm是最新版本

sudo port -d selfupdate

接著安裝 npm

sudo port install npm

若讀者的 Nodejs並非使用MacPorts安裝則不建議使用MacPorts安裝npm因為 MacPorts會自動檢查並安裝相依套件而 npm相依 nodejs所以MacPorts也會一併將 nodejs套件安裝造成先前讀者使用其它方式安裝的 nodejs被覆蓋

讀者可以先使用 MacPorts安裝 curl(sudo port install curl)

再參考 Linux的 installsh安裝方式即可使用 npm官方提供的安裝程式

NPM安裝後測試

npm是指令列工具(command-line tool)使用時請先打開系統的文字終端機工具

測試 npm安裝與設定是否正確請輸入指令如下

npm -v

或是

npm --version

如果 npm已經正確安裝設定就會顯示版本訊息

44 5 NPM套件管理工具

Nodejs中文電子書

執行結果(範例)

110-2

52 使用 NPM安裝套件

npm目前擁有超過 6000種套件(packages)可以在 npm registry使用關鍵字搜尋套件

httpsearchnpmjsorg

舉例來說在關鍵字欄位輸入「coffee-script」下方的清單就會自動列出包含 coffee-script關鍵字的套件

接著我們回到終端機模式的操作npm的指令工具本身就可以完成套

件搜尋的任務

例如以下的指令同樣可以找出 coffee-script相關套件

npm search coffee-script

以下是搜尋結果的參考畫面

52 使用 NPM安裝套件 45

Nodejs中文電子書

找到需要的套件後(例如 express)即可使用以下指令安裝

npm install coffee-script

值得注意的一點是使用 npm install會將指定的套件安裝在工

作目錄(Working Directory)的 node modules資料夾下

以Windows為例如果執行 npm install的目錄位於

Cproject1

那麼 npm將會自動建立一個 node modules的子目錄(如果不存在)

Cproject1node modules

並且將下載的套件放置於這個子目錄例如

Cproject1node modulescoffee-script

這個設計讓專案可以個別管理相依的套件並且可以在專案佈署或發

行時將這些套件(位於 node modules)一併打包方便其它專案的使用者不必再重新下載套件

這個 npm install的預設安裝模式為 local(本地)只會變更當前專案的資料夾不會影響系統

另一種安裝模式稱為 global(全域)這種模式會將套件安裝到系統資

料夾也就是 npm安裝路徑的 node modules資料夾例如

CProgram Filesnodejsnode modules

46 5 NPM套件管理工具

Nodejs中文電子書

是否要使用全域安裝可以依照套件是否提供新指令來判斷舉例來說express 套件提供 express 這個指令而 coffee-script 則提供 coffee

指令

在 local 安 裝 模 式 中這 些 指 令 的 程 式 檔 案會 被 安 裝 到node modules的 bin這個隱藏資料夾下除非將bin的路徑加入 PATH環境變數否則要執行這些指令將會相當不便

為了方便指令的執行我們可以在 npm install 加上 -g 或 --

global參數啟用 global安裝模式例如

npm install -g coffee-script

npm install -g express

使用 global安裝模式需要注意執行權限的問題若權限不足可能會出現類似以下的錯誤訊息

npm ERR Error EACCES permission denied rsquorsquo

npm ERR

npm ERR Please try running this command again as rootAdministrator

要獲得足夠得執行權限請參考以下說明

bull Windows 7 或 2008 以上在「命令提示字元」的捷徑按右鍵選擇「以系統管理員身分執行」執行 npm指令時就會具有 Administrator身分

bull Mac OS X或 Linux系統可以使用 sudo指令例如

sudo npm install -g express

bull Linux系統可以使用 root權限登入或是以「sudo su -」切換成 root身分(使用 root權限操作系統相當危險因此並不建議使用這種方式)

若加上 -g參數使用 npm install -g coffee-script完成安裝

後就可以在終端機執行 coffee指令例如

coffee -v

52 使用 NPM安裝套件 47

Nodejs中文電子書

執行結果(範例)

CoffeeScript version 120

53 套件的更新及維護

除了前一節說明的 search 及 install 用法npm 還提供其他許多指令(commands)

使用 npm help可以查詢可用的指令

npm help

執行結果(部分)

where ltcommandgt is one of

adduser apihelp author bin bugs c cache completion

config deprecate docs edit explore faq find get

help help-search home i info init install la link

list ll ln login ls outdated owner pack prefix

prune publish r rb rebuild remove restart rm root

run-script s se search set show star start stop

submodule tag test un uninstall unlink unpublish

unstar up update version view whoami

使用 npm help command可以查詢指令的詳細用法例如

npm help list

接下來本節要介紹開發過程常用的 npm指令

使用 list可以列出已安裝套件

npm list

48 5 NPM套件管理工具

Nodejs中文電子書

執行結果(範例)

-- coffee-script120

- express256

- connect185

| -- formidable108

-- mime124

-- mkdirp007

-- qs041

檢視某個套件的詳細資訊例如

npm show express

升級所有套件(如果該套件已發佈更新版本)

npm update

升級指定的套件

npm update express

移除指定的套件

npm uninstall express

54 使用 packagejson

對於正式的 Nodejs專案可以建立一個命名為 packagejson的設

定檔(純文字格式)檔案內容參考範例如下

54 使用 packagejson 49

Nodejs中文電子書

packagejson(範例)

rdquonamerdquo rdquoapplication-namerdquo

rdquoversionrdquo rdquo001rdquo

rdquoprivaterdquo true

rdquodependenciesrdquo

rdquoexpressrdquo rdquo255rdquo

rdquocoffee-scriptrdquo rdquolatestrdquo

rdquomongooserdquo rdquogt= 253rdquo

其中 name與 version依照專案的需求設置

需要注意的是 dependencies的設定它用於指定專案相依的套件名

稱及版本

bull rdquoexpressrdquo rdquo255rdquo

代表此專案相依版本 255的 express套件

bull rdquocoffee-scriptrdquo rdquolatestrdquo

使用最新版的 coffee-script套件(每次更新都會檢查新版)

bull rdquomongooserdquo rdquogt= 253rdquo

使用版本大於 253的 mongoose套件

假設某個套件的新版可能造成專案無法正常運作就必須指定套件的

版本避免專案的程式碼來不及更新以相容新版套件通常在開發初期的

專案需要盡可能維持新套件的相容性(以取得套件的更新或修正)可以

用「gt=」設定最低相容的版本或是使用「latest」設定永遠保持最新

套件

50 5 NPM套件管理工具

6

Express介紹

在前面的 nodejs基礎當中介紹許多許多開設 http的使用方法及介紹以及許多基本的 nodejs基本應用

接下來要介紹一個套件稱為 express [Express](httpexpressjscom)這個套件主要幫忙解決許多 nodejs http server所需要的基本服務讓開發 httpservice變得更為容易不需要像之前需要透過層層模組(module)才有辦法開始編寫自己的程式

這個套件是由 TJ Holowaychuk 製作而成的套件裡面包含基本的路由處理 (route)http資料處理(GETPOSTPUT)另外還與樣板套件(jshtml template engine)搭配同時也可以處理許多複雜化的問題

61 Express安裝

安裝方式十分簡單只要透過之前介紹的 NPM就可以使用簡單的指令安裝指令如下

這邊建議需要將此套件安裝成為全域模組方便日後使用

51

Nodejs中文電子書

62 Express基本操作

express的使用也十分簡單先來建立一個基本的 hello world

var app = require(rsquoexpressrsquo)createServer()

port = 1337

applisten(port)

appget(rsquorsquo function(req res)

ressend(rsquohello worldrsquo)

)

consolelog(rsquostart express servernrsquo)

可以從上面的程式碼發現基本操作與 nodejs http的建立方式沒有太大差異主要差在當我們設定路由時可以直接透過 appget方式設定回應與接受方式

63 Express路由處理

Express對於 http服務上有許多包裝讓開發者使用及設定上更為方便例如有幾個路由設定那我們就統一藉由 appget來處理

Create http server

appget(rsquorsquo function(req res)

ressend(rsquohello worldrsquo)

)

appget(rsquotestrsquo function(req res)

ressend(rsquotest renderrsquo)

)

appget(rsquouserrsquo function(req res)

ressend(rsquouser pagersquo)

)

52 6 Express介紹

Nodejs中文電子書

如上面的程式碼所表示appget可以帶入兩個參數第一個是路徑名稱設定第二個為回應函式 (call back function)回應函式裡面就如同之前的 createServer方法裡面包含 requestresponse兩個物件可供使用使用者就可以透過瀏覽器輸入不同的 url切換到不同的頁面顯示不同的結果

路由設定上也有基本的配對方式讓使用者從瀏覽器輸入的網址可以

是一個變數只要符合型態就可以有對應的頁面產出例如

Create http server

appget(rsquouseridrsquo function(req res)

ressend(rsquouser rsquo + reqparamsid)

)

appget(rsquonumberrsquo function(req res)

ressend(rsquonumber rsquo + reqparamsnumber)

)

裡 面 使 用 到number 從 網 址 輸 入 之 後 就 可 以 直 接 使 用reqparamsnumber 取得所輸入的資料變成 url 參數使用當然前面也是可以加上路徑的設定userid在瀏覽器上路徑必須符合userxxx透

過 reqparamsid就可以取到 xxx這個字串值

另外express參數處理也提供了路由參數配對處理也可以透過正規表示法作為參數設定

var app = require(rsquoexpressrsquo)createServer()

port = 1337

applisten(port)

appget(^ip((d23)((d23))((d23))((d23))) function(req res)

ressend(reqparams)

)

上面程式碼可以發現後面路由設定的型態是正規表示法裡面設定

格式為ip之後必須要加上 ip型態才會符合資料格式同時取得 ip資料已經由正規表示法將資料做分群因此可以取得 ip的四個數字

此程式執行之後可以透過瀏覽器測試輸入網址為 localhost3000ip25525510010可以從頁面獲得資料

63 Express路由處理 53

Nodejs中文電子書

此章節全部範例程式碼如下

overview

author Caesar Chi

blog clonnblogspotcom

version 20120226

create server

var app = require(rsquoexpressrsquo)createServer()

port = 1337

applisten(port)

normal style

appget(rsquorsquo function(req res)

ressend(rsquohello worldrsquo)

)

appget(rsquotestrsquo function(req res)

ressend(rsquotest renderrsquo)

)

parameter style

appget(rsquouseridrsquo function(req res)

ressend(rsquouser rsquo + reqparamsid)

)

appget(rsquonumberrsquo function(req res)

ressend(rsquonumber rsquo + reqparamsnumber)

)

REGX style

appget(^ip((d23)((d23))((d23))) function(req res)

ressend(reqparams)

)

appget(rsquorsquo function(req res)

ressend(rsquoPage not foundrsquo 404)

54 6 Express介紹

Nodejs中文電子書

)

consolelog(rsquostart express servernrsquo)

64 Express middleware

Express 裡面有一個十分好用的應用概念稱為 middleware可以透過middleware做出複雜的效果同時上面也有介紹 next方法參數傳遞就是靠 middleware的概念來傳遞參數讓開發者可以明確的控制程式邏輯

create http server

appuse(expressbodyParser())

appuse(expressmethodOverride())

appuse(expresssession())

上面都是一種 middleware的使用方式透過 appuse方式裡面載入函式執行方法回應函式會包含三個基本參數responserequestnext其中next表示下一個 middleware執行函式同時會自動將預設三個參數繼續帶往下個函式執行底下有個實驗

上面的片段程式執行後開啟瀏覽器連結上 localhost1337會發現伺服器回應結果順序如下

first middle ware

second middle ware

execute middle ware

end middleware function

從上面的結果可以得知剛才設定的 middleware都生效了在 appuse設定的 middleware是所有 url皆會執行方法如果有指定特定方法就可以使用 appget的 middleware設定在 appget函式的第二個參數就可以帶入函式或者是匿名函式只要函式裡面最後會接受 request response next這三個參數同時也有正確指定 next函式的執行時機最後都會執行到最後一個方法當然開發者也可以評估程式邏輯要執行到哪一個階段讓邏輯

可以更為分明

64 Express middleware 55

Nodejs中文電子書

65 Express路由應用

在實際開發上可能會遇到需要使用參數等方式混和變數一起使用

express裡面提供了一個很棒的處理方法 appall這個方式可以先採用基本路由配對再將設定為每個不同的處理方式開發者可以透過這個方式簡

化自己的程式邏輯

overview

author

version 20120226

create server

var app = require(rsquoexpressrsquo)createServer()

port = 1337

users = [

name rsquoClonnrsquo

name rsquoChirsquo

]

applisten(port)

appall(rsquouseridoprsquo function(req res next)

requser = users[reqparamsid]

if (requser)

next()

else

next(new Error(rsquocannot find user rsquo + reqparamsid))

)

appget(rsquouseridrsquo function(req res)

ressend(rsquoviewing rsquo + requsername)

)

appget(rsquouserideditrsquo function(req res)

ressend(rsquoediting rsquo + requsername)

)

56 6 Express介紹

Nodejs中文電子書

appget(rsquouseriddeletersquo function(req res)

ressend(rsquodeleting rsquo + requsername)

)

appget(rsquorsquo function(req res)

ressend(rsquoPage not foundrsquo 404)

)

consolelog(rsquostart express servernrsquo)

內部宣告一組預設的使用者分別給予名稱設定藉由 appall這個方法可以先將路由雛形建立再接下來設定 appget的路徑格式只要符合格式就會分配進入對應的方法中像上面的程式當中如果使用者輸入路徑為user0除了執行 appall程式之後執行 next方法就會對應到路徑設定為userid的這個方法當中如果使用者輸入路徑為user0edit就會執行到useridedit的對應方法

66 Express GET應用範例

我們準備一個使用 GET方法傳送資料的表單

ltDOCTYPE htmlgt

lthtmlgt

ltheadgt

ltmeta http-equiv=rdquoContent-Typerdquo content=rdquotexthtml charset=utf8rdquogt

lttitlegtNodejs 菜鳥筆記 (1)lttitlegt

ltlink rel=rdquostylesheetrdquo href=rdquocssstylecssrdquo type=rdquotextcssrdquo media=rdquoallrdquo gt

ltheadgt

ltbodygt

lth1gtNodejs 菜鳥筆記-註冊lth1gt

ltform id=rdquosignuprdquo method=rdquoGETrdquo action=rdquohttplocalhost3000Signuprdquogt

ltlabelgt 使用者名稱ltlabelgtltinput type=rdquotextrdquo id=rdquousernamerdquo name=rdquousernamerdquo gtltbrgt

ltlabelgt 電子郵件ltlabelgtltinput type=rdquotextrdquo id=rdquoemailrdquo name=rdquoemailrdquo gtltbrgt

ltinput type=rdquosubmitrdquo value=rdquo 註冊我的帳號rdquo gtltbrgt

ltformgt

ltbodygt

lthtmlgt

66 Express GET應用範例 57

Nodejs中文電子書

這個表單沒有什麼特別的地方我們只需要看第 9 行form 使用的method是 GET然後 action是rdquohttplocalhost3000Signupldquo等一下我們要來撰寫Signup這個 URL Path的處理程式

處理 Signup行為

我們知道所謂的 GET方法會透過 URL來把表單的值給帶過去以上面的表單來說到時候 URL會以這樣的形式傳遞

httplocalhost3000Signupusername=xxxampemail=xxx

所以要能處理這樣的資料必須有以下功能

bull 解析 URL

bull 辨別動作是 Signup

bull 解析出 username和 email

一旦能取得 username和 email的值程式就能加以應用了

處理 Signup的程式碼雛形

load module

var url = require(rsquourlrsquo)

urlData = urlparse(requrltrue)

action = urlDatapathname

reswriteHead(200 rdquoContent-Typerdquordquotexthtml charset=utf-8rdquo)

if (action === rdquoSignuprdquo)

user = urlDataquery

resend(rdquolth1gtrdquo + userusername + rdquo 歡迎您的加入 lth1gtltpgt 我們已經將會員啟用信寄至rdquo + useremail + rdquoltpgtrdquo)

首先需要加載 url module它是用來協助我們解析 URL的模組接著使用 urlparse方法第一個傳入 url字串變數也就是 requrl另外第二個參數的用意是設為 ture則引進 querystring模組來協助處理預設是 false它影響到的是 urlDataquery設為 true會傳回物件不然就只是一般的字串urlparse會將字串內容整理成一個物件我們把它指定給 urlData

action變數作為記錄 pathname這是我們稍後要來判斷目前網頁的動作是什麼接著先將 html表頭資訊 (Header)準備好再來判斷路徑邏輯如

58 6 Express介紹

Nodejs中文電子書

果是 Signup這個動作就把 urlDataquery裡的資料指定給 user然後輸出userusername和 useremail把使用者從表單註冊的資料顯示於頁面中

最後進行程式測試啟動 nodejs主程式之後開啟瀏覽器就會看到表單填寫完畢按下送出就可以看到結果了

完整 nodejs程式碼如下

var http = require(rsquohttprsquo)

url = require(rsquourlrsquo)

fs = require(rdquofsrdquo)

server

server = httpcreateServer(function (reqres)

var urlData

encode = rdquoutf8rdquo

filePath = rdquoviewexpress_get_example_formhtmlrdquo

action

urlData = urlparse(requrltrue)

action = urlDatapathname

reswriteHead(200 rdquoContent-Typerdquordquotexthtml charset=utf-8rdquo)

if (action === rdquoSignuprdquo)

user = urlDataquery

resend(rdquolth1gtrdquo + userusername + rdquo 歡迎您的加入 lth1gtltpgt 我們已經將會員啟用信寄至rdquo + useremail + rdquoltpgtrdquo)

else

fsreadFile(filePath encode function(err file)

reswrite(file)

resend()

)

)

serverlisten(3000)

consolelog(rsquoServer 跑起來了現在時間是rsquo + new Date())

66 Express GET應用範例 59

Nodejs中文電子書

67 Express POST應用範例

一開始準備基本的 html表單傳送內容以 POST方式form的 action屬性設定為 POST其餘 html內容與前一個範例應用相同

ltDOCTYPE htmlgt

lthtmlgt

ltheadgt

ltmeta http-equiv=rdquoContent-Typerdquo content=rdquotexthtml charset=utf8rdquogt

lttitlegtNodejs 菜鳥筆記 (1)lttitlegt

ltlink rel=rdquostylesheetrdquo href=rdquocssstylecssrdquo type=rdquotextcssrdquo media=rdquoallrdquo gt

ltheadgt

ltbodygt

lth1gtNodejs 菜鳥筆記-註冊lth1gt

ltform id=rdquosignuprdquo method=rdquoPOSTrdquo action=rdquohttplocalhost3000Signuprdquogt

ltlabelgt 使用者名稱ltlabelgtltinput type=rdquotextrdquo id=rdquousernamerdquo name=rdquousernamerdquo gtltbrgt

ltlabelgt 電子郵件ltlabelgtltinput type=rdquotextrdquo id=rdquoemailrdquo name=rdquoemailrdquo gtltbrgt

ltinput type=rdquosubmitrdquo value=rdquo 註冊我的帳號rdquo gtltbrgt

ltformgt

ltbodygt

lthtmlgt

nodejs的程式處理邏輯與前面 GET範例類似部分程式碼如下

qs = require(rsquoquerystringrsquo)

if (action === rdquoSignuprdquo)

formData = rsquorsquo

reqon(rdquodatardquo function (data)

formData += data

)

reqon(rdquoendrdquo function ()

user = qsparse(formData)

resend(rdquolth1gtrdquo + userusername + rdquo 歡迎您的加入 lth1gtltpgt 我們已經將會員啟用信寄至rdquo + useremail + rdquoltpgtrdquo)

)

60 6 Express介紹

Nodejs中文電子書

主要加入了rsquoquerystringrsquo這個moduel方便我們等一下解析由表單 POST回來的資料另外加入一個 formData的變數用來搜集待等一下表單回傳的資料前面的 GET範例我們只從 req拿出 url的資料這次要在利用req身上的事件處理

JavaScript 在訂閱事件時使用 addEventListener而 nodejs 使用的則是on這邊加上了監聽 data的事件會在瀏覽器傳送資料到Web Server時被執行參數是它所接收到的資料型態是字串

接著再增加 end的事件當瀏覽器的請求事件結束時它就會動作

由於瀏覽器使用 POST在上傳資料時會將資料一塊塊地上傳因為我們在監聽 data事件時透過 formData變數將它累加起來lt不過由於我們

上傳的資料很少一次就結束不過如果日後需要傳的是資料比較大的檔

案這個累加動作就很重要

當資料傳完就進到 end 事件中會用到 qsparse 來解析 formDataformData的內容是字串內容是

username=wordsmithampemail=wordsmith40somewhere

而 qsparse可以幫我們把這個 querystring轉成物件的格式也就是

username=wordsmithampemail=wordsmith40somewhere

一旦轉成物件並指定給 user之後其他的事情就和 GET方法時操作的一樣寫 response的表頭將內容回傳並將 userusername和 useremail代入到內容中

修改完成後接著執行 nodejs程式啟動 web server開啟瀏覽器進入表單測試看看POST的方式能否順利運作

完整程式碼如下

var http = require(rsquohttprsquo)

url = require(rsquourlrsquo)

fs = require(rdquofsrdquo)

qs = require(rsquoquerystringrsquo)

server

server = httpcreateServer(function (reqres)

var urlData

67 Express POST應用範例 61

Nodejs中文電子書

encode = rdquoutf8rdquo

filePath = rdquoviewexpress_post_example_formhtmlrdquo

formData

action

urlData = urlparse(requrltrue)

action = urlDatapathname

reswriteHead(200 rdquoContent-Typerdquordquotexthtml charset=utf-8rdquo)

if (action === rdquoSignuprdquo)

formData = rsquorsquo

reqon(rdquodatardquo function (data)

formData += data

)

reqon(rdquoendrdquo function ()

user = qsparse(formData)

resend(rdquolth1gtrdquo + userusername + rdquo 歡迎您的加入 lth1gtltpgt 我們已經將會員啟用信寄至rdquo + useremail + rdquoltpgtrdquo)

)

else

fsreadFile(filePath encode function(err file)

reswrite(file)

resend()

)

)

serverlisten(3000)

consolelog(rsquoServer 跑起來了現在時間是rsquo + new Date())

62 6 Express介紹

Nodejs中文電子書

68 Express AJAX應用範例

在 Nodejs要使用 Ajax傳送資料並且與之互動在接受資料的部份沒有太大的差別client端不是用 GET就是用 POST來傳資料重點在處理完後用 JSON格式回傳當然 Ajax不見得只傳 JSON格式有時是回傳一段 HTML碼不過後者對伺服器來說基本上就和前兩篇沒有差別了所以我們還是以回傳 JSON做為這一回的主題

這一回其實大多數的工作都會落在前端 Ajax上面前端要負責發送與接收資料並在接收資料後撤掉原先發送資料的表單並將取得的資料

改成 HTML格式之後放上頁面

首先先準備 HTML靜態頁面資料

ltDOCTYPE htmlgt

lthtmlgt

ltheadgt

lttitlegtNodejs 菜鳥筆記 (3)lttitlegt

ltlink rel=rdquostylesheetrdquo href=rdquocssstylecssrdquo type=rdquotextcssrdquo media=rdquoallrdquo gt

ltscript src=rdquohttpsajaxgoogleapiscomajaxlibsjquery171jqueryminjsrdquogtltscriptgt

ltheadgt

ltbodygt

lth1gtNodejs 菜鳥筆記-註冊 (用 Ajax)lth1gt

ltform id=rdquosignuprdquo action=rdquoSignuprdquo method=rdquoPOSTrdquogt

ltlabelgt 使用者名稱ltlabelgtltinput type=rdquotextrdquo id=rdquousernamerdquo name=rdquousernamerdquo gtltbrgt

ltlabelgt 電子郵件ltlabelgtltinput type=rdquotextrdquo id=rdquoemailrdquo name=rdquoemailrdquo gtltbrgt

ltinput type=rdquosubmitrdquo value=rdquo 註冊我的帳號rdquo gtltbrgt

ltformgt

ltdiv id=rdquoresultrdquogt

ltdivgt

ltscript type=rdquotextjavascriptrdquogt

$(function()$(rsquosignup input[type=rdquosubmitrdquo]rsquo)click(function(e)

var user =

userusername = $(rdquousernamerdquo)val()useremail = $(rdquoemailrdquo)val()

$post(rdquoSignuprdquouserfunction(data)greet(data)

)

68 Express AJAX應用範例 63

Nodejs中文電子書

epreventDefault()

)

var greet = function(msg)

var resultNode = $(rsquoresultrsquo)greeting = $(rsquolth2gtHirsquo+msgusername+rsquo 你的會員 id 是rsquo+ msgid +rsquo 我們會將會員啟動信寄至rsquo+msgemail+rsquolth2gtrsquo)

$(rsquosignuprsquo)hide()resultNodehtml(rdquordquo)

resultNodeappend(greeting)

)

ltscriptgt

ltbodygt

lthtmlgt

HTML頁面上準備了一個表單用來傳送註冊資料接著直接引用了Google CDN來載入 jQuery用來幫我們處理 Ajax的工作這次要傳送和接收的工作很大的變動都在 HTML頁面上的 JavaScript當中我們要做的事有 (相關 jQuery處理這邊不多做贅述指提起主要功能解說)

bull 用 jQUery取得 submit按鈕綁定它的 click動作

bull 取得表單 username和 email的值存放在 user這個物件中

bull 用 jQuery的 $post方法將 user的資料傳到 Server

bull 一旦成功取得資料後透過 greet這個 function組成回報給 user的訊息

bull 清空原本給使用者填資料的表單

bull 將 Server回傳的 usernameemail和 id這 3個資料組成回應的訊息

bull 將訊息放到原本表單的位置

經過以上的處理後一個 Ajax的表單的基本功能已經完成

接著進行 nodejs主要程式的編輯部分程式碼如下

var fs = require(rdquofsrdquo)

64 6 Express介紹

Nodejs中文電子書

qs = require(rsquoquerystringrsquo)

if (action === rdquoSignuprdquo)

formData = rsquorsquo

reqon(rdquodatardquo function (data)

formData += data

)

reqon(rdquoendrdquo function ()

var msg

user = qsparse(formData)

userid = rdquo123456rdquo

msg = JSONstringify(user)

reswriteHead(200 rdquoContent-Typerdquordquoapplicationjson charset=utf-8rdquordquoContent-Lengthrdquomsglength)

resend(msg)

)

這裡的程式和前面 POST範例基本上大同小異差別在

bull 幫 user的資料加上 id隨意存放一些文字進去讓 Server回傳的資料多於 Client端傳上來的不然會覺得 Server都沒做事

bull 增加了 msg 這個變數存放將 user 物件 JSON 文字化的結果JSONstringify這個轉換函式是 V8引擎所提供的如果你好奇的話

bull 大重點來了我們要告訴 Client端這次回傳的資料格式是 JSON所在 Content-type和 Content-Length要提供給 Client

Server很輕鬆就完成任務了最後進行程式測試啟動 nodejs主程式之後開啟瀏覽器就會看到表單填寫完畢按下送出就可以看到結果了

最後 nodejs本篇範例程式碼如下

var http = require(rsquohttprsquo)

url = require(rsquourlrsquo)

fs = require(rdquofsrdquo)

qs = require(rsquoquerystringrsquo)

server

68 Express AJAX應用範例 65

Nodejs中文電子書

server = httpcreateServer(function (reqres)

var urlData

encode = rdquoutf8rdquo

filePath = rdquoviewexpress_ajax_example_formhtmlrdquo

formData

action

urlData = urlparse(requrltrue)

action = urlDatapathname

if (action === rdquoSignuprdquo)

formData = rsquorsquo

reqon(rdquodatardquo function (data)

formData += data

)

reqon(rdquoendrdquo function ()

var msg

user = qsparse(formData)

userid = rdquo123456rdquo

msg = JSONstringify(user)

reswriteHead(200 rdquoContent-TyperdquordquoapplicationjsonrdquordquoContent-Lengthrdquomsglength)

resend(msg)

)

else

fsreadFile(filePath encode function(err file)

reswriteHead(200 rdquoContent-Typerdquordquotexthtml charset=utf-8rdquo)

reswrite(file)

resend()

)

)

serverlisten(3000)

66 6 Express介紹

Nodejs中文電子書

consolelog(rsquoServer 跑起來了現在時間是rsquo + new Date())

69 原始資料提供

bull [NodeJS初學者筆記 (1)-用 GET傳送資料] (httpithelpithomecomtwquestion10087402)

bull [NodeJS初學者筆記 (2)-用 POST傳送資料] (httpithelpithomecomtwquestion10087489)

bull [NodeJS 初學者筆記 (3)-用 Ajax 傳送資料] (httpithelpithomecomtwquestion10087627)

69 原始資料提供 67

Nodejs中文電子書

68 6 Express介紹

7

CoffeeScript

bull Spine

bull Hem

bull Spineapp

Letrsquos Have a Cup of CoffeeScript

bull es5-shim ECMAScript 5 compatibility shims for legacy JavaScript engines

ESnext

node-iform - A middleware for node-validator httpsgithubcomguileennode-iform

69

Nodejs中文電子書

70 7 CoffeeScript

8

製作一個 Hubot的 Plurk Adapter

81 應用事項提醒

此應用範例以CoffeeScript編寫主要程式架構採用Github釋放的Hubot專案以下有幾個主要注意事項請讀者注意

bull Hubot 一開始的架構除了內建的兩個 Adapter 之外其餘都要以Nodejs的Module方式才能運作

bull Hubot的 binhubot裡面寫著 npm install所以不管你怎麼改原始碼也不能改變第一點的狀況

其實上述都在討論同一件事情NodeJS的模組而且模組不能設定相對路徑之類的來安裝一定要包裝成 npm相符和格式才有辦法正確執行

82 建立 Adapter

首先需要準備兩個檔案

bull packagejson

bull plurkcoffee

71

Nodejs中文電子書

有這兩個就足以變成 NodeJS的模組了在開始 Coding之前先來設定一下 packagejson相依性

rdquonamerdquo rdquohubot-plurkrdquo

rdquoversionrdquo rdquo011rdquo

rdquomainrdquo rdquoplurkrdquo

rdquodependenciesrdquo

rdquohubotrdquo rdquogt=205rdquo

rdquooauthrdquo rdquordquo

rdquocronrdquordquordquo

這邊會使用到NodeJS的 cron模組(Module)而登入 Plurk需要OAuth標準授權才行所以也需要加載 OAuth模組

注意Hubot在讀取非內建模組時會自動在前面加上 hubot-的前置

83 建立 Robot跟 API

本篇應用基本上程式碼大部分參考 Hubot - Twitter Adapter來製作主要差異只有在使用 OAuth這個模組Twitter有 Streaming可用而 Plurk則得用 Comet方式來達到即時讀取

plurkcoffee程式碼

Robot = require(rdquohubotrdquo)robot()

Adapter = require(rdquohubtordquo)adapter()

EventEmitter = require(rdquoeventsrdquo)EventEmitter

oauth = require(rdquooauthrdquo)

cronJob = require(rdquocronrdquo)CronJob

class Plurk exntends Adapter

class PlurkStreaming exnteds EventEmitter

72 8 製作一個 Hubot的 Plurk Adapter

Nodejs中文電子書

先弄個基本架構主要 Class皆參考 Twitter Adapter命名方式接著再將 Plurk這個 Class增加幾個Method基本上只要有 run send reply就夠了而 run用來做初始化的部分

class Plurk entends Adapter

send (plurk id strings⋯) -gt

reply (plurk id strings⋯) -gt

run -gt

看起來有點東西接著來處理主要的 Plurk API結合部分

class PlurkStreaming extends EventEmitter

consuctor (options) -gt

plurk (callback) -gt

觀察河道

getChannel -gt

取得 Comet 網址

reply (plurk id message) -gt

回噗

acceptFriends -gt

接受好友

get (path callback) -gt

GET 請求

post (path body callback)-gt

POST 請求(其實是裝飾)

request (method path body callback)-gt

主要的 OAuth 請求

comet (server callback)-gt

噗浪的 Comet 傳回是 JavaScript Callback 要另外處理後才會變成 JSON

然後把注意力集中到 constructor上先把建構子弄好

constructor (options) -gt

super()

if optionskey and optionssecret and optionstoken and optionstoken secret

key = optionskey

secret = optionssecret

token = optionstoken

token secret = optionstoken secret

83 建立 Robot跟 API 73

Nodejs中文電子書

建立 OAuth 連接

consumer = new oauthOAuth(

rdquohttpwwwplurkcomOAuthrequest tokenrdquo

rdquohttpwwwplurkcomOAuthaccess tokenrdquo

key

secret

rdquo10rdquo

rdquohttpwwwplurkcomOAuthauthorizerdquo

rdquoHMAC-SHA1rdquo

)

domain = rdquowwwplurkcomrdquo

初始化取得 Comet 網址

do getChannel

else

throw new Error(rdquo 參數不足需要 Key Secret Token Token Secretrdquo)

接著來處理 request這個 method

request (method path body callback) -gt

記錄一下這次的 Request

consolelog(rdquohttpdomainpathrdquo)

Callback 這邊先不丟進去要用另一種方式處理

request = consumerget(rdquohttpdomainpathrdquo token token secret null)

requeston rdquoresponserdquo (res) -gt

reson rdquodatardquo (chunk) -gt

parseResponse(chunk+rsquorsquo callback)

reson rdquoendrdquo (data) -gt

consolelog rdquoEnd Request pathrdquo

reson rdquoerrorrdquo (data) -gt

consolelog rdquoError rdquo + data

requestend()

處理資料

parseResponse = (data callback) -gt

if datalength gt 0

用 TryCatch 避免處理 JSON 出錯導致整個中斷

try

callback null JSONparse(data)

catch err

74 8 製作一個 Hubot的 Plurk Adapter

Nodejs中文電子書

consolelog(rdquoError Parse JSONrdquo + data err)

繼續執行

callback null data ||

大致上就是這樣上面程式的架構已經將整個 Hubot Plurk Adapter完成因為在測試時竟然因為噗浪 Lag而沒讀到完整的 Comet資料然後造成程式異常為了避免這個問題發生因此需要加上為了完美呈現需要再

加上 Comet的處理所以要使用到 EventEmitter的功能

comet (server callback) -gt

在 Callback 裡面會找不到自身所以設定區域變數

self =

記錄一下這次的 Request

consolelog(rdquo[Comet] serverrdquo)

Callback 這邊先不丟進去要用另一種方式處理

request = consumerget(rdquohttpdomainpathrdquo token token secret null)

requeston rdquoresponserdquo (res) -gt

reson rdquodatardquo (chunk) -gt

parseResponse(chunk+rsquorsquo callback)

reson rdquoendrdquo (data) -gt

consolelog rdquoEnd Request pathrdquo

請求結束發出事件通知可以進行下一次請求

selfemit rdquonextPlurkrdquo

reson rdquoerrorrdquo (data) -gt

consolelog rdquoError rdquo + data

requestend()

處理資料

parseResponse = (data callback) -gt

if datalength gt 0

用 trycatch 避免失敗中斷

try

去掉 JavaScript 的 Callback

data = datamatch(CometChannelscriptCallback((+))s)

jsonData = rdquordquo

if data

83 建立 Robot跟 API 75

Nodejs中文電子書

jsonData = JSONparse(data[1])

else

如果沒有任何 Match 嘗試直接 parse

jsonData = JSONparse(data)

catch err

consolelog(rdquo[Comet] Errorrdquo data err)

用 TryCatch 避免處理 JSON 出錯導致整個中斷

try

只傳入 json 的 data 部分

callback null jsonDatadata

catch err

consolelog(rdquo[Comet]Error Parse JSONrdquo + data err)

繼續執行

callback null data ||

後面的 get跟 post就簡單多了

get (path callback) -gt

request(rdquoGETrdquo path null callback)

post (path body callback) -gt

request(rdquoPOSTrdquo path body callback)

接著處理取的 Comet網址的 getChannel

getChannel -gt

self =

get rdquoAPPRealtimegetUserChannelrdquo (error data) -gt

if error

檢查是否有 comet server

if datacomet server

selfchannel = datacomet server

如果沒有 Channel Ready 就嘗試連接會失敗

selfemit(rsquochannel readyrsquo)

那麼先來處理 Plurk Adaper好處理的部份

send (plruk id strings⋯)-gt

跟 Reply 一樣直接交給 reply 做

reply plurk id strings⋯

76 8 製作一個 Hubot的 Plurk Adapter

Nodejs中文電子書

reply (plurk id strings⋯) -gt

stringsforEach (message) =gt

botreply(plruk id message)

接著把 run處理好就可以上線運作摟

run -gt

self =

options =

key processenvHUBOT PLURK KEY

secret processenvHUBOT PLURK SECRET

token processenvHUBOT PLURK TOKEN

token secret processenvHUBOT PLURK TOKEN SECRET

創建剛剛的 API

bot = new PlurkStreaming(options)

依照 Twitter 的 new RobotTextMessage 會沒有反應所以參考 hubot-minecraft 的方式

r = robotconstructor

處理噗浪河道訊息

doPlurk = (data)-gt

檢查是否為回噗

if dataresponse

datacontent raw = dataresponsecontent raw

datauser id = dataresponseuser id

確定有噗浪 ID 跟訊息

if dataplurk id and datacontent raw

selfreceive new rTextMessage(dataplurk id datacontent raw)

取得 Comet Server 完成開始第一次 Comet 連接

boton rdquochannel readyrdquo () -gt

botplurk selfdoPlurk

上一次 Comet 完成繼續 Polling

boton rdquonextPlurkrdquo ()-gt

botplurk selfdoPlurk

定時接受好友邀請

do botacceptFriends

bot = bot

83 建立 Robot跟 API 77

Nodejs中文電子書

終於完成 AdapterHubot專案裡面的 scripts資料夾內是互動部分不需要像 Adapter如此大費周章處理只新增檔案並且設計好對白之後就會回噗了我開發用的機器人在此大家可以去跟他玩玩[httpplurkcomelct9620 bot](httpplurkcomelct9620 bot)

84 原始資料提供

bull [製 作 一 個 Hubot 的 噗 浪 Adapter](http revo-skillfrosttw blog20120318create-a-hubot-plurk-adapter)

78 8 製作一個 Hubot的 Plurk Adapter

9

Nodejs好用工具介紹

91 logio

Real-time log monitoring in your browser

httplogioorg

nodejs (gt=0412 lt=0611)

npm config set unsafe-perm true

npm install -g --prefix=usrlocal logio

logio server start

httplocalhost8998

79

Nodejs中文電子書

80 9 Nodejs好用工具介紹

10

精選文章

101 Express

bull 使用 NodeJS + Express從 GETPOST Request取值 [邀稿中]

bull link2 [邀稿中]

bull link3 [邀稿中]

81

Nodejs中文電子書

82 10 精選文章

11

參考資源

111 Nodejs書籍

bull Programming Nodejs April 2012 (est) Travis Swicegood Pragmatic Bookshelf

bull e Little Book on CoffeeScript January 2012 Alex MacCaw OrsquoReilly Media

bull Node for Front-End Developers January 2012 Garann Means OrsquoReilly Media

bull Building Hypermedia APIs with HTML5 and Node November 2011 MikeAmundsen OrsquoReilly Media

bull Node Web Development August 2011 David Herron PacktPub

bull CoffeeScript Accelerated JavaScript Development July 2011 Trevor BurnhamPragmatic Bookshelf

bull Getting Started with GEO CouchDB and Nodejs July 2011 Mick omp-son OrsquoReilly Media

bull What Is Node July 2011 Brett McLaughlin OrsquoReilly Media

bull Node Up and Running May 2011 Tom Hughes-Croucher Mike WilsonOrsquoReilly Media

83

Nodejs中文電子書

bull Hands-on Nodejs e Nodejs introduction and API reference May 2011 Pe-dro Teixeira LeanPub

bull e Node Beginner Book Manuel Kiessling

bull Mastering Nodejs visionmedia

112 Nodejs影音教學

bull e Node Sessions e Best of OSCON 2011 August 2011 OrsquoReilly Media(Video)

bull Tom Hughes-Croucher on Node March 2011 OrsquoReilly Media (Video)

113 Nodejs教學網站

bull Node Tuts

114 Nodejs課程

bull Introduction To Nodejs Van Nguyen CodeLesson

84 11 參考資源

Page 6: Nodejs Wiki

iv

關於本書

這是一本關於 Nodejs技術的開放源碼電子書我們使用 GitHub維護電子書內容並交由 ContPub(Continuous Publishing)系統自動線上發佈本書提供 PDFEPUBMOBI及 HTML等格式您除了可以在網站檢視本書所有內容也可以將電子書下載至閱讀器保存

本書的線上閱讀網址與 GitHub資料同步更新

httpbooknodejstw

如果您想要取得本書的其他格式可以從 ContPub的電子書專頁下載最新版本

httpcontpuborgreadnodejs-wiki-book

本書適合 Nodejs初學者至進階開發者也歡迎您在學習時一起參與本書內容撰寫

授權

Nodejs台灣社群協作電子書採用創用 CC姓名標示 -非商業性授權您不必為本書付費

Nodejs Wiki Book book is licensed under the Attribution-NonCommercial 30Unported license You should not have paid for this book

您可以複製散佈及修改本書內容但請勿將本書用於商業用途

您可以在以下網址取得授權條款全文

1

Nodejs中文電子書

httpcreativecommonsorglicensesby-nc30legalcode

作者

本書由 Nodejs Taiwan社群成員協作以下名單依照字母排序

bull Caesar Chi (clonn)

bull Fillano Feng ( llano)

bull Kyle Lin (lyhcode)

Nodejs Taiwan是一個自由開放的技術學習社群我們歡迎您加入一起學習研究及分享

下載電子書

線上閱讀本書

httpbooknodejstw

PDF格式適合一般電腦及 7吋以上平板電腦閱讀

httpcontpuborgdownloadnodejs-wiki-bookpdf

EPUB格式適合 iPadiPhone行動裝置閱讀

httpcontpuborgdownloadnodejs-wiki-bookepub

MOBI格式適合 Kindle電子書閱讀器

httpcontpuborgdownloadnodejs-wiki-bookmobi

原始碼

本書最新的原始碼(中文版)網址如下

httpgithubcomnodejs-twnodejs-wiki-book

2 關於本書

Nodejs中文電子書

01 精選文章收錄流程

精選文章的用意是鼓勵作者在自已的網誌發表 Nodejs 教學再由Nodejs Taiwan社群挑選系列文章列入電子書的精選文集

1 將文章標題及連結貼到「精選文章」分類

2 社群工作小組以 E-Mail通知作者文章列入精選並邀請將內文授權給Nodejs Taiwan電子書分享

3 作者同意後由工作小組負責整理圖文發佈至電子書

4 以 E-Mail寄出感謝函通知原作者文章已收錄依作者意願調整文章內容

5 於 Nodejs Taiwan首頁及粉絲專頁推薦作者的文章

01 精選文章收錄流程 3

Nodejs中文電子書

4 關於本書

前言

Nodejs是 JavaScript程式語言的開發框架由 04x至目前 v060版本核心上已經有許多變更當然目的只有一個就是讓開發變得更簡單速

度能夠更快這是所有人奮鬥的目標而 nodeJS 華文維基平台主力由nodeJStw一群喜好 javascript開發者共同主筆內文以中文為主希望能夠降低開發者學習門檻藉由我們拋磚引玉讓更多華人開發者共同學習

討論

CoffeeScript好好玩

5

Nodejs中文電子書

6 前言

1

Nodejs簡介

Nodejs是一個高效能易擴充的網站應用程式開發框架 (Web Applica-tion Framework)它誕生的原因是為了讓開發者能夠更容易開發高延展性的網路服務不需要經過太多複雜的調校效能調整及程式修改就能

滿足網路服務在不同發展階段對效能的要求

Ryan Dahl是NodeJS的催生者目前任職於 Joyent (主機託管服務公司)他開發 NodeJS的目的就是希望能解決 Apache在連線數量過高時緩衝區 (buffer)和系統資源會很快被耗盡的問題希望能建立一個新的開發框架以解決這個問題因此嘗試使用效能十分優秀的 V8 JavaScript Engine讓網站開發人員熟悉的 JavaScript程式語言也能應用於後端服務程式的開發並且具有出色的執行效能

JavaScript是功能強大的物件導向程式語言但是在 JavaScript的官方規格中主要是定義網頁 (以瀏覽器為基礎) 應用程式需要的應用程式介面(API)對 JavaScript程式的應用範圍有所侷限為使 JavaScript能夠在更多用途發展CommonJS規範一組標準函式庫 (standard library)使 JavaScript的應用範圍能夠和 RubyPython及 Java等語言同樣豐富撰寫 NodeJS的JavaScript程式碼符合 CommonJS規範可以使用 CommonJS API為基礎開發程式並且在不同的 CommonJS兼容 (compliant) JavaScript執行環境中程式碼具有可攜性

7

Nodejs中文電子書

瀏覽器的 JavaScript與實現 CommonJS規範的 NodeJS有何不同呢瀏覽器的 JavaScript 提供 XMLHttpRequest 讓程式可以和網頁伺服器建立資料傳輸連線但這通常只能適用於網站開發的需求因為我們只能用

XMLHttpRequest與網頁伺服器通訊卻無法利用它建立其他類型如 Telnet FTP NTP的伺服器通訊如果我們想開發網路服務程式例如 SMTP電子郵件伺服器就必須使用 Sockets建立 TCP (某些服務則用 UDP)監聽及連線其他程式語言如 PHPJavaPythonPerl及 Ruby等在標準開發環境中皆有提供 Sockets API而瀏覽器的 JavaScript基於安全及貼近網站設計需求的考量下並未將 Sockets列入標準函式庫之中CommonJS的規範填補這種基礎函式庫功能的空缺遵循 CommonJS規範的 NodeJS可以直接使用 Sockets API建立各種網路服務程式

JavaScript語言本身支援 Lambda的特性因此一個匿名函式 (anonymousfunction)可以被儲存成一個變數並當作參數傳遞給另一個函式

var proc1 = function(op x) return op(x)

var op1 = function(x) return x+1 var op2 = function(x) return xx

proc1(op1 3) result is 4 proc1(op2 5) result is 25

另一個 JavaScript開發者必須掌握的語言特性稱為 Closure

NodeJS符合 CommonJS的規範使得 Callback方式易於實現也能夠讓更多同好基於 JavaScript開發符合 NodeJS的外掛模組 (Module)

回想以前要寫一個能夠同時容納上百人的上線的網路服務需要花費

多大的苦工可能 10人多就需要經過一次程式調整而 NodeJS就是為了解決這個困境NodeJS因此誕生它是一種利用 V8 Javascript編譯器所開發的產品利用 V8編譯器的高效能與 Javascript的程式開發特性所產生的網路程式

開發人員所編寫出來的 Javascript腳本程式怎麼可能會比其他語言寫出來的網路程式還要快上許多呢以前的網路程式原理是將使用者每次的

連線 (connection)都開啟一個執行緒 (thread)當連線爆增的時候將會快速耗盡系統效能並且容易產生阻塞 (block)的發生

8 1 Nodejs簡介

Nodejs中文電子書

NodeJS對於資源的調校有所不同當程式接收到一筆連線 (connection)會通知作業系統透過 epoll kqueue devpoll或 select將連線保留並且放入heap中配置先讓連線進入休眠 (Sleep)狀態當系統通知時才會觸發連線的 callback這種處理連線方式只會佔用掉記憶體並不會使用到 CPU資源另外因為採用 Javascript語言的特性每個 request都會有一個 callback如此可以避免發生 Block的狀況發生

基於 Callback特性目前NodeJS大多應用於 Comet(long pulling) RequestServer或者是高連線數量的網路服務上目前也有許多公司將 NodeJS設為內部核心網路服務之一在 NodeJS也提供了外掛管理 (Node packagemanagement)讓愛好 NodeJS輕易開發更多有趣的服務外掛並且提供到 npm讓全世界使用者快速安裝使用

本書最後執行測試版本為 nodejs v068相關 API文件可查詢 lsquohttpnodejsorg lthttpnodejsorggtlsquo本書所有範例均可於 linux windows上執行如遇到任何問題歡迎至 lsquohttpnodejstw lthttpnodejstwgtlsquo詢問對於nodejs相關問題

9

Nodejs中文電子書

10 1 Nodejs簡介

2

JavaScript與 NodeJS

其實使用 JavaScript 在網頁端與伺服器端的差距並不大但是為了使NodeJS可以發揮他最強大的能力有一些知識還是必要的所以還是針對這些主題介紹一下其中 Event LoopScope以及 Callback其實是比較需要了解的基本知識cpscurrying ow control是更進階的技巧與應用

21 Event Loop

可能很多人在寫 Javascript 時並不知道他是怎麼被執行的這個時候可以參考一下 jQuery作者 John Resig一篇好文章介紹事件及 timer怎麼在瀏覽器中執行How JavaScript Timers Work通常在網頁中所有的Javascript執行完畢後(這部份全部都在 global scope跑除非執行函數)接下來就是如 John Resig解釋的這樣所有的事件處理函數以及 timer執行的函數會排在一個 queue結構中利用一個無窮迴圈不斷從 queue中取出函數來執行這個就是 event loop

(除了 John Resig的那篇文章Nicholas C Zakas的 ldquoProfessional Javascriptfor Web Developer 2nd editionrdquo 有一個試閱本httpyuiblogcomassetspdfzakas-projs-2ed-ch18pdf598頁剛好也有簡短的說明)

所以在 Javascript中雖然有非同步但是他並不是使用執行緒所有

11

Nodejs中文電子書

的事件或是非同步執行的函數都是在同一個執行緒中利用 event loop的方式在執行至於一些比較慢的動作例如 IO網頁 render re ow等實際動作會在其他執行緒跑等到有結果時才利用事件來觸發處理函數來處理

這樣的模型有幾個好處沒有執行緒的額外成本所以反應速度很快不會

有任何程式同時用到同一個變數不必考慮 lock也不會產生 dead lock所以程式撰寫很簡單但是也有一些潛在問題任一個函數執行時間較長都

會讓其他函數更慢執行(因為一個跑完才會跑另一個)在多核心硬體普遍

的現在無法用單一的應用程式 instance發揮所有的硬體能力用 NodeJS撰寫伺服器程式碰到的也是一樣的狀況要讓系統發揮 event loop的效能就要盡量利用事件的方式來組織程式架構另外對於一些有可能較為耗

時的操作可以考慮使用 processnextTick函數來讓他以非同步的方式執行避免在同一個函數中執行太久擋住所有函數的執行

如果想要測試 event loop怎樣在「瀏覽器」中運行可以在函數中呼叫alert()這樣會讓所有 Javascript的執行停下來尤其會干擾所有使用 timer的函數執行有一個簡單的例子這是一個會依照設定的時間間隔嚴格執

行動作的動畫如果時間過了就會跳過要執行的動作點按圖片以後人

物會快速旋轉但是在旋轉執行完畢前按下「delay」按鈕讓 alert訊息等久一點接下來的動畫就完全不會出現了

22 Scope與 Closure

要快速理解 JavaScript的 Scope(變數作用範圍)原理只要記住他是Lexical Scope就差不多了簡單地說變數作用範圍是依照程式定義時(或者叫做程式文本)的上下文決定而不是執行時的上下文決定

為了維護程式執行時所依賴的變數即使執行時程式運行在原本的

scope之外他的變數作用範圍仍然維持不變這時程式依賴的自由變數(定義時不是 local的而是在上一層 scope定義的變數)一樣可以使用就好像被關閉起來所以叫做 Closure用程式看比較好懂

function outter(arg1)

arg1 及 free_variable1 對 inner 函數來說都是自由變數

var free_variable1 = 3

return function inner(arg2)

12 2 JavaScript與 NodeJS

Nodejs中文電子書

var local_variable1 =2arg2 及 local_variable1 對 inner 函數來說都是本地變數

return arg1 + arg2 + free_variable + local_variable1

var a = outter(1)變數 a就是 outter函數執行後返回的 inner函數 var b =a(4)執行 inner函數執行時上下文已經在 outter函數之外但是仍然能正常執行而且可以使用定義在 outter函數裡面的 arg1及 free variable1變數 consolelog(b)結果 10

在 Javascript中scope最主要的單位是函數(另外有 global及 eval)所以有可能製造出 closure的狀況通常在形式上都是有巢狀的函數定義而且內側的函數使用到定義在外側函數裡面的變數

Closure有可能會造成記憶體洩漏主要是因為被參考的變數無法被垃圾收集機制處理造成佔用的資源無法釋放所以使用上必須考慮清楚

不要造成意外的記憶體洩漏(在上面的例子中如果 a一直未執行使用到的記憶體就不會被釋放)

跟透過函數的參數把變數傳給函數比較起來Javascript Engine會比較難對 Closure進行最佳化如果有效能上的考量這一點也需要注意

23 Callback

要介紹 Callback之前要先提到 JavaScript的特色

JavaScript是一種函數式語言(functional language)所有 Javascript語言內的函數都是高階函數 (higher order function這是數學名詞計算機用語好像是 rst class function意指函數使用沒有任何限制與其他物件一樣)也就是說函數可以作為函數的參數傳給函數也可以當作函數的返回值這個特性讓 Javascript的函數使用上非常有彈性而且功能強大

callback在形式上其實就是把函數傳給函數然後在適當的時機呼叫傳入的函數Javascript使用的事件系統通常就是使用這種形式NodeJS中有一個物件叫做 EventEmitter這是 NodeJS事件處理的核心物件所有會使用事件處理的函數都會「繼承」這個物件(這裡說的繼承實

作上應該像是 mixin)他的使用很簡單可以使用物件on(事件名稱callback

23 Callback 13

Nodejs中文電子書

函數) 或是物件addListener(事件名稱callback 函數) 把你想要處理事件的函數傳入在物件中可以使用物件emit(事件名稱 參數) 呼叫傳入的callback函數這是 Observer Pattern的簡單實作而且跟在網頁中使用 DOM的 addEventListener使用上很類似也很容易上手不過 NodeJS是大量使用非同步方式執行的應用所以程式邏輯幾乎都是寫在 callback函數中當邏輯比較複雜時大量的 callback會讓程式看起來很複雜也比較難單元測試舉例來說

var p client = new Db(lsquointegration tests 20rsquo new Server(ldquo127001rdquo 27017) lsquopkrsquoCustomPKFactory) p clientopen(function(err p client)

p clientdropDatabase(function(err done)

p clientcreateCollection(lsquotest custom keyrsquo function(err collection)

collectioninsert(lsquoarsquo1 function(err docs)

collection nd(lsquo idrsquonew ObjectID(ldquoaaaaaaaaaaaardquo) function(err cursor)

cursortoArray(function(err items) testassertEquals(1 itemslength) p clientclose()

)

)

)

)

)

)

這是在網路上看到的一段操作 mongodb的程式碼為了循序操作所以必須在一個 callback裡面呼叫下一個動作要使用的函數這個函數裡面還是會使用 callback最後就形成一個非常深的巢狀

這樣的程式碼會比較難進行單元測試有一個簡單的解決方式是

盡量不要使用匿名函數來當作 callback或是 event handler透過這樣的方式

14 2 JavaScript與 NodeJS

Nodejs中文電子書

就可以對各個 handler做單元測試了例如

var http = require(lsquohttprsquo) var tools =

cookieParser function(request response) if(requestheaders[rsquoCookiersquo]) do parsing

var server = httpcreateServer(function(request response)

thisemit(lsquoinitrsquo request response)

) serveron(lsquoinitrsquo toolscookieParser) serverlisten(8080 lsquo127001rsquo)

更進一步可以把 tools改成外部 module例如叫做 toolsjs

moduleexports = cookieParser function(request response) if(requestheaders[rsquoCookiersquo]) do parsing

然後把程式改成

var http = require(lsquohttprsquo)

var server = httpcreateServer(function(request response) thisemit(lsquoinitrsquo re-quest response)

) serveron(lsquoinitrsquo require(lsquo toolsrsquo)cookieParser) serverlisten(8080lsquo127001rsquo)

這樣就可以單元測試 cookieParser了例如使用 nodeunit時可以這樣寫

var testCase = require(lsquonodeunitrsquo)testCase moduleexports = testCase(

ldquosetUprdquo function(cb) thisrequest = headers Cookielsquoname1val1 name2val2rsquo thisresponse = thisresult= name1rsquoval1rsquoname2rsquoval2rsquo

cb()

ldquotearDownrdquo function(cb)

cb()

23 Callback 15

Nodejs中文電子書

ldquonormal caserdquo function(test)

testexpect(1) var obj = require(lsquotoolsrsquo)cookieParser(thisrequest thisresponse)testdeepEqual(obj thisresult) testdone()

)

善於利用模組可以讓程式更好維護與測試

24 CPS(Continuation-Passing Style)

cps 是 callback 使用上的特例形式上就是在函數最後呼叫 callback這樣就好像把函數執行後把結果交給 callback 繼續運行所以稱作continuation-passing style利用 cps可以在非同步執行的情況下透過傳給 callback的這個 cps callback來獲知 callback執行完畢或是取得執行結果例如

lthtmlgt ltbodygt ltdiv id=rdquopanelrdquo style=rdquovisibilityhiddenrdquogtlt divgtlt bodygt lt htmlgt ltscriptgt var request = new XMLHttpRequest() re-questopen(lsquoGETrsquo lsquotest749txt timestamp=rsquo+new Date()getTime() true) re-questaddEventListener(lsquoreadystatechangersquo function(next)

return function() if(thisreadyState===4ampampthisstatus===200) next(thisresponseText)lt== 傳入的 cps callback 在動作完成時執行並取得結果進一步處理

(function(str)lt==這個匿名函數就是 cps callback

documentgetElementById(lsquopanelrsquo)innerHTML=str docu-mentgetElementById(lsquopanelrsquo)stylevisibility = lsquovisiblersquo

) false) requestsend() ltscriptgt

進一步的應用也可以參考 2-6流程控制

16 2 JavaScript與 NodeJS

Nodejs中文電子書

25 函數返回函數與 Currying

前面的 cps範例裡面使用了函數返回函數這是為了把 cps callback傳遞給 onreadystatechange事件處理函數的方法(因為這個事件處理函數並沒有設計好會傳送接收這樣的參數)實際會執行的事件處理函數其實是內層返回的那個函數之外包覆的這個函數主要是為了利用 Closure把 next傳給內層的事件處理函數這個方法更常使用的地方是為了解決一些

scope問題例如

ltscriptgt var accu=0count=10 for(var i=0 iltcount i++)

setTimeout(

function() countndash accu+=i if(countlt=0)

consolelog(accu)

50)

ltscriptgt

最後得出的結果會是 100而不是想像中的 45這是因為等到 setTime-out指定的函數執行時變數 i已經變成 10而離開迴圈了要解決這個問題就需要透過 Closure來保存變數 i

ltscriptgt var accu=0count=10 for(var i=0 iltcount i++)

setTimeout(

function(i) return function() countndash

accu+=i if(countlt=0)

consolelog(accu)

(i)

50)

25 函數返回函數與 Currying 17

Nodejs中文電子書

淺藍色底色的部份是跟上面例子不一樣的地方 ltscriptgt

函數返回函數的另外一個用途是可以暫緩函數執行例如

function add(m n) return m+n

var a = add(20 10) consolelog(a)

add這個函數必須同時輸入兩個參數才有辦法執行如果我希望這個函數可以先給它一個參數等一些處理過後再給一個參數然後得到結

果就必須用函數返回函數的方式做修改

function add(m)

return function(n) return m+n

var wait another arg = add(20)先給一個參數 var a = function(arr)

var ret=0 for(var i=0iltarrlengthi++) ret+=arr[i] return ret

([1234])計算一下另一個參數 var b = wait another arg(a)然後再繼續執行 consolelog(b)

像這樣利用函數返回函數使得原本接受多個參數的函數可以一次

接受一個參數直到參數接收完成才執行得到結果的方式有一個學名就

叫做Currying

綜合以上許多奇技淫巧就可以透過用函數來處理函數的方式調整

程式流程接下來看看

26 流程控制

(以 sync方式使用 async函數避開巢狀 callback循序呼叫 async callback等奇技淫巧)

建議參考

bull httphowtonodeorgcontrol- ow

bull httphowtonodeorgcontrol- ow-part-ii

18 2 JavaScript與 NodeJS

Nodejs中文電子書

bull httphowtonodeorgcontrol- ow-part-iii

bull httpblogmixunet20110202essential-node-js-patterns-and-snippets

這幾篇都是非常經典的 NodeJSJavascript流程控制好文章(阿mixu是在介紹一些 pattern時提到這方面的主題)不過我還是用幾個簡單的程式介紹一下做法跟概念

並發與等待

下面的程式參考了 mixu文章中的做法

var wait = function(callbacks done) consolelog(lsquowait startrsquo) var counter = call-backslength var results = [] var next = function(result) 接收函數執行結果並判斷是否結束執行 resultspush(result) if(ndashcounter == 0) done(results)如果結束執行就把所有執行結果傳給指定的 callback處理 for(var i = 0 i lt callbackslength i++) 依次呼叫所有要執行的函數 callbacks[i](next) consolelog(lsquowait endrsquo)

wait( [ function(next) setTimeout(function() consolelog(lsquodone arsquo) var result =500 next(result) 500) function(next) setTimeout(function() con-solelog(lsquodone brsquo) var result = 1000 next(result) 1000) function(next)setTimeout(function() consolelog(lsquodone crsquo) var result = 1500 next(1500) 1500) ] function(results) var ret = 0 i=0 for( iltresultslength i++) ret+= results[i] consolelog(lsquodone all result lsquo+ret)

)

執行結果wait start wait end done a done b done c done all result 3000

可以看出來其實 wait並不是真的等到所有函數執行完才結束執行而是在所有傳給他的函數執行完畢後(不論同步非同步)才執行處理結

果的函數(也就是 done())

不過這樣的寫法還不夠實用因為沒辦法實際讓函數可以等待執行

完畢又能當作事件處理函數來實際使用上面參考到的 Tim Caswell的文

26 流程控制 19

Nodejs中文電子書

章裡面有一種解法不過還需要額外包裝(在他的例子中)NodeJS核心的 fs物件把一些函數(例如 readFile)用 Currying處理類似像這樣

var fs = require(lsquofsrsquo) var readFile = function(path)

return function(callback errback)

fsreadFile(path function(err data)

if(err) errback()

else callback(data)

)

其他部份可以參考 Tim Caswell的文章他的 Doparallel跟上面的 wait差不多意思這裡只提示一下他沒說到的地方

另外一種做法是去修飾一下 callback當他作為事件處理函數執行後再用 cps的方式取得結果

ltscriptgt function Wait(fns done)

var count = 0 var results = [] thisgetCallback = function(index)

count++ return (function(waitback)

return function() var i=0args=[]for(iltargumentslengthi++)

argspush(arguments[i])

argspush(waitback) fns[index]apply(thisargs)

)(function(result) resultspush(result) if(ndashcount == 0)

20 2 JavaScript與 NodeJS

Nodejs中文電子書

done(results)

)

var a = new Wait(

[ function(waitback) consolelog(lsquodone arsquo) var result = 500 wait-back(result) function(waitback) consolelog(lsquodone brsquo) var result =1000 waitback(result) function(waitback) consolelog(lsquodone crsquo) varresult = 1500 waitback(result) ] function(results) var ret = 0 i=0for( iltresultslength i++) ret += results[i] consolelog(lsquodone allresult lsquo+ret)

) var callbacks = [agetCallback(0)agetCallback(1)agetCallback(0)agetCallback(2)]一次取出要使用的 callbacks避免結果提早送出 setTimeout(callbacks[0]500) setTimeout(callbacks[1] 1000) setTimeout(callbacks[2] 1500) setTime-out(callbacks[3] 2000) 當所有取出的 callbacks執行完畢就呼叫 done()來處理結果 ltscriptgt

執行結果

done a done b done a done c done all result 3500

上面只是一些小實驗更成熟的作品是 Tim Caswell 的 stephttpsgithubcomcreationixstep

如果希望真正使用同步的方式寫非同步則需要使用 Promisejs這一類的 library來轉換非同步函數不過他結構比較複雜 XD(見仁見智不過有些人認為 Promise有點過頭了)httpblogsmsdncombrbucktonarchive20110815promise-js-2-0-promise-framework-for-javascriptaspx

如果想不透過其他 Library做轉換又能直接用同步方式執行非同步函數大概就要使用一些需要額外 compile原始程式碼的方法了例如 BrunoJouhier的 streamlinejshttpsgithubcomSagestreamlinejs

26 流程控制 21

Nodejs中文電子書

循序執行

循序執行可以協助把非常深的巢狀 callback結構攤平例如用這樣的簡單模組來做(serialjs)

moduleexports = function(funs) var c = 0 if(isArrayOfFunctions(funs))

throw(lsquoArgument type was not matched Should be array of func-tionsrsquo)

return function()

var args = Arrayprototypeslicecall(arguments 0) if((cgt=funslength))

c++ return funs[c-1]apply(this args)

function isArrayOfFunctions(f ) if(typeof f == lsquoobjectrsquo) return false if(flength)return false if(fconcat) return false if(fsplice) return false var i = 0 for(iltflength i++)

if(typeof f[i] == lsquofunctionrsquo) return false

return true

簡單的測試範例(testSerialjs)使用 fs 模組確定某個 path 是檔案然後讀取印出檔案內容這樣會用到兩層的 callback所以測試中有使用serial的版本與 nested callbacks的版本做對照

var serial = require(lsquoserialrsquo) fs = require(lsquofsrsquo) path = lsquodclientjsrsquo cb = serial([ func-tion(err data)

if(err)

if(dataisFile) fsreadFile(path cb)

22 2 JavaScript與 NodeJS

Nodejs中文電子書

else consolelog(err)

function(err data)

if(err) consolelog(lsquo[ attened by searial]rsquo) con-solelog(datatoString(lsquoutf8rsquo))

else consolelog(err)

]) fsstat(path cb)

fsstat(path function(err data) 第一層 callback if(err)

if(dataisFile)

fsreadFile(path function(err data) 第二層 callback if(err)

consolelog(lsquo[nested callbacks]rsquo) con-solelog(datatoString(lsquoutf8rsquo))

else consolelog(err)

)

else consolelog(err)

)

關鍵在於這些 callback的執行是有順序性的所以利用 serial返回的一個函數 cb來取代這些 callback然後在 cb中控制每次會循序呼叫的函數

26 流程控制 23

Nodejs中文電子書

就可以把巢狀的 callback攤平成循序的 function陣列(就是傳給 serial函數的參數)

測試中的dclientjs 是一個簡單的 dnode 測試程式放在跟 testSerialjs同一個目錄

var dnode = require(lsquodnodersquo)

dnodeconnect(8000 lsquolocalhostrsquo function(remote)

remoterestart(function(str) consolelog(str) processexit()

)

)

執行測試程式後出現結果

[ attened by searial] var dnode = require(lsquodnodersquo)

dnodeconnect(8000 lsquolocalhostrsquo function(remote)

remoterestart(function(str) consolelog(str) processexit()

)

)

[nested callbacks] var dnode = require(lsquodnodersquo)

dnodeconnect(8000 lsquolocalhostrsquo function(remote)

remoterestart(function(str) consolelog(str) processexit()

)

)

對照起來看兩種寫法的結果其實是一樣的但是利用 serialjs巢狀的 callback結構就會消失

不過這樣也只限於順序單純的狀況如果函數執行的順序比較複雜

(不只是一直線)還是需要用功能更完整的流程控制模組比較好例如

httpsgithubcomcaolanasync

24 2 JavaScript與 NodeJS

3

Nodejs安裝與設定

本篇將講解如何在各個不同 OS建立 NodeJS環境目前 NodeJS 048版本環境架設方式需依賴 Linux指令才可編譯完成當然在不同作業系統中也已經有 NodeJS package可以直接使用指令快速架設以下各不同作業系統解說如何安裝 NodeJS

31 Ubuntu Linux

更新推薦使用 nvm

git clone gitgithubcomcreationixnvmgit ~nvm

echo rdquo ~nvmnvmshrdquo gtgt ~bashrc

nvm install v0614

nvm alias default v0614

以 上 可 參 考 http dreamerslabcom blog tw how-to-setup-a-node-js-development-environment-on-ubuntu-11-04

使用 APT套件管理工具是常見的方法以下是使用社群提供的 PPA安裝方式

sudo apt-get install python-software-properties

sudo add-apt-repository ppachris-leanodejs-devel

25

Nodejs中文電子書

sudo apt-get update

sudo apt-get install nodejs

32 Other Linux

Linux很適合作為 NodeJS的伺服器作業系統及開發環境安裝前請先確認以下套件已正確安裝

bull curl (wget)用來下載檔案的工具

bull git先進的版本控制工具

bull g++ GNU C++軟體編譯工具

bull make GNU軟體專案建置工具

安裝指令如下如設有權限問題請在指令前面加上 sudo

git clone httpsgithubcomjoyentnodegit

cd node

git checkout v067

configure

make

sudo make install

接著測試 nodeJS是否正常執行

node --version

出現版本訊息即表示安裝成功

33 Windows

nodeJS 在 v060 版本之後開始正式支援 windows native直接使用nodeexe 就可以執行程式支援性完全與 linux 相同更棒的部份就是不需經過編譯經過下載之後簡單設定完成立即開發 node程式

26 3 Nodejs安裝與設定

Nodejs中文電子書

下載 nodejs安裝檔案 lthttpnodejsorgdownloadgt

如此完成 windows native nodeexe安裝接著可以進入 command line執行測試在 command line輸入rdquonoderdquo會出現 nodejs版本訊息畫面表示安裝完成

33 Windows 27

Nodejs中文電子書

28 3 Nodejs安裝與設定

4

Nodejs基礎

前篇文章已經由介紹安裝至設定都有完整介紹nodeJS 內部除了javascript常用的函式 (function)物件 (object)之外也有許多不同的自訂物件nodeJS預設建立這些物件為核心物件是為了要讓開發流程更為這些資料在官方文件已經具有許多具體說明接下來將會介紹在開發 nodeJS程式時常見的物件特性與使用方法

41 nodejs http伺服器建立

在 lsquonodejs官方網站 lthttpnodejsorggtlsquo裡面有舉一個最簡單的 HTTP伺服器建立一開始初步就是建立一個伺服器平台讓 nodejs可以與瀏覽器互相行為每種語言一開始的程式建立都是以 Hello world開始最初也從 Hello world帶各位進入 nodejs的世界

輸入以下程式碼儲存檔案為 node basic http hello worldjs

var server

ip = rdquo127001rdquo

port = 1337

http = require(rsquohttprsquo)

server = httpcreateServer(function (req res)

29

Nodejs中文電子書

reswriteHead(200 rsquoContent-Typersquo rsquotextplainrsquo)

resend(rsquoHello Worldnrsquo)

)

serverlisten(port ip)

consolelog(rdquoServer running at httprdquo + ip + rdquordquo + port)

程式碼解講一開始需要有幾個基本的變數

bull ip 機器本身的 ip位置因為使用本地端因此設定為 127001

bull port 需要開通的阜號通常設定為 http port 80因範例不希望與基本 port相衝隨意設定為 1337

在 nodejs 的程式中有許多預設的模組可以使用因此需要使用require方法將模組引入在這邊我們需要使用 http這個模組因此將 http載入Http模組裡面內建有許多方法可以使用這邊採用 createServer創建一個基本的 http伺服器再將 http伺服器給予一個 server變數裡面的回呼函式 (call back function)可以載入 http伺服器的資料與回應方法 (requestresponse)在程式裡面就可以看到我們直接回應給瀏覽器端所需的 Header回應內容

reswriteHead(200 rsquoContent-Typersquo rsquotextplainrsquo)

resend(rsquoHello Worldnrsquo)

Http伺服器需要設定 port ip在最後需要設定 Http監聽需要使用到listen事件監聽所有 Http伺服器行為

httplisten(port ip)

所有事情都完成之後需要確認伺服器正確執行因此使用 console在javascript裡就有這個原生物件console所印出的資料都會顯示於 nodejs伺服器頁面這邊印出的資料並不會傳送到使用者頁面上之後許多除壞

(debug)都會用到 console物件

consolelog(rdquoServer running at httprdquo + ip + rdquordquo + port)

30 4 Nodejs基礎

Nodejs中文電子書

42 nodejs http路徑建立

前面已經介紹如何建立一個簡單的 http伺服器接下來本章節將會介紹如何處理伺服器路徑 (rout)問題在 http的協定下所有從瀏覽器發出的要求 (request)都需要經過處理路徑上的建立也是如此

路徑就是指伺服器 ip位置或者是網域名稱之後對於伺服器給予的要求修改剛才的 hello world檔案修改如下

server = httpcreateServer(function (req res)

consolelog(requrl)

reswriteHead(200 rsquoContent-Typersquo rsquotextplainrsquo)

resend(rsquohello worldnrsquo)

)

重新啟動 nodejs程式後在瀏覽器端測試一下路徑行為結果如下圖

當在瀏覽器輸入 http1270011337test 在伺服器端會收到兩個要求一個是我們輸入的test要求另外一個則是faviconicotest的路徑要求http伺服器本身需要經過程式設定才有辦法回應給瀏覽器端所需要的回應在伺服器中所有的路徑要求都是需要被解析才有辦法取得資料從

42 nodejs http路徑建立 31

Nodejs中文電子書

上面解說可以了解到在 nodejs當中所有的路徑都需要經過設定未經過設定的路由會讓瀏覽器無法取得任何資料導致錯誤頁面的發生底下將會解

說如何設定路由同時避免發生錯誤情形先前 nodejs程式需要增加一些修改才能讓使用者透過瀏覽器在不同路徑時有不同的結果根據剛才

的程式做如下的修改

var server

ip = rdquo127001rdquo

port = 1337

http = require(rsquohttprsquo)

url = require(rsquourlrsquo)

path

server = httpcreateServer(function (req res)

path = urlparse(requrl)

reswriteHead(200 rsquoContent-Typersquo rsquotextplainrsquo)

switch (pathpathname)

case rdquoindexrdquo

resend(rsquoI am indexnrsquo)

break

case rdquotestrdquo

resend(rsquothis is test pagenrsquo)

break

default

resend(rsquodefault pagenrsquo)

break

)

serverlisten(port ip)

consolelog(rdquoServer running at httprdquo + ip + rdquordquo + port)

程式做了片段的修改首先載入 url模組另外增加一個 path變數url模組就跟如同他的命名一般專門處理 url字串處理裡面提供了許多方法來解決路徑上的問題因為從瀏覽器發出的要求路徑可能會帶有多種需求

或者 GET參數組合等因此我們需要將路徑單純化取用路徑部分的資料即可例如使用者可能會送出 http1270011337testsend=1如果直接

32 4 Nodejs基礎

Nodejs中文電子書

信任 requrl就會收到結果為testsend=1所以需要透過 url模組的方法將路徑資料過濾

在這邊使用 urlparse的方法裡面帶入網址格式資料會回傳路徑資料為了後需方便使用將回傳的資料設定到 path變數當中在回傳的路徑資料裡面包含資訊如下圖

這邊只需要使用單純的路徑要求直接取用 pathpathname就可以達到我們的目的

最後要做路徑的判別在不同的路徑可以指定不同的輸出在範例中

有三個可能結果第一個從瀏覽器輸入index就會顯示 index結果test 就會呈現出 test頁面最後如果都不符合預期的輸入會直接顯示 default的頁面最後的預防可以讓瀏覽器不會出現非預期結果讓程式的可靠性提昇

底下為測試結果

42 nodejs http路徑建立 33

Nodejs中文電子書

43 nodejs檔案讀取

前面已經介紹如何使用路由(rount)做出不同的回應實際應用只有在瀏覽器只有輸出幾個文字資料總是不夠的在本章節中將介紹如何使

用檔案讀取輸出檔案資料讓使用者在前端瀏覽器也可以讀取到完整的

html css javascript檔案輸出

檔案管理最重要的部分就是 lsquoFile system lthttpnodejsorgdocslatestapifshtmlgtlsquo這個模組此模組可以針對檔案做管理監控讀取等行為裡面有許多預設的方法底下是檔案輸出的基本範例底下會有兩個檔案

第一個是靜態 html檔案

ltDOCTYPE htmlgt

lthtml xmlns=rdquohttpwwww3org1999xhtmlrdquo xmllang=rdquozh-CNrdquo lang=rdquozh-CNrdquogt

ltheadgt

ltmeta http-equiv=rdquoContent-Typerdquo content=rdquotexthtml charset=UTF-7rdquo gt

lttitlegtnodejs index html filelttitlegt

ltheadgt

ltbodygt

34 4 Nodejs基礎

Nodejs中文電子書

lth1gtnodejs index html filelth1gt

ltbodygt

lthtmlgt

另一個為 nodejs程式

var fs = require(rdquofsrdquo)

filename = rdquostaticindexhtmlrdquo

encode = rdquoutf8rdquo

fsreadFile(filename encode function(err file)

consolelog(file)

)

一開始直接載入 le system模組 載入名稱為 fs讀取檔案主要使

用的方法為 readFile裡面以三個參數路徑 ( le path) 編碼方式 (encoding)

回應函式 (callback)路徑必須要設定為靜態 html所在位置才能指定到正確的檔案靜態檔案的編碼方式也必須正確這邊使用靜態檔案的編

碼為 utf8如果編碼設定錯誤nodejs讀取出來檔案結果會使用 byte raw格式輸出如果錯誤編碼格式會導致輸出資料為 byte raw

回應函式中裡面會使用兩個變數error為錯誤資訊如果讀取的檔案不存在或者發生錯誤error數值會是 true如果成功讀取資料 error將會是 falsecontent則是檔案內容資料讀取後將會把資料全數丟到 content這個變數當中

最後程式的輸出結果畫面如下

43 nodejs檔案讀取 35

Nodejs中文電子書

44 nodejs http靜態檔案輸出

前面已經了解如何讀取本地端檔案接下來將配合 http伺服器路由讓每個路由都能夠輸出相對應的靜態 html檔案首先新增加幾個靜態 html檔案

ltDOCTYPE htmlgt

lthtml xmlns=rdquohttpwwww3org1999xhtmlrdquo xmllang=rdquozh-CNrdquo lang=rdquozh-CNrdquogt

ltheadgt

ltmeta http-equiv=rdquoContent-Typerdquo content=rdquotexthtml charset=UTF-7rdquo gt

lttitlegtnodejs index html filelttitlegt

ltheadgt

ltbodygt

lth1gtnodejs index html filelth1gt

ltbodygt

lthtmlgt

ltDOCTYPE htmlgt

lthtml xmlns=rdquohttpwwww3org1999xhtmlrdquo xmllang=rdquozh-CNrdquo lang=rdquozh-CNrdquogt

ltheadgt

ltmeta http-equiv=rdquoContent-Typerdquo content=rdquotexthtml charset=UTF-7rdquo gt

lttitlegtnodejs test html filelttitlegt

ltheadgt

ltbodygt

lth1gtnodejs test html filelth1gt

ltbodygt

lthtmlgt

ltDOCTYPE htmlgt

lthtml xmlns=rdquohttpwwww3org1999xhtmlrdquo xmllang=rdquozh-CNrdquo lang=rdquozh-CNrdquogt

ltheadgt

ltmeta http-equiv=rdquoContent-Typerdquo content=rdquotexthtml charset=UTF-7rdquo gt

lttitlegtnodejs static html filelttitlegt

ltheadgt

ltbodygt

lth1gtnodejs static html filelth1gt

ltbodygt

lthtmlgt

36 4 Nodejs基礎

Nodejs中文電子書

準備一個包含基本路由功能的 http伺服器

var server

ip = rdquo127001rdquo

port = 1337

http = require(rsquohttprsquo)

url = require(rsquourlrsquo)

server = httpcreateServer(function (req res)

var path = urlparse(requrl)

)

serverlisten(port ip)

consolelog(rdquoServer running at httprdquo + ip + rdquordquo + port)

加入 le system 模組使用 readFile 的功能將這一段程式放置於

createServer的回應函式中

fsreadFile(filePath encode function(err file)

)

readFile的回應函式裡面加入頁面輸出讓瀏覽器可以正確讀到檔案在這邊我們設定讀取的檔案為 html 靜態檔案所以 Content-type 設定為texthtml讀取到檔案的內容將會正確輸出成 html靜態檔案

fsreadFile(filePath encode function(err file)

reswriteHead(200 rsquoContent-Typersquo rsquotexthtmlrsquo)

reswrite(file)

resend()

)

到這邊為止基本的程式內容都已經完成剩下一些細節的調整首先

路徑上必須做調整目前的靜態檔案全部都放置於 static資料夾底下設定一個變數來記住資料夾位置

接著將瀏覽器發出要求路徑與資料夾組合讀取正確 html靜態檔案使用者有可能會輸入錯誤路徑所以在讀取檔案的時候要加入錯誤處理

同時回應 404伺服器無法正確回應的 http header格式

加入這些細節的修改一個基本的 http 靜態 html輸出伺服器就完成了完整程式碼如下

44 nodejs http靜態檔案輸出 37

Nodejs中文電子書

var server

ip = rdquo127001rdquo

port = 1337

http = require(rsquohttprsquo)

fs = require(rdquofsrdquo)

folderPath = rdquostaticrdquo

url = require(rsquourlrsquo)

path

filePath

encode = rdquoutf8rdquo

server = httpcreateServer(function (req res)

path = urlparse(requrl)

filePath = folderPath + pathpathname

fsreadFile(filePath encode function(err file)

if (err)

reswriteHead(404 rsquoContent-Typersquo rsquotextplainrsquo)

resend()

return

reswriteHead(200 rsquoContent-Typersquo rsquotextapplicationrsquo)

reswrite(file)

resend()

)

)

serverlisten(port ip)

consolelog(rdquoServer running at httprdquo + ip + rdquordquo + port)

45 nodejs http GET資料擷取

http伺服器中除了路由之外另一個最常使用的方法就是擷取 GET資料本單元將會介紹如何透過基本 http伺服器擷取瀏覽器傳來的要求擷取 GET資料

在 http協定中GET參數都是藉由 URL從瀏覽器發出要求送至伺服器

38 4 Nodejs基礎

Nodejs中文電子書

端基本的傳送網址格式可能如下

http127001testsend=1amptest=2

上面這段網址裡面的 GET參數就是 send而這個變數的數值就為 1如果想要在 http伺服器取得 GET資料需要在瀏覽器給予的要求 (request)做處理

首先需要載入 query string這個模組這個模組主要是用來將字串資料

過濾後轉換成javascript物件

qs = require(rsquoquerystringrsquo)

接著在第一階段利用 url模組過濾瀏覽器發出的 URL資料後將回應的物件裡面的 query這個變數是一個字串值資料過濾後如下

send=1amptest=2

透過 query string使用 parse這個方法將資料轉換成 javascript物件就表示 GET的資料已經被伺服器端正式擷取下來

path = urlparse(requrl)

parameter = qsparse(pathquery)

整個 nodejs http GET參數完整擷取程式碼如下

var server

ip = rdquo127001rdquo

port = 1337

http = require(rsquohttprsquo)

qs = require(rsquoquerystringrsquo)

url = require(rsquourlrsquo)

server = httpcreateServer(function (req res)

var path = urlparse(requrl)

parameter = qsparse(pathquery)

consoledir(parameter)

reswriteHead(200 rsquoContent-Typersquo rsquotextplainrsquo)

reswrite(rsquoBrowser test GET parameternrsquo)

resend()

)

45 nodejs http GET資料擷取 39

Nodejs中文電子書

serverlisten(port ip)

consolelog(rdquoServer running at httprdquo + ip + rdquordquo + port)

程式運作之後由瀏覽器輸入要求網址之後nodejs伺服器端回應資料為

Server running at http1270011337

send rsquo1rsquo test rsquo1rsquo

46 本章結語

前面所解說的部份一大部分主要是處理 http伺服器基本問題雖然在某些部分有牽扯到 http 伺服器基本運作原理主要還是希望可以藉由這些基本範例練習 nodejs練習回應函式與語法串接的特點習慣編寫javascript風格程式當然直接這樣開發 nodejs是非常辛苦的接下來在模組實戰開發的部份將會介紹特定的模組一步一步帶領各位從無到有進行

nodejs應用程式開發

40 4 Nodejs基礎

5

NPM套件管理工具

npm全名為 Node Package Manager是 Nodejs的套件(package)管理工具類似 Perl的 ppm或 PHP的 PEAR等安裝 npm後使用npm install

module name指令即可安裝新套件維護管理套件的工作會更加輕鬆

npm可以讓Nodejs的開發者直接利用擴充線上的套件庫(packagesregistry)加速軟體專案的開發npm提供很友善的搜尋功能可以快速找到安裝需要的套件當這些套件發行新版本時npm也可以協助開發者自動更新這些套件

npm不僅可用於安裝新的套件它也支援搜尋列出已安裝模組及更新的功能

51 安裝 NPM

Nodejs在 063版本開始內建 npm讀者安裝的版本若是此版本或更新的版本就可以略過以下安裝說明

若要檢查 npm是否正確安裝可以使用以下的指令

npm -v

41

Nodejs中文電子書

執行結果說明

若 npm正確安裝執行 npm -v將會看到類似 110-2的版本訊息

若讀者安裝的 Nodejs版本比較舊或是有興趣嘗試自己動手安裝 npm工具則可以參考以下的說明

安裝於Windows系統

Nodejs for Windows 於 062 版開始內建 npm使用 nodejsorg 官方提供的安裝程式不需要進一步的設定就可以立即使用 npm指令對於Windows的開發者來說大幅降低環境設定的問題與門檻

除了使用 Nodejs內建的 npm讀者也可以從 npm官方提供的以下網址

httpnpmjsorgdist

這是由 npm提供的 Fancy Windows Install版本請下載壓縮檔(例如npm-110-3zip)並將壓縮檔內容解壓縮至 Nodejs的安裝路徑(例如CProgram Filesnodejs)

解壓縮後在 Nodejs的安裝路徑下應該有以下的檔案及資料夾

bull npmcmd(檔案)

bull node modules(資料夾)

安裝於 Linux系統

Ubuntu Linux的使用者可以加入 NPM Unoffcial PPA這個 repository即可使用 apt-get完成 npm安裝

42 5 NPM套件管理工具

Nodejs中文電子書

Ubuntu Linux使用 apt-get安裝 npm

sudo apt-get install python-software-properties

sudo add-apt-repository ppagias-kay-leenpm

sudo apt-get update

sudo apt-get install npm

npm官方提供的安裝程式 installsh可以適用於大多數的 Linux系統使用這個安裝程式請先確認

1 系統已安裝 curl工具(請使用 curl --version查看版本訊息)

2 已安裝 Nodejs並且 PATH正確設置

3 Nodejs的版本必須大於 04x

以下為 npm提供的安裝指令

curl httpnpmjsorginstallsh | sh

安裝成功會看到如下訊息

installsh安裝成功的訊息

npm10105 homeUSERNAMElocalnodelibnode_modulesnpm

It worked

安裝於Mac OS X

建議採用與 Nodejs相同的方式進行 npm的安裝例如使用MacPorts安裝 Nodejs就同樣使用MacPorts安裝 npm這樣對日後的維護才會更方便容易

使用 MacPorts安裝 npm是本書比較建議的方式它可以讓 npm的安裝移除及更新工作自動化將會幫助開發者節省寶貴時間

51 安裝 NPM 43

Nodejs中文電子書

安裝MacPorts的提示

在MacPorts網站可以取得 OS X系統版本對應的安裝程式(例如 106或 107)httpwwwmacportsorg安裝過程會詢問系統管理者密碼使用預設的選項完成安裝即可安

裝MacPorts之後在終端機執行 port -v將會看到MacPorts的版本訊息

安裝 npm之前先更新MacPorts的套件清單以確保安裝的 npm是最新版本

sudo port -d selfupdate

接著安裝 npm

sudo port install npm

若讀者的 Nodejs並非使用MacPorts安裝則不建議使用MacPorts安裝npm因為 MacPorts會自動檢查並安裝相依套件而 npm相依 nodejs所以MacPorts也會一併將 nodejs套件安裝造成先前讀者使用其它方式安裝的 nodejs被覆蓋

讀者可以先使用 MacPorts安裝 curl(sudo port install curl)

再參考 Linux的 installsh安裝方式即可使用 npm官方提供的安裝程式

NPM安裝後測試

npm是指令列工具(command-line tool)使用時請先打開系統的文字終端機工具

測試 npm安裝與設定是否正確請輸入指令如下

npm -v

或是

npm --version

如果 npm已經正確安裝設定就會顯示版本訊息

44 5 NPM套件管理工具

Nodejs中文電子書

執行結果(範例)

110-2

52 使用 NPM安裝套件

npm目前擁有超過 6000種套件(packages)可以在 npm registry使用關鍵字搜尋套件

httpsearchnpmjsorg

舉例來說在關鍵字欄位輸入「coffee-script」下方的清單就會自動列出包含 coffee-script關鍵字的套件

接著我們回到終端機模式的操作npm的指令工具本身就可以完成套

件搜尋的任務

例如以下的指令同樣可以找出 coffee-script相關套件

npm search coffee-script

以下是搜尋結果的參考畫面

52 使用 NPM安裝套件 45

Nodejs中文電子書

找到需要的套件後(例如 express)即可使用以下指令安裝

npm install coffee-script

值得注意的一點是使用 npm install會將指定的套件安裝在工

作目錄(Working Directory)的 node modules資料夾下

以Windows為例如果執行 npm install的目錄位於

Cproject1

那麼 npm將會自動建立一個 node modules的子目錄(如果不存在)

Cproject1node modules

並且將下載的套件放置於這個子目錄例如

Cproject1node modulescoffee-script

這個設計讓專案可以個別管理相依的套件並且可以在專案佈署或發

行時將這些套件(位於 node modules)一併打包方便其它專案的使用者不必再重新下載套件

這個 npm install的預設安裝模式為 local(本地)只會變更當前專案的資料夾不會影響系統

另一種安裝模式稱為 global(全域)這種模式會將套件安裝到系統資

料夾也就是 npm安裝路徑的 node modules資料夾例如

CProgram Filesnodejsnode modules

46 5 NPM套件管理工具

Nodejs中文電子書

是否要使用全域安裝可以依照套件是否提供新指令來判斷舉例來說express 套件提供 express 這個指令而 coffee-script 則提供 coffee

指令

在 local 安 裝 模 式 中這 些 指 令 的 程 式 檔 案會 被 安 裝 到node modules的 bin這個隱藏資料夾下除非將bin的路徑加入 PATH環境變數否則要執行這些指令將會相當不便

為了方便指令的執行我們可以在 npm install 加上 -g 或 --

global參數啟用 global安裝模式例如

npm install -g coffee-script

npm install -g express

使用 global安裝模式需要注意執行權限的問題若權限不足可能會出現類似以下的錯誤訊息

npm ERR Error EACCES permission denied rsquorsquo

npm ERR

npm ERR Please try running this command again as rootAdministrator

要獲得足夠得執行權限請參考以下說明

bull Windows 7 或 2008 以上在「命令提示字元」的捷徑按右鍵選擇「以系統管理員身分執行」執行 npm指令時就會具有 Administrator身分

bull Mac OS X或 Linux系統可以使用 sudo指令例如

sudo npm install -g express

bull Linux系統可以使用 root權限登入或是以「sudo su -」切換成 root身分(使用 root權限操作系統相當危險因此並不建議使用這種方式)

若加上 -g參數使用 npm install -g coffee-script完成安裝

後就可以在終端機執行 coffee指令例如

coffee -v

52 使用 NPM安裝套件 47

Nodejs中文電子書

執行結果(範例)

CoffeeScript version 120

53 套件的更新及維護

除了前一節說明的 search 及 install 用法npm 還提供其他許多指令(commands)

使用 npm help可以查詢可用的指令

npm help

執行結果(部分)

where ltcommandgt is one of

adduser apihelp author bin bugs c cache completion

config deprecate docs edit explore faq find get

help help-search home i info init install la link

list ll ln login ls outdated owner pack prefix

prune publish r rb rebuild remove restart rm root

run-script s se search set show star start stop

submodule tag test un uninstall unlink unpublish

unstar up update version view whoami

使用 npm help command可以查詢指令的詳細用法例如

npm help list

接下來本節要介紹開發過程常用的 npm指令

使用 list可以列出已安裝套件

npm list

48 5 NPM套件管理工具

Nodejs中文電子書

執行結果(範例)

-- coffee-script120

- express256

- connect185

| -- formidable108

-- mime124

-- mkdirp007

-- qs041

檢視某個套件的詳細資訊例如

npm show express

升級所有套件(如果該套件已發佈更新版本)

npm update

升級指定的套件

npm update express

移除指定的套件

npm uninstall express

54 使用 packagejson

對於正式的 Nodejs專案可以建立一個命名為 packagejson的設

定檔(純文字格式)檔案內容參考範例如下

54 使用 packagejson 49

Nodejs中文電子書

packagejson(範例)

rdquonamerdquo rdquoapplication-namerdquo

rdquoversionrdquo rdquo001rdquo

rdquoprivaterdquo true

rdquodependenciesrdquo

rdquoexpressrdquo rdquo255rdquo

rdquocoffee-scriptrdquo rdquolatestrdquo

rdquomongooserdquo rdquogt= 253rdquo

其中 name與 version依照專案的需求設置

需要注意的是 dependencies的設定它用於指定專案相依的套件名

稱及版本

bull rdquoexpressrdquo rdquo255rdquo

代表此專案相依版本 255的 express套件

bull rdquocoffee-scriptrdquo rdquolatestrdquo

使用最新版的 coffee-script套件(每次更新都會檢查新版)

bull rdquomongooserdquo rdquogt= 253rdquo

使用版本大於 253的 mongoose套件

假設某個套件的新版可能造成專案無法正常運作就必須指定套件的

版本避免專案的程式碼來不及更新以相容新版套件通常在開發初期的

專案需要盡可能維持新套件的相容性(以取得套件的更新或修正)可以

用「gt=」設定最低相容的版本或是使用「latest」設定永遠保持最新

套件

50 5 NPM套件管理工具

6

Express介紹

在前面的 nodejs基礎當中介紹許多許多開設 http的使用方法及介紹以及許多基本的 nodejs基本應用

接下來要介紹一個套件稱為 express [Express](httpexpressjscom)這個套件主要幫忙解決許多 nodejs http server所需要的基本服務讓開發 httpservice變得更為容易不需要像之前需要透過層層模組(module)才有辦法開始編寫自己的程式

這個套件是由 TJ Holowaychuk 製作而成的套件裡面包含基本的路由處理 (route)http資料處理(GETPOSTPUT)另外還與樣板套件(jshtml template engine)搭配同時也可以處理許多複雜化的問題

61 Express安裝

安裝方式十分簡單只要透過之前介紹的 NPM就可以使用簡單的指令安裝指令如下

這邊建議需要將此套件安裝成為全域模組方便日後使用

51

Nodejs中文電子書

62 Express基本操作

express的使用也十分簡單先來建立一個基本的 hello world

var app = require(rsquoexpressrsquo)createServer()

port = 1337

applisten(port)

appget(rsquorsquo function(req res)

ressend(rsquohello worldrsquo)

)

consolelog(rsquostart express servernrsquo)

可以從上面的程式碼發現基本操作與 nodejs http的建立方式沒有太大差異主要差在當我們設定路由時可以直接透過 appget方式設定回應與接受方式

63 Express路由處理

Express對於 http服務上有許多包裝讓開發者使用及設定上更為方便例如有幾個路由設定那我們就統一藉由 appget來處理

Create http server

appget(rsquorsquo function(req res)

ressend(rsquohello worldrsquo)

)

appget(rsquotestrsquo function(req res)

ressend(rsquotest renderrsquo)

)

appget(rsquouserrsquo function(req res)

ressend(rsquouser pagersquo)

)

52 6 Express介紹

Nodejs中文電子書

如上面的程式碼所表示appget可以帶入兩個參數第一個是路徑名稱設定第二個為回應函式 (call back function)回應函式裡面就如同之前的 createServer方法裡面包含 requestresponse兩個物件可供使用使用者就可以透過瀏覽器輸入不同的 url切換到不同的頁面顯示不同的結果

路由設定上也有基本的配對方式讓使用者從瀏覽器輸入的網址可以

是一個變數只要符合型態就可以有對應的頁面產出例如

Create http server

appget(rsquouseridrsquo function(req res)

ressend(rsquouser rsquo + reqparamsid)

)

appget(rsquonumberrsquo function(req res)

ressend(rsquonumber rsquo + reqparamsnumber)

)

裡 面 使 用 到number 從 網 址 輸 入 之 後 就 可 以 直 接 使 用reqparamsnumber 取得所輸入的資料變成 url 參數使用當然前面也是可以加上路徑的設定userid在瀏覽器上路徑必須符合userxxx透

過 reqparamsid就可以取到 xxx這個字串值

另外express參數處理也提供了路由參數配對處理也可以透過正規表示法作為參數設定

var app = require(rsquoexpressrsquo)createServer()

port = 1337

applisten(port)

appget(^ip((d23)((d23))((d23))((d23))) function(req res)

ressend(reqparams)

)

上面程式碼可以發現後面路由設定的型態是正規表示法裡面設定

格式為ip之後必須要加上 ip型態才會符合資料格式同時取得 ip資料已經由正規表示法將資料做分群因此可以取得 ip的四個數字

此程式執行之後可以透過瀏覽器測試輸入網址為 localhost3000ip25525510010可以從頁面獲得資料

63 Express路由處理 53

Nodejs中文電子書

此章節全部範例程式碼如下

overview

author Caesar Chi

blog clonnblogspotcom

version 20120226

create server

var app = require(rsquoexpressrsquo)createServer()

port = 1337

applisten(port)

normal style

appget(rsquorsquo function(req res)

ressend(rsquohello worldrsquo)

)

appget(rsquotestrsquo function(req res)

ressend(rsquotest renderrsquo)

)

parameter style

appget(rsquouseridrsquo function(req res)

ressend(rsquouser rsquo + reqparamsid)

)

appget(rsquonumberrsquo function(req res)

ressend(rsquonumber rsquo + reqparamsnumber)

)

REGX style

appget(^ip((d23)((d23))((d23))) function(req res)

ressend(reqparams)

)

appget(rsquorsquo function(req res)

ressend(rsquoPage not foundrsquo 404)

54 6 Express介紹

Nodejs中文電子書

)

consolelog(rsquostart express servernrsquo)

64 Express middleware

Express 裡面有一個十分好用的應用概念稱為 middleware可以透過middleware做出複雜的效果同時上面也有介紹 next方法參數傳遞就是靠 middleware的概念來傳遞參數讓開發者可以明確的控制程式邏輯

create http server

appuse(expressbodyParser())

appuse(expressmethodOverride())

appuse(expresssession())

上面都是一種 middleware的使用方式透過 appuse方式裡面載入函式執行方法回應函式會包含三個基本參數responserequestnext其中next表示下一個 middleware執行函式同時會自動將預設三個參數繼續帶往下個函式執行底下有個實驗

上面的片段程式執行後開啟瀏覽器連結上 localhost1337會發現伺服器回應結果順序如下

first middle ware

second middle ware

execute middle ware

end middleware function

從上面的結果可以得知剛才設定的 middleware都生效了在 appuse設定的 middleware是所有 url皆會執行方法如果有指定特定方法就可以使用 appget的 middleware設定在 appget函式的第二個參數就可以帶入函式或者是匿名函式只要函式裡面最後會接受 request response next這三個參數同時也有正確指定 next函式的執行時機最後都會執行到最後一個方法當然開發者也可以評估程式邏輯要執行到哪一個階段讓邏輯

可以更為分明

64 Express middleware 55

Nodejs中文電子書

65 Express路由應用

在實際開發上可能會遇到需要使用參數等方式混和變數一起使用

express裡面提供了一個很棒的處理方法 appall這個方式可以先採用基本路由配對再將設定為每個不同的處理方式開發者可以透過這個方式簡

化自己的程式邏輯

overview

author

version 20120226

create server

var app = require(rsquoexpressrsquo)createServer()

port = 1337

users = [

name rsquoClonnrsquo

name rsquoChirsquo

]

applisten(port)

appall(rsquouseridoprsquo function(req res next)

requser = users[reqparamsid]

if (requser)

next()

else

next(new Error(rsquocannot find user rsquo + reqparamsid))

)

appget(rsquouseridrsquo function(req res)

ressend(rsquoviewing rsquo + requsername)

)

appget(rsquouserideditrsquo function(req res)

ressend(rsquoediting rsquo + requsername)

)

56 6 Express介紹

Nodejs中文電子書

appget(rsquouseriddeletersquo function(req res)

ressend(rsquodeleting rsquo + requsername)

)

appget(rsquorsquo function(req res)

ressend(rsquoPage not foundrsquo 404)

)

consolelog(rsquostart express servernrsquo)

內部宣告一組預設的使用者分別給予名稱設定藉由 appall這個方法可以先將路由雛形建立再接下來設定 appget的路徑格式只要符合格式就會分配進入對應的方法中像上面的程式當中如果使用者輸入路徑為user0除了執行 appall程式之後執行 next方法就會對應到路徑設定為userid的這個方法當中如果使用者輸入路徑為user0edit就會執行到useridedit的對應方法

66 Express GET應用範例

我們準備一個使用 GET方法傳送資料的表單

ltDOCTYPE htmlgt

lthtmlgt

ltheadgt

ltmeta http-equiv=rdquoContent-Typerdquo content=rdquotexthtml charset=utf8rdquogt

lttitlegtNodejs 菜鳥筆記 (1)lttitlegt

ltlink rel=rdquostylesheetrdquo href=rdquocssstylecssrdquo type=rdquotextcssrdquo media=rdquoallrdquo gt

ltheadgt

ltbodygt

lth1gtNodejs 菜鳥筆記-註冊lth1gt

ltform id=rdquosignuprdquo method=rdquoGETrdquo action=rdquohttplocalhost3000Signuprdquogt

ltlabelgt 使用者名稱ltlabelgtltinput type=rdquotextrdquo id=rdquousernamerdquo name=rdquousernamerdquo gtltbrgt

ltlabelgt 電子郵件ltlabelgtltinput type=rdquotextrdquo id=rdquoemailrdquo name=rdquoemailrdquo gtltbrgt

ltinput type=rdquosubmitrdquo value=rdquo 註冊我的帳號rdquo gtltbrgt

ltformgt

ltbodygt

lthtmlgt

66 Express GET應用範例 57

Nodejs中文電子書

這個表單沒有什麼特別的地方我們只需要看第 9 行form 使用的method是 GET然後 action是rdquohttplocalhost3000Signupldquo等一下我們要來撰寫Signup這個 URL Path的處理程式

處理 Signup行為

我們知道所謂的 GET方法會透過 URL來把表單的值給帶過去以上面的表單來說到時候 URL會以這樣的形式傳遞

httplocalhost3000Signupusername=xxxampemail=xxx

所以要能處理這樣的資料必須有以下功能

bull 解析 URL

bull 辨別動作是 Signup

bull 解析出 username和 email

一旦能取得 username和 email的值程式就能加以應用了

處理 Signup的程式碼雛形

load module

var url = require(rsquourlrsquo)

urlData = urlparse(requrltrue)

action = urlDatapathname

reswriteHead(200 rdquoContent-Typerdquordquotexthtml charset=utf-8rdquo)

if (action === rdquoSignuprdquo)

user = urlDataquery

resend(rdquolth1gtrdquo + userusername + rdquo 歡迎您的加入 lth1gtltpgt 我們已經將會員啟用信寄至rdquo + useremail + rdquoltpgtrdquo)

首先需要加載 url module它是用來協助我們解析 URL的模組接著使用 urlparse方法第一個傳入 url字串變數也就是 requrl另外第二個參數的用意是設為 ture則引進 querystring模組來協助處理預設是 false它影響到的是 urlDataquery設為 true會傳回物件不然就只是一般的字串urlparse會將字串內容整理成一個物件我們把它指定給 urlData

action變數作為記錄 pathname這是我們稍後要來判斷目前網頁的動作是什麼接著先將 html表頭資訊 (Header)準備好再來判斷路徑邏輯如

58 6 Express介紹

Nodejs中文電子書

果是 Signup這個動作就把 urlDataquery裡的資料指定給 user然後輸出userusername和 useremail把使用者從表單註冊的資料顯示於頁面中

最後進行程式測試啟動 nodejs主程式之後開啟瀏覽器就會看到表單填寫完畢按下送出就可以看到結果了

完整 nodejs程式碼如下

var http = require(rsquohttprsquo)

url = require(rsquourlrsquo)

fs = require(rdquofsrdquo)

server

server = httpcreateServer(function (reqres)

var urlData

encode = rdquoutf8rdquo

filePath = rdquoviewexpress_get_example_formhtmlrdquo

action

urlData = urlparse(requrltrue)

action = urlDatapathname

reswriteHead(200 rdquoContent-Typerdquordquotexthtml charset=utf-8rdquo)

if (action === rdquoSignuprdquo)

user = urlDataquery

resend(rdquolth1gtrdquo + userusername + rdquo 歡迎您的加入 lth1gtltpgt 我們已經將會員啟用信寄至rdquo + useremail + rdquoltpgtrdquo)

else

fsreadFile(filePath encode function(err file)

reswrite(file)

resend()

)

)

serverlisten(3000)

consolelog(rsquoServer 跑起來了現在時間是rsquo + new Date())

66 Express GET應用範例 59

Nodejs中文電子書

67 Express POST應用範例

一開始準備基本的 html表單傳送內容以 POST方式form的 action屬性設定為 POST其餘 html內容與前一個範例應用相同

ltDOCTYPE htmlgt

lthtmlgt

ltheadgt

ltmeta http-equiv=rdquoContent-Typerdquo content=rdquotexthtml charset=utf8rdquogt

lttitlegtNodejs 菜鳥筆記 (1)lttitlegt

ltlink rel=rdquostylesheetrdquo href=rdquocssstylecssrdquo type=rdquotextcssrdquo media=rdquoallrdquo gt

ltheadgt

ltbodygt

lth1gtNodejs 菜鳥筆記-註冊lth1gt

ltform id=rdquosignuprdquo method=rdquoPOSTrdquo action=rdquohttplocalhost3000Signuprdquogt

ltlabelgt 使用者名稱ltlabelgtltinput type=rdquotextrdquo id=rdquousernamerdquo name=rdquousernamerdquo gtltbrgt

ltlabelgt 電子郵件ltlabelgtltinput type=rdquotextrdquo id=rdquoemailrdquo name=rdquoemailrdquo gtltbrgt

ltinput type=rdquosubmitrdquo value=rdquo 註冊我的帳號rdquo gtltbrgt

ltformgt

ltbodygt

lthtmlgt

nodejs的程式處理邏輯與前面 GET範例類似部分程式碼如下

qs = require(rsquoquerystringrsquo)

if (action === rdquoSignuprdquo)

formData = rsquorsquo

reqon(rdquodatardquo function (data)

formData += data

)

reqon(rdquoendrdquo function ()

user = qsparse(formData)

resend(rdquolth1gtrdquo + userusername + rdquo 歡迎您的加入 lth1gtltpgt 我們已經將會員啟用信寄至rdquo + useremail + rdquoltpgtrdquo)

)

60 6 Express介紹

Nodejs中文電子書

主要加入了rsquoquerystringrsquo這個moduel方便我們等一下解析由表單 POST回來的資料另外加入一個 formData的變數用來搜集待等一下表單回傳的資料前面的 GET範例我們只從 req拿出 url的資料這次要在利用req身上的事件處理

JavaScript 在訂閱事件時使用 addEventListener而 nodejs 使用的則是on這邊加上了監聽 data的事件會在瀏覽器傳送資料到Web Server時被執行參數是它所接收到的資料型態是字串

接著再增加 end的事件當瀏覽器的請求事件結束時它就會動作

由於瀏覽器使用 POST在上傳資料時會將資料一塊塊地上傳因為我們在監聽 data事件時透過 formData變數將它累加起來lt不過由於我們

上傳的資料很少一次就結束不過如果日後需要傳的是資料比較大的檔

案這個累加動作就很重要

當資料傳完就進到 end 事件中會用到 qsparse 來解析 formDataformData的內容是字串內容是

username=wordsmithampemail=wordsmith40somewhere

而 qsparse可以幫我們把這個 querystring轉成物件的格式也就是

username=wordsmithampemail=wordsmith40somewhere

一旦轉成物件並指定給 user之後其他的事情就和 GET方法時操作的一樣寫 response的表頭將內容回傳並將 userusername和 useremail代入到內容中

修改完成後接著執行 nodejs程式啟動 web server開啟瀏覽器進入表單測試看看POST的方式能否順利運作

完整程式碼如下

var http = require(rsquohttprsquo)

url = require(rsquourlrsquo)

fs = require(rdquofsrdquo)

qs = require(rsquoquerystringrsquo)

server

server = httpcreateServer(function (reqres)

var urlData

67 Express POST應用範例 61

Nodejs中文電子書

encode = rdquoutf8rdquo

filePath = rdquoviewexpress_post_example_formhtmlrdquo

formData

action

urlData = urlparse(requrltrue)

action = urlDatapathname

reswriteHead(200 rdquoContent-Typerdquordquotexthtml charset=utf-8rdquo)

if (action === rdquoSignuprdquo)

formData = rsquorsquo

reqon(rdquodatardquo function (data)

formData += data

)

reqon(rdquoendrdquo function ()

user = qsparse(formData)

resend(rdquolth1gtrdquo + userusername + rdquo 歡迎您的加入 lth1gtltpgt 我們已經將會員啟用信寄至rdquo + useremail + rdquoltpgtrdquo)

)

else

fsreadFile(filePath encode function(err file)

reswrite(file)

resend()

)

)

serverlisten(3000)

consolelog(rsquoServer 跑起來了現在時間是rsquo + new Date())

62 6 Express介紹

Nodejs中文電子書

68 Express AJAX應用範例

在 Nodejs要使用 Ajax傳送資料並且與之互動在接受資料的部份沒有太大的差別client端不是用 GET就是用 POST來傳資料重點在處理完後用 JSON格式回傳當然 Ajax不見得只傳 JSON格式有時是回傳一段 HTML碼不過後者對伺服器來說基本上就和前兩篇沒有差別了所以我們還是以回傳 JSON做為這一回的主題

這一回其實大多數的工作都會落在前端 Ajax上面前端要負責發送與接收資料並在接收資料後撤掉原先發送資料的表單並將取得的資料

改成 HTML格式之後放上頁面

首先先準備 HTML靜態頁面資料

ltDOCTYPE htmlgt

lthtmlgt

ltheadgt

lttitlegtNodejs 菜鳥筆記 (3)lttitlegt

ltlink rel=rdquostylesheetrdquo href=rdquocssstylecssrdquo type=rdquotextcssrdquo media=rdquoallrdquo gt

ltscript src=rdquohttpsajaxgoogleapiscomajaxlibsjquery171jqueryminjsrdquogtltscriptgt

ltheadgt

ltbodygt

lth1gtNodejs 菜鳥筆記-註冊 (用 Ajax)lth1gt

ltform id=rdquosignuprdquo action=rdquoSignuprdquo method=rdquoPOSTrdquogt

ltlabelgt 使用者名稱ltlabelgtltinput type=rdquotextrdquo id=rdquousernamerdquo name=rdquousernamerdquo gtltbrgt

ltlabelgt 電子郵件ltlabelgtltinput type=rdquotextrdquo id=rdquoemailrdquo name=rdquoemailrdquo gtltbrgt

ltinput type=rdquosubmitrdquo value=rdquo 註冊我的帳號rdquo gtltbrgt

ltformgt

ltdiv id=rdquoresultrdquogt

ltdivgt

ltscript type=rdquotextjavascriptrdquogt

$(function()$(rsquosignup input[type=rdquosubmitrdquo]rsquo)click(function(e)

var user =

userusername = $(rdquousernamerdquo)val()useremail = $(rdquoemailrdquo)val()

$post(rdquoSignuprdquouserfunction(data)greet(data)

)

68 Express AJAX應用範例 63

Nodejs中文電子書

epreventDefault()

)

var greet = function(msg)

var resultNode = $(rsquoresultrsquo)greeting = $(rsquolth2gtHirsquo+msgusername+rsquo 你的會員 id 是rsquo+ msgid +rsquo 我們會將會員啟動信寄至rsquo+msgemail+rsquolth2gtrsquo)

$(rsquosignuprsquo)hide()resultNodehtml(rdquordquo)

resultNodeappend(greeting)

)

ltscriptgt

ltbodygt

lthtmlgt

HTML頁面上準備了一個表單用來傳送註冊資料接著直接引用了Google CDN來載入 jQuery用來幫我們處理 Ajax的工作這次要傳送和接收的工作很大的變動都在 HTML頁面上的 JavaScript當中我們要做的事有 (相關 jQuery處理這邊不多做贅述指提起主要功能解說)

bull 用 jQUery取得 submit按鈕綁定它的 click動作

bull 取得表單 username和 email的值存放在 user這個物件中

bull 用 jQuery的 $post方法將 user的資料傳到 Server

bull 一旦成功取得資料後透過 greet這個 function組成回報給 user的訊息

bull 清空原本給使用者填資料的表單

bull 將 Server回傳的 usernameemail和 id這 3個資料組成回應的訊息

bull 將訊息放到原本表單的位置

經過以上的處理後一個 Ajax的表單的基本功能已經完成

接著進行 nodejs主要程式的編輯部分程式碼如下

var fs = require(rdquofsrdquo)

64 6 Express介紹

Nodejs中文電子書

qs = require(rsquoquerystringrsquo)

if (action === rdquoSignuprdquo)

formData = rsquorsquo

reqon(rdquodatardquo function (data)

formData += data

)

reqon(rdquoendrdquo function ()

var msg

user = qsparse(formData)

userid = rdquo123456rdquo

msg = JSONstringify(user)

reswriteHead(200 rdquoContent-Typerdquordquoapplicationjson charset=utf-8rdquordquoContent-Lengthrdquomsglength)

resend(msg)

)

這裡的程式和前面 POST範例基本上大同小異差別在

bull 幫 user的資料加上 id隨意存放一些文字進去讓 Server回傳的資料多於 Client端傳上來的不然會覺得 Server都沒做事

bull 增加了 msg 這個變數存放將 user 物件 JSON 文字化的結果JSONstringify這個轉換函式是 V8引擎所提供的如果你好奇的話

bull 大重點來了我們要告訴 Client端這次回傳的資料格式是 JSON所在 Content-type和 Content-Length要提供給 Client

Server很輕鬆就完成任務了最後進行程式測試啟動 nodejs主程式之後開啟瀏覽器就會看到表單填寫完畢按下送出就可以看到結果了

最後 nodejs本篇範例程式碼如下

var http = require(rsquohttprsquo)

url = require(rsquourlrsquo)

fs = require(rdquofsrdquo)

qs = require(rsquoquerystringrsquo)

server

68 Express AJAX應用範例 65

Nodejs中文電子書

server = httpcreateServer(function (reqres)

var urlData

encode = rdquoutf8rdquo

filePath = rdquoviewexpress_ajax_example_formhtmlrdquo

formData

action

urlData = urlparse(requrltrue)

action = urlDatapathname

if (action === rdquoSignuprdquo)

formData = rsquorsquo

reqon(rdquodatardquo function (data)

formData += data

)

reqon(rdquoendrdquo function ()

var msg

user = qsparse(formData)

userid = rdquo123456rdquo

msg = JSONstringify(user)

reswriteHead(200 rdquoContent-TyperdquordquoapplicationjsonrdquordquoContent-Lengthrdquomsglength)

resend(msg)

)

else

fsreadFile(filePath encode function(err file)

reswriteHead(200 rdquoContent-Typerdquordquotexthtml charset=utf-8rdquo)

reswrite(file)

resend()

)

)

serverlisten(3000)

66 6 Express介紹

Nodejs中文電子書

consolelog(rsquoServer 跑起來了現在時間是rsquo + new Date())

69 原始資料提供

bull [NodeJS初學者筆記 (1)-用 GET傳送資料] (httpithelpithomecomtwquestion10087402)

bull [NodeJS初學者筆記 (2)-用 POST傳送資料] (httpithelpithomecomtwquestion10087489)

bull [NodeJS 初學者筆記 (3)-用 Ajax 傳送資料] (httpithelpithomecomtwquestion10087627)

69 原始資料提供 67

Nodejs中文電子書

68 6 Express介紹

7

CoffeeScript

bull Spine

bull Hem

bull Spineapp

Letrsquos Have a Cup of CoffeeScript

bull es5-shim ECMAScript 5 compatibility shims for legacy JavaScript engines

ESnext

node-iform - A middleware for node-validator httpsgithubcomguileennode-iform

69

Nodejs中文電子書

70 7 CoffeeScript

8

製作一個 Hubot的 Plurk Adapter

81 應用事項提醒

此應用範例以CoffeeScript編寫主要程式架構採用Github釋放的Hubot專案以下有幾個主要注意事項請讀者注意

bull Hubot 一開始的架構除了內建的兩個 Adapter 之外其餘都要以Nodejs的Module方式才能運作

bull Hubot的 binhubot裡面寫著 npm install所以不管你怎麼改原始碼也不能改變第一點的狀況

其實上述都在討論同一件事情NodeJS的模組而且模組不能設定相對路徑之類的來安裝一定要包裝成 npm相符和格式才有辦法正確執行

82 建立 Adapter

首先需要準備兩個檔案

bull packagejson

bull plurkcoffee

71

Nodejs中文電子書

有這兩個就足以變成 NodeJS的模組了在開始 Coding之前先來設定一下 packagejson相依性

rdquonamerdquo rdquohubot-plurkrdquo

rdquoversionrdquo rdquo011rdquo

rdquomainrdquo rdquoplurkrdquo

rdquodependenciesrdquo

rdquohubotrdquo rdquogt=205rdquo

rdquooauthrdquo rdquordquo

rdquocronrdquordquordquo

這邊會使用到NodeJS的 cron模組(Module)而登入 Plurk需要OAuth標準授權才行所以也需要加載 OAuth模組

注意Hubot在讀取非內建模組時會自動在前面加上 hubot-的前置

83 建立 Robot跟 API

本篇應用基本上程式碼大部分參考 Hubot - Twitter Adapter來製作主要差異只有在使用 OAuth這個模組Twitter有 Streaming可用而 Plurk則得用 Comet方式來達到即時讀取

plurkcoffee程式碼

Robot = require(rdquohubotrdquo)robot()

Adapter = require(rdquohubtordquo)adapter()

EventEmitter = require(rdquoeventsrdquo)EventEmitter

oauth = require(rdquooauthrdquo)

cronJob = require(rdquocronrdquo)CronJob

class Plurk exntends Adapter

class PlurkStreaming exnteds EventEmitter

72 8 製作一個 Hubot的 Plurk Adapter

Nodejs中文電子書

先弄個基本架構主要 Class皆參考 Twitter Adapter命名方式接著再將 Plurk這個 Class增加幾個Method基本上只要有 run send reply就夠了而 run用來做初始化的部分

class Plurk entends Adapter

send (plurk id strings⋯) -gt

reply (plurk id strings⋯) -gt

run -gt

看起來有點東西接著來處理主要的 Plurk API結合部分

class PlurkStreaming extends EventEmitter

consuctor (options) -gt

plurk (callback) -gt

觀察河道

getChannel -gt

取得 Comet 網址

reply (plurk id message) -gt

回噗

acceptFriends -gt

接受好友

get (path callback) -gt

GET 請求

post (path body callback)-gt

POST 請求(其實是裝飾)

request (method path body callback)-gt

主要的 OAuth 請求

comet (server callback)-gt

噗浪的 Comet 傳回是 JavaScript Callback 要另外處理後才會變成 JSON

然後把注意力集中到 constructor上先把建構子弄好

constructor (options) -gt

super()

if optionskey and optionssecret and optionstoken and optionstoken secret

key = optionskey

secret = optionssecret

token = optionstoken

token secret = optionstoken secret

83 建立 Robot跟 API 73

Nodejs中文電子書

建立 OAuth 連接

consumer = new oauthOAuth(

rdquohttpwwwplurkcomOAuthrequest tokenrdquo

rdquohttpwwwplurkcomOAuthaccess tokenrdquo

key

secret

rdquo10rdquo

rdquohttpwwwplurkcomOAuthauthorizerdquo

rdquoHMAC-SHA1rdquo

)

domain = rdquowwwplurkcomrdquo

初始化取得 Comet 網址

do getChannel

else

throw new Error(rdquo 參數不足需要 Key Secret Token Token Secretrdquo)

接著來處理 request這個 method

request (method path body callback) -gt

記錄一下這次的 Request

consolelog(rdquohttpdomainpathrdquo)

Callback 這邊先不丟進去要用另一種方式處理

request = consumerget(rdquohttpdomainpathrdquo token token secret null)

requeston rdquoresponserdquo (res) -gt

reson rdquodatardquo (chunk) -gt

parseResponse(chunk+rsquorsquo callback)

reson rdquoendrdquo (data) -gt

consolelog rdquoEnd Request pathrdquo

reson rdquoerrorrdquo (data) -gt

consolelog rdquoError rdquo + data

requestend()

處理資料

parseResponse = (data callback) -gt

if datalength gt 0

用 TryCatch 避免處理 JSON 出錯導致整個中斷

try

callback null JSONparse(data)

catch err

74 8 製作一個 Hubot的 Plurk Adapter

Nodejs中文電子書

consolelog(rdquoError Parse JSONrdquo + data err)

繼續執行

callback null data ||

大致上就是這樣上面程式的架構已經將整個 Hubot Plurk Adapter完成因為在測試時竟然因為噗浪 Lag而沒讀到完整的 Comet資料然後造成程式異常為了避免這個問題發生因此需要加上為了完美呈現需要再

加上 Comet的處理所以要使用到 EventEmitter的功能

comet (server callback) -gt

在 Callback 裡面會找不到自身所以設定區域變數

self =

記錄一下這次的 Request

consolelog(rdquo[Comet] serverrdquo)

Callback 這邊先不丟進去要用另一種方式處理

request = consumerget(rdquohttpdomainpathrdquo token token secret null)

requeston rdquoresponserdquo (res) -gt

reson rdquodatardquo (chunk) -gt

parseResponse(chunk+rsquorsquo callback)

reson rdquoendrdquo (data) -gt

consolelog rdquoEnd Request pathrdquo

請求結束發出事件通知可以進行下一次請求

selfemit rdquonextPlurkrdquo

reson rdquoerrorrdquo (data) -gt

consolelog rdquoError rdquo + data

requestend()

處理資料

parseResponse = (data callback) -gt

if datalength gt 0

用 trycatch 避免失敗中斷

try

去掉 JavaScript 的 Callback

data = datamatch(CometChannelscriptCallback((+))s)

jsonData = rdquordquo

if data

83 建立 Robot跟 API 75

Nodejs中文電子書

jsonData = JSONparse(data[1])

else

如果沒有任何 Match 嘗試直接 parse

jsonData = JSONparse(data)

catch err

consolelog(rdquo[Comet] Errorrdquo data err)

用 TryCatch 避免處理 JSON 出錯導致整個中斷

try

只傳入 json 的 data 部分

callback null jsonDatadata

catch err

consolelog(rdquo[Comet]Error Parse JSONrdquo + data err)

繼續執行

callback null data ||

後面的 get跟 post就簡單多了

get (path callback) -gt

request(rdquoGETrdquo path null callback)

post (path body callback) -gt

request(rdquoPOSTrdquo path body callback)

接著處理取的 Comet網址的 getChannel

getChannel -gt

self =

get rdquoAPPRealtimegetUserChannelrdquo (error data) -gt

if error

檢查是否有 comet server

if datacomet server

selfchannel = datacomet server

如果沒有 Channel Ready 就嘗試連接會失敗

selfemit(rsquochannel readyrsquo)

那麼先來處理 Plurk Adaper好處理的部份

send (plruk id strings⋯)-gt

跟 Reply 一樣直接交給 reply 做

reply plurk id strings⋯

76 8 製作一個 Hubot的 Plurk Adapter

Nodejs中文電子書

reply (plurk id strings⋯) -gt

stringsforEach (message) =gt

botreply(plruk id message)

接著把 run處理好就可以上線運作摟

run -gt

self =

options =

key processenvHUBOT PLURK KEY

secret processenvHUBOT PLURK SECRET

token processenvHUBOT PLURK TOKEN

token secret processenvHUBOT PLURK TOKEN SECRET

創建剛剛的 API

bot = new PlurkStreaming(options)

依照 Twitter 的 new RobotTextMessage 會沒有反應所以參考 hubot-minecraft 的方式

r = robotconstructor

處理噗浪河道訊息

doPlurk = (data)-gt

檢查是否為回噗

if dataresponse

datacontent raw = dataresponsecontent raw

datauser id = dataresponseuser id

確定有噗浪 ID 跟訊息

if dataplurk id and datacontent raw

selfreceive new rTextMessage(dataplurk id datacontent raw)

取得 Comet Server 完成開始第一次 Comet 連接

boton rdquochannel readyrdquo () -gt

botplurk selfdoPlurk

上一次 Comet 完成繼續 Polling

boton rdquonextPlurkrdquo ()-gt

botplurk selfdoPlurk

定時接受好友邀請

do botacceptFriends

bot = bot

83 建立 Robot跟 API 77

Nodejs中文電子書

終於完成 AdapterHubot專案裡面的 scripts資料夾內是互動部分不需要像 Adapter如此大費周章處理只新增檔案並且設計好對白之後就會回噗了我開發用的機器人在此大家可以去跟他玩玩[httpplurkcomelct9620 bot](httpplurkcomelct9620 bot)

84 原始資料提供

bull [製 作 一 個 Hubot 的 噗 浪 Adapter](http revo-skillfrosttw blog20120318create-a-hubot-plurk-adapter)

78 8 製作一個 Hubot的 Plurk Adapter

9

Nodejs好用工具介紹

91 logio

Real-time log monitoring in your browser

httplogioorg

nodejs (gt=0412 lt=0611)

npm config set unsafe-perm true

npm install -g --prefix=usrlocal logio

logio server start

httplocalhost8998

79

Nodejs中文電子書

80 9 Nodejs好用工具介紹

10

精選文章

101 Express

bull 使用 NodeJS + Express從 GETPOST Request取值 [邀稿中]

bull link2 [邀稿中]

bull link3 [邀稿中]

81

Nodejs中文電子書

82 10 精選文章

11

參考資源

111 Nodejs書籍

bull Programming Nodejs April 2012 (est) Travis Swicegood Pragmatic Bookshelf

bull e Little Book on CoffeeScript January 2012 Alex MacCaw OrsquoReilly Media

bull Node for Front-End Developers January 2012 Garann Means OrsquoReilly Media

bull Building Hypermedia APIs with HTML5 and Node November 2011 MikeAmundsen OrsquoReilly Media

bull Node Web Development August 2011 David Herron PacktPub

bull CoffeeScript Accelerated JavaScript Development July 2011 Trevor BurnhamPragmatic Bookshelf

bull Getting Started with GEO CouchDB and Nodejs July 2011 Mick omp-son OrsquoReilly Media

bull What Is Node July 2011 Brett McLaughlin OrsquoReilly Media

bull Node Up and Running May 2011 Tom Hughes-Croucher Mike WilsonOrsquoReilly Media

83

Nodejs中文電子書

bull Hands-on Nodejs e Nodejs introduction and API reference May 2011 Pe-dro Teixeira LeanPub

bull e Node Beginner Book Manuel Kiessling

bull Mastering Nodejs visionmedia

112 Nodejs影音教學

bull e Node Sessions e Best of OSCON 2011 August 2011 OrsquoReilly Media(Video)

bull Tom Hughes-Croucher on Node March 2011 OrsquoReilly Media (Video)

113 Nodejs教學網站

bull Node Tuts

114 Nodejs課程

bull Introduction To Nodejs Van Nguyen CodeLesson

84 11 參考資源

Page 7: Nodejs Wiki

關於本書

這是一本關於 Nodejs技術的開放源碼電子書我們使用 GitHub維護電子書內容並交由 ContPub(Continuous Publishing)系統自動線上發佈本書提供 PDFEPUBMOBI及 HTML等格式您除了可以在網站檢視本書所有內容也可以將電子書下載至閱讀器保存

本書的線上閱讀網址與 GitHub資料同步更新

httpbooknodejstw

如果您想要取得本書的其他格式可以從 ContPub的電子書專頁下載最新版本

httpcontpuborgreadnodejs-wiki-book

本書適合 Nodejs初學者至進階開發者也歡迎您在學習時一起參與本書內容撰寫

授權

Nodejs台灣社群協作電子書採用創用 CC姓名標示 -非商業性授權您不必為本書付費

Nodejs Wiki Book book is licensed under the Attribution-NonCommercial 30Unported license You should not have paid for this book

您可以複製散佈及修改本書內容但請勿將本書用於商業用途

您可以在以下網址取得授權條款全文

1

Nodejs中文電子書

httpcreativecommonsorglicensesby-nc30legalcode

作者

本書由 Nodejs Taiwan社群成員協作以下名單依照字母排序

bull Caesar Chi (clonn)

bull Fillano Feng ( llano)

bull Kyle Lin (lyhcode)

Nodejs Taiwan是一個自由開放的技術學習社群我們歡迎您加入一起學習研究及分享

下載電子書

線上閱讀本書

httpbooknodejstw

PDF格式適合一般電腦及 7吋以上平板電腦閱讀

httpcontpuborgdownloadnodejs-wiki-bookpdf

EPUB格式適合 iPadiPhone行動裝置閱讀

httpcontpuborgdownloadnodejs-wiki-bookepub

MOBI格式適合 Kindle電子書閱讀器

httpcontpuborgdownloadnodejs-wiki-bookmobi

原始碼

本書最新的原始碼(中文版)網址如下

httpgithubcomnodejs-twnodejs-wiki-book

2 關於本書

Nodejs中文電子書

01 精選文章收錄流程

精選文章的用意是鼓勵作者在自已的網誌發表 Nodejs 教學再由Nodejs Taiwan社群挑選系列文章列入電子書的精選文集

1 將文章標題及連結貼到「精選文章」分類

2 社群工作小組以 E-Mail通知作者文章列入精選並邀請將內文授權給Nodejs Taiwan電子書分享

3 作者同意後由工作小組負責整理圖文發佈至電子書

4 以 E-Mail寄出感謝函通知原作者文章已收錄依作者意願調整文章內容

5 於 Nodejs Taiwan首頁及粉絲專頁推薦作者的文章

01 精選文章收錄流程 3

Nodejs中文電子書

4 關於本書

前言

Nodejs是 JavaScript程式語言的開發框架由 04x至目前 v060版本核心上已經有許多變更當然目的只有一個就是讓開發變得更簡單速

度能夠更快這是所有人奮鬥的目標而 nodeJS 華文維基平台主力由nodeJStw一群喜好 javascript開發者共同主筆內文以中文為主希望能夠降低開發者學習門檻藉由我們拋磚引玉讓更多華人開發者共同學習

討論

CoffeeScript好好玩

5

Nodejs中文電子書

6 前言

1

Nodejs簡介

Nodejs是一個高效能易擴充的網站應用程式開發框架 (Web Applica-tion Framework)它誕生的原因是為了讓開發者能夠更容易開發高延展性的網路服務不需要經過太多複雜的調校效能調整及程式修改就能

滿足網路服務在不同發展階段對效能的要求

Ryan Dahl是NodeJS的催生者目前任職於 Joyent (主機託管服務公司)他開發 NodeJS的目的就是希望能解決 Apache在連線數量過高時緩衝區 (buffer)和系統資源會很快被耗盡的問題希望能建立一個新的開發框架以解決這個問題因此嘗試使用效能十分優秀的 V8 JavaScript Engine讓網站開發人員熟悉的 JavaScript程式語言也能應用於後端服務程式的開發並且具有出色的執行效能

JavaScript是功能強大的物件導向程式語言但是在 JavaScript的官方規格中主要是定義網頁 (以瀏覽器為基礎) 應用程式需要的應用程式介面(API)對 JavaScript程式的應用範圍有所侷限為使 JavaScript能夠在更多用途發展CommonJS規範一組標準函式庫 (standard library)使 JavaScript的應用範圍能夠和 RubyPython及 Java等語言同樣豐富撰寫 NodeJS的JavaScript程式碼符合 CommonJS規範可以使用 CommonJS API為基礎開發程式並且在不同的 CommonJS兼容 (compliant) JavaScript執行環境中程式碼具有可攜性

7

Nodejs中文電子書

瀏覽器的 JavaScript與實現 CommonJS規範的 NodeJS有何不同呢瀏覽器的 JavaScript 提供 XMLHttpRequest 讓程式可以和網頁伺服器建立資料傳輸連線但這通常只能適用於網站開發的需求因為我們只能用

XMLHttpRequest與網頁伺服器通訊卻無法利用它建立其他類型如 Telnet FTP NTP的伺服器通訊如果我們想開發網路服務程式例如 SMTP電子郵件伺服器就必須使用 Sockets建立 TCP (某些服務則用 UDP)監聽及連線其他程式語言如 PHPJavaPythonPerl及 Ruby等在標準開發環境中皆有提供 Sockets API而瀏覽器的 JavaScript基於安全及貼近網站設計需求的考量下並未將 Sockets列入標準函式庫之中CommonJS的規範填補這種基礎函式庫功能的空缺遵循 CommonJS規範的 NodeJS可以直接使用 Sockets API建立各種網路服務程式

JavaScript語言本身支援 Lambda的特性因此一個匿名函式 (anonymousfunction)可以被儲存成一個變數並當作參數傳遞給另一個函式

var proc1 = function(op x) return op(x)

var op1 = function(x) return x+1 var op2 = function(x) return xx

proc1(op1 3) result is 4 proc1(op2 5) result is 25

另一個 JavaScript開發者必須掌握的語言特性稱為 Closure

NodeJS符合 CommonJS的規範使得 Callback方式易於實現也能夠讓更多同好基於 JavaScript開發符合 NodeJS的外掛模組 (Module)

回想以前要寫一個能夠同時容納上百人的上線的網路服務需要花費

多大的苦工可能 10人多就需要經過一次程式調整而 NodeJS就是為了解決這個困境NodeJS因此誕生它是一種利用 V8 Javascript編譯器所開發的產品利用 V8編譯器的高效能與 Javascript的程式開發特性所產生的網路程式

開發人員所編寫出來的 Javascript腳本程式怎麼可能會比其他語言寫出來的網路程式還要快上許多呢以前的網路程式原理是將使用者每次的

連線 (connection)都開啟一個執行緒 (thread)當連線爆增的時候將會快速耗盡系統效能並且容易產生阻塞 (block)的發生

8 1 Nodejs簡介

Nodejs中文電子書

NodeJS對於資源的調校有所不同當程式接收到一筆連線 (connection)會通知作業系統透過 epoll kqueue devpoll或 select將連線保留並且放入heap中配置先讓連線進入休眠 (Sleep)狀態當系統通知時才會觸發連線的 callback這種處理連線方式只會佔用掉記憶體並不會使用到 CPU資源另外因為採用 Javascript語言的特性每個 request都會有一個 callback如此可以避免發生 Block的狀況發生

基於 Callback特性目前NodeJS大多應用於 Comet(long pulling) RequestServer或者是高連線數量的網路服務上目前也有許多公司將 NodeJS設為內部核心網路服務之一在 NodeJS也提供了外掛管理 (Node packagemanagement)讓愛好 NodeJS輕易開發更多有趣的服務外掛並且提供到 npm讓全世界使用者快速安裝使用

本書最後執行測試版本為 nodejs v068相關 API文件可查詢 lsquohttpnodejsorg lthttpnodejsorggtlsquo本書所有範例均可於 linux windows上執行如遇到任何問題歡迎至 lsquohttpnodejstw lthttpnodejstwgtlsquo詢問對於nodejs相關問題

9

Nodejs中文電子書

10 1 Nodejs簡介

2

JavaScript與 NodeJS

其實使用 JavaScript 在網頁端與伺服器端的差距並不大但是為了使NodeJS可以發揮他最強大的能力有一些知識還是必要的所以還是針對這些主題介紹一下其中 Event LoopScope以及 Callback其實是比較需要了解的基本知識cpscurrying ow control是更進階的技巧與應用

21 Event Loop

可能很多人在寫 Javascript 時並不知道他是怎麼被執行的這個時候可以參考一下 jQuery作者 John Resig一篇好文章介紹事件及 timer怎麼在瀏覽器中執行How JavaScript Timers Work通常在網頁中所有的Javascript執行完畢後(這部份全部都在 global scope跑除非執行函數)接下來就是如 John Resig解釋的這樣所有的事件處理函數以及 timer執行的函數會排在一個 queue結構中利用一個無窮迴圈不斷從 queue中取出函數來執行這個就是 event loop

(除了 John Resig的那篇文章Nicholas C Zakas的 ldquoProfessional Javascriptfor Web Developer 2nd editionrdquo 有一個試閱本httpyuiblogcomassetspdfzakas-projs-2ed-ch18pdf598頁剛好也有簡短的說明)

所以在 Javascript中雖然有非同步但是他並不是使用執行緒所有

11

Nodejs中文電子書

的事件或是非同步執行的函數都是在同一個執行緒中利用 event loop的方式在執行至於一些比較慢的動作例如 IO網頁 render re ow等實際動作會在其他執行緒跑等到有結果時才利用事件來觸發處理函數來處理

這樣的模型有幾個好處沒有執行緒的額外成本所以反應速度很快不會

有任何程式同時用到同一個變數不必考慮 lock也不會產生 dead lock所以程式撰寫很簡單但是也有一些潛在問題任一個函數執行時間較長都

會讓其他函數更慢執行(因為一個跑完才會跑另一個)在多核心硬體普遍

的現在無法用單一的應用程式 instance發揮所有的硬體能力用 NodeJS撰寫伺服器程式碰到的也是一樣的狀況要讓系統發揮 event loop的效能就要盡量利用事件的方式來組織程式架構另外對於一些有可能較為耗

時的操作可以考慮使用 processnextTick函數來讓他以非同步的方式執行避免在同一個函數中執行太久擋住所有函數的執行

如果想要測試 event loop怎樣在「瀏覽器」中運行可以在函數中呼叫alert()這樣會讓所有 Javascript的執行停下來尤其會干擾所有使用 timer的函數執行有一個簡單的例子這是一個會依照設定的時間間隔嚴格執

行動作的動畫如果時間過了就會跳過要執行的動作點按圖片以後人

物會快速旋轉但是在旋轉執行完畢前按下「delay」按鈕讓 alert訊息等久一點接下來的動畫就完全不會出現了

22 Scope與 Closure

要快速理解 JavaScript的 Scope(變數作用範圍)原理只要記住他是Lexical Scope就差不多了簡單地說變數作用範圍是依照程式定義時(或者叫做程式文本)的上下文決定而不是執行時的上下文決定

為了維護程式執行時所依賴的變數即使執行時程式運行在原本的

scope之外他的變數作用範圍仍然維持不變這時程式依賴的自由變數(定義時不是 local的而是在上一層 scope定義的變數)一樣可以使用就好像被關閉起來所以叫做 Closure用程式看比較好懂

function outter(arg1)

arg1 及 free_variable1 對 inner 函數來說都是自由變數

var free_variable1 = 3

return function inner(arg2)

12 2 JavaScript與 NodeJS

Nodejs中文電子書

var local_variable1 =2arg2 及 local_variable1 對 inner 函數來說都是本地變數

return arg1 + arg2 + free_variable + local_variable1

var a = outter(1)變數 a就是 outter函數執行後返回的 inner函數 var b =a(4)執行 inner函數執行時上下文已經在 outter函數之外但是仍然能正常執行而且可以使用定義在 outter函數裡面的 arg1及 free variable1變數 consolelog(b)結果 10

在 Javascript中scope最主要的單位是函數(另外有 global及 eval)所以有可能製造出 closure的狀況通常在形式上都是有巢狀的函數定義而且內側的函數使用到定義在外側函數裡面的變數

Closure有可能會造成記憶體洩漏主要是因為被參考的變數無法被垃圾收集機制處理造成佔用的資源無法釋放所以使用上必須考慮清楚

不要造成意外的記憶體洩漏(在上面的例子中如果 a一直未執行使用到的記憶體就不會被釋放)

跟透過函數的參數把變數傳給函數比較起來Javascript Engine會比較難對 Closure進行最佳化如果有效能上的考量這一點也需要注意

23 Callback

要介紹 Callback之前要先提到 JavaScript的特色

JavaScript是一種函數式語言(functional language)所有 Javascript語言內的函數都是高階函數 (higher order function這是數學名詞計算機用語好像是 rst class function意指函數使用沒有任何限制與其他物件一樣)也就是說函數可以作為函數的參數傳給函數也可以當作函數的返回值這個特性讓 Javascript的函數使用上非常有彈性而且功能強大

callback在形式上其實就是把函數傳給函數然後在適當的時機呼叫傳入的函數Javascript使用的事件系統通常就是使用這種形式NodeJS中有一個物件叫做 EventEmitter這是 NodeJS事件處理的核心物件所有會使用事件處理的函數都會「繼承」這個物件(這裡說的繼承實

作上應該像是 mixin)他的使用很簡單可以使用物件on(事件名稱callback

23 Callback 13

Nodejs中文電子書

函數) 或是物件addListener(事件名稱callback 函數) 把你想要處理事件的函數傳入在物件中可以使用物件emit(事件名稱 參數) 呼叫傳入的callback函數這是 Observer Pattern的簡單實作而且跟在網頁中使用 DOM的 addEventListener使用上很類似也很容易上手不過 NodeJS是大量使用非同步方式執行的應用所以程式邏輯幾乎都是寫在 callback函數中當邏輯比較複雜時大量的 callback會讓程式看起來很複雜也比較難單元測試舉例來說

var p client = new Db(lsquointegration tests 20rsquo new Server(ldquo127001rdquo 27017) lsquopkrsquoCustomPKFactory) p clientopen(function(err p client)

p clientdropDatabase(function(err done)

p clientcreateCollection(lsquotest custom keyrsquo function(err collection)

collectioninsert(lsquoarsquo1 function(err docs)

collection nd(lsquo idrsquonew ObjectID(ldquoaaaaaaaaaaaardquo) function(err cursor)

cursortoArray(function(err items) testassertEquals(1 itemslength) p clientclose()

)

)

)

)

)

)

這是在網路上看到的一段操作 mongodb的程式碼為了循序操作所以必須在一個 callback裡面呼叫下一個動作要使用的函數這個函數裡面還是會使用 callback最後就形成一個非常深的巢狀

這樣的程式碼會比較難進行單元測試有一個簡單的解決方式是

盡量不要使用匿名函數來當作 callback或是 event handler透過這樣的方式

14 2 JavaScript與 NodeJS

Nodejs中文電子書

就可以對各個 handler做單元測試了例如

var http = require(lsquohttprsquo) var tools =

cookieParser function(request response) if(requestheaders[rsquoCookiersquo]) do parsing

var server = httpcreateServer(function(request response)

thisemit(lsquoinitrsquo request response)

) serveron(lsquoinitrsquo toolscookieParser) serverlisten(8080 lsquo127001rsquo)

更進一步可以把 tools改成外部 module例如叫做 toolsjs

moduleexports = cookieParser function(request response) if(requestheaders[rsquoCookiersquo]) do parsing

然後把程式改成

var http = require(lsquohttprsquo)

var server = httpcreateServer(function(request response) thisemit(lsquoinitrsquo re-quest response)

) serveron(lsquoinitrsquo require(lsquo toolsrsquo)cookieParser) serverlisten(8080lsquo127001rsquo)

這樣就可以單元測試 cookieParser了例如使用 nodeunit時可以這樣寫

var testCase = require(lsquonodeunitrsquo)testCase moduleexports = testCase(

ldquosetUprdquo function(cb) thisrequest = headers Cookielsquoname1val1 name2val2rsquo thisresponse = thisresult= name1rsquoval1rsquoname2rsquoval2rsquo

cb()

ldquotearDownrdquo function(cb)

cb()

23 Callback 15

Nodejs中文電子書

ldquonormal caserdquo function(test)

testexpect(1) var obj = require(lsquotoolsrsquo)cookieParser(thisrequest thisresponse)testdeepEqual(obj thisresult) testdone()

)

善於利用模組可以讓程式更好維護與測試

24 CPS(Continuation-Passing Style)

cps 是 callback 使用上的特例形式上就是在函數最後呼叫 callback這樣就好像把函數執行後把結果交給 callback 繼續運行所以稱作continuation-passing style利用 cps可以在非同步執行的情況下透過傳給 callback的這個 cps callback來獲知 callback執行完畢或是取得執行結果例如

lthtmlgt ltbodygt ltdiv id=rdquopanelrdquo style=rdquovisibilityhiddenrdquogtlt divgtlt bodygt lt htmlgt ltscriptgt var request = new XMLHttpRequest() re-questopen(lsquoGETrsquo lsquotest749txt timestamp=rsquo+new Date()getTime() true) re-questaddEventListener(lsquoreadystatechangersquo function(next)

return function() if(thisreadyState===4ampampthisstatus===200) next(thisresponseText)lt== 傳入的 cps callback 在動作完成時執行並取得結果進一步處理

(function(str)lt==這個匿名函數就是 cps callback

documentgetElementById(lsquopanelrsquo)innerHTML=str docu-mentgetElementById(lsquopanelrsquo)stylevisibility = lsquovisiblersquo

) false) requestsend() ltscriptgt

進一步的應用也可以參考 2-6流程控制

16 2 JavaScript與 NodeJS

Nodejs中文電子書

25 函數返回函數與 Currying

前面的 cps範例裡面使用了函數返回函數這是為了把 cps callback傳遞給 onreadystatechange事件處理函數的方法(因為這個事件處理函數並沒有設計好會傳送接收這樣的參數)實際會執行的事件處理函數其實是內層返回的那個函數之外包覆的這個函數主要是為了利用 Closure把 next傳給內層的事件處理函數這個方法更常使用的地方是為了解決一些

scope問題例如

ltscriptgt var accu=0count=10 for(var i=0 iltcount i++)

setTimeout(

function() countndash accu+=i if(countlt=0)

consolelog(accu)

50)

ltscriptgt

最後得出的結果會是 100而不是想像中的 45這是因為等到 setTime-out指定的函數執行時變數 i已經變成 10而離開迴圈了要解決這個問題就需要透過 Closure來保存變數 i

ltscriptgt var accu=0count=10 for(var i=0 iltcount i++)

setTimeout(

function(i) return function() countndash

accu+=i if(countlt=0)

consolelog(accu)

(i)

50)

25 函數返回函數與 Currying 17

Nodejs中文電子書

淺藍色底色的部份是跟上面例子不一樣的地方 ltscriptgt

函數返回函數的另外一個用途是可以暫緩函數執行例如

function add(m n) return m+n

var a = add(20 10) consolelog(a)

add這個函數必須同時輸入兩個參數才有辦法執行如果我希望這個函數可以先給它一個參數等一些處理過後再給一個參數然後得到結

果就必須用函數返回函數的方式做修改

function add(m)

return function(n) return m+n

var wait another arg = add(20)先給一個參數 var a = function(arr)

var ret=0 for(var i=0iltarrlengthi++) ret+=arr[i] return ret

([1234])計算一下另一個參數 var b = wait another arg(a)然後再繼續執行 consolelog(b)

像這樣利用函數返回函數使得原本接受多個參數的函數可以一次

接受一個參數直到參數接收完成才執行得到結果的方式有一個學名就

叫做Currying

綜合以上許多奇技淫巧就可以透過用函數來處理函數的方式調整

程式流程接下來看看

26 流程控制

(以 sync方式使用 async函數避開巢狀 callback循序呼叫 async callback等奇技淫巧)

建議參考

bull httphowtonodeorgcontrol- ow

bull httphowtonodeorgcontrol- ow-part-ii

18 2 JavaScript與 NodeJS

Nodejs中文電子書

bull httphowtonodeorgcontrol- ow-part-iii

bull httpblogmixunet20110202essential-node-js-patterns-and-snippets

這幾篇都是非常經典的 NodeJSJavascript流程控制好文章(阿mixu是在介紹一些 pattern時提到這方面的主題)不過我還是用幾個簡單的程式介紹一下做法跟概念

並發與等待

下面的程式參考了 mixu文章中的做法

var wait = function(callbacks done) consolelog(lsquowait startrsquo) var counter = call-backslength var results = [] var next = function(result) 接收函數執行結果並判斷是否結束執行 resultspush(result) if(ndashcounter == 0) done(results)如果結束執行就把所有執行結果傳給指定的 callback處理 for(var i = 0 i lt callbackslength i++) 依次呼叫所有要執行的函數 callbacks[i](next) consolelog(lsquowait endrsquo)

wait( [ function(next) setTimeout(function() consolelog(lsquodone arsquo) var result =500 next(result) 500) function(next) setTimeout(function() con-solelog(lsquodone brsquo) var result = 1000 next(result) 1000) function(next)setTimeout(function() consolelog(lsquodone crsquo) var result = 1500 next(1500) 1500) ] function(results) var ret = 0 i=0 for( iltresultslength i++) ret+= results[i] consolelog(lsquodone all result lsquo+ret)

)

執行結果wait start wait end done a done b done c done all result 3000

可以看出來其實 wait並不是真的等到所有函數執行完才結束執行而是在所有傳給他的函數執行完畢後(不論同步非同步)才執行處理結

果的函數(也就是 done())

不過這樣的寫法還不夠實用因為沒辦法實際讓函數可以等待執行

完畢又能當作事件處理函數來實際使用上面參考到的 Tim Caswell的文

26 流程控制 19

Nodejs中文電子書

章裡面有一種解法不過還需要額外包裝(在他的例子中)NodeJS核心的 fs物件把一些函數(例如 readFile)用 Currying處理類似像這樣

var fs = require(lsquofsrsquo) var readFile = function(path)

return function(callback errback)

fsreadFile(path function(err data)

if(err) errback()

else callback(data)

)

其他部份可以參考 Tim Caswell的文章他的 Doparallel跟上面的 wait差不多意思這裡只提示一下他沒說到的地方

另外一種做法是去修飾一下 callback當他作為事件處理函數執行後再用 cps的方式取得結果

ltscriptgt function Wait(fns done)

var count = 0 var results = [] thisgetCallback = function(index)

count++ return (function(waitback)

return function() var i=0args=[]for(iltargumentslengthi++)

argspush(arguments[i])

argspush(waitback) fns[index]apply(thisargs)

)(function(result) resultspush(result) if(ndashcount == 0)

20 2 JavaScript與 NodeJS

Nodejs中文電子書

done(results)

)

var a = new Wait(

[ function(waitback) consolelog(lsquodone arsquo) var result = 500 wait-back(result) function(waitback) consolelog(lsquodone brsquo) var result =1000 waitback(result) function(waitback) consolelog(lsquodone crsquo) varresult = 1500 waitback(result) ] function(results) var ret = 0 i=0for( iltresultslength i++) ret += results[i] consolelog(lsquodone allresult lsquo+ret)

) var callbacks = [agetCallback(0)agetCallback(1)agetCallback(0)agetCallback(2)]一次取出要使用的 callbacks避免結果提早送出 setTimeout(callbacks[0]500) setTimeout(callbacks[1] 1000) setTimeout(callbacks[2] 1500) setTime-out(callbacks[3] 2000) 當所有取出的 callbacks執行完畢就呼叫 done()來處理結果 ltscriptgt

執行結果

done a done b done a done c done all result 3500

上面只是一些小實驗更成熟的作品是 Tim Caswell 的 stephttpsgithubcomcreationixstep

如果希望真正使用同步的方式寫非同步則需要使用 Promisejs這一類的 library來轉換非同步函數不過他結構比較複雜 XD(見仁見智不過有些人認為 Promise有點過頭了)httpblogsmsdncombrbucktonarchive20110815promise-js-2-0-promise-framework-for-javascriptaspx

如果想不透過其他 Library做轉換又能直接用同步方式執行非同步函數大概就要使用一些需要額外 compile原始程式碼的方法了例如 BrunoJouhier的 streamlinejshttpsgithubcomSagestreamlinejs

26 流程控制 21

Nodejs中文電子書

循序執行

循序執行可以協助把非常深的巢狀 callback結構攤平例如用這樣的簡單模組來做(serialjs)

moduleexports = function(funs) var c = 0 if(isArrayOfFunctions(funs))

throw(lsquoArgument type was not matched Should be array of func-tionsrsquo)

return function()

var args = Arrayprototypeslicecall(arguments 0) if((cgt=funslength))

c++ return funs[c-1]apply(this args)

function isArrayOfFunctions(f ) if(typeof f == lsquoobjectrsquo) return false if(flength)return false if(fconcat) return false if(fsplice) return false var i = 0 for(iltflength i++)

if(typeof f[i] == lsquofunctionrsquo) return false

return true

簡單的測試範例(testSerialjs)使用 fs 模組確定某個 path 是檔案然後讀取印出檔案內容這樣會用到兩層的 callback所以測試中有使用serial的版本與 nested callbacks的版本做對照

var serial = require(lsquoserialrsquo) fs = require(lsquofsrsquo) path = lsquodclientjsrsquo cb = serial([ func-tion(err data)

if(err)

if(dataisFile) fsreadFile(path cb)

22 2 JavaScript與 NodeJS

Nodejs中文電子書

else consolelog(err)

function(err data)

if(err) consolelog(lsquo[ attened by searial]rsquo) con-solelog(datatoString(lsquoutf8rsquo))

else consolelog(err)

]) fsstat(path cb)

fsstat(path function(err data) 第一層 callback if(err)

if(dataisFile)

fsreadFile(path function(err data) 第二層 callback if(err)

consolelog(lsquo[nested callbacks]rsquo) con-solelog(datatoString(lsquoutf8rsquo))

else consolelog(err)

)

else consolelog(err)

)

關鍵在於這些 callback的執行是有順序性的所以利用 serial返回的一個函數 cb來取代這些 callback然後在 cb中控制每次會循序呼叫的函數

26 流程控制 23

Nodejs中文電子書

就可以把巢狀的 callback攤平成循序的 function陣列(就是傳給 serial函數的參數)

測試中的dclientjs 是一個簡單的 dnode 測試程式放在跟 testSerialjs同一個目錄

var dnode = require(lsquodnodersquo)

dnodeconnect(8000 lsquolocalhostrsquo function(remote)

remoterestart(function(str) consolelog(str) processexit()

)

)

執行測試程式後出現結果

[ attened by searial] var dnode = require(lsquodnodersquo)

dnodeconnect(8000 lsquolocalhostrsquo function(remote)

remoterestart(function(str) consolelog(str) processexit()

)

)

[nested callbacks] var dnode = require(lsquodnodersquo)

dnodeconnect(8000 lsquolocalhostrsquo function(remote)

remoterestart(function(str) consolelog(str) processexit()

)

)

對照起來看兩種寫法的結果其實是一樣的但是利用 serialjs巢狀的 callback結構就會消失

不過這樣也只限於順序單純的狀況如果函數執行的順序比較複雜

(不只是一直線)還是需要用功能更完整的流程控制模組比較好例如

httpsgithubcomcaolanasync

24 2 JavaScript與 NodeJS

3

Nodejs安裝與設定

本篇將講解如何在各個不同 OS建立 NodeJS環境目前 NodeJS 048版本環境架設方式需依賴 Linux指令才可編譯完成當然在不同作業系統中也已經有 NodeJS package可以直接使用指令快速架設以下各不同作業系統解說如何安裝 NodeJS

31 Ubuntu Linux

更新推薦使用 nvm

git clone gitgithubcomcreationixnvmgit ~nvm

echo rdquo ~nvmnvmshrdquo gtgt ~bashrc

nvm install v0614

nvm alias default v0614

以 上 可 參 考 http dreamerslabcom blog tw how-to-setup-a-node-js-development-environment-on-ubuntu-11-04

使用 APT套件管理工具是常見的方法以下是使用社群提供的 PPA安裝方式

sudo apt-get install python-software-properties

sudo add-apt-repository ppachris-leanodejs-devel

25

Nodejs中文電子書

sudo apt-get update

sudo apt-get install nodejs

32 Other Linux

Linux很適合作為 NodeJS的伺服器作業系統及開發環境安裝前請先確認以下套件已正確安裝

bull curl (wget)用來下載檔案的工具

bull git先進的版本控制工具

bull g++ GNU C++軟體編譯工具

bull make GNU軟體專案建置工具

安裝指令如下如設有權限問題請在指令前面加上 sudo

git clone httpsgithubcomjoyentnodegit

cd node

git checkout v067

configure

make

sudo make install

接著測試 nodeJS是否正常執行

node --version

出現版本訊息即表示安裝成功

33 Windows

nodeJS 在 v060 版本之後開始正式支援 windows native直接使用nodeexe 就可以執行程式支援性完全與 linux 相同更棒的部份就是不需經過編譯經過下載之後簡單設定完成立即開發 node程式

26 3 Nodejs安裝與設定

Nodejs中文電子書

下載 nodejs安裝檔案 lthttpnodejsorgdownloadgt

如此完成 windows native nodeexe安裝接著可以進入 command line執行測試在 command line輸入rdquonoderdquo會出現 nodejs版本訊息畫面表示安裝完成

33 Windows 27

Nodejs中文電子書

28 3 Nodejs安裝與設定

4

Nodejs基礎

前篇文章已經由介紹安裝至設定都有完整介紹nodeJS 內部除了javascript常用的函式 (function)物件 (object)之外也有許多不同的自訂物件nodeJS預設建立這些物件為核心物件是為了要讓開發流程更為這些資料在官方文件已經具有許多具體說明接下來將會介紹在開發 nodeJS程式時常見的物件特性與使用方法

41 nodejs http伺服器建立

在 lsquonodejs官方網站 lthttpnodejsorggtlsquo裡面有舉一個最簡單的 HTTP伺服器建立一開始初步就是建立一個伺服器平台讓 nodejs可以與瀏覽器互相行為每種語言一開始的程式建立都是以 Hello world開始最初也從 Hello world帶各位進入 nodejs的世界

輸入以下程式碼儲存檔案為 node basic http hello worldjs

var server

ip = rdquo127001rdquo

port = 1337

http = require(rsquohttprsquo)

server = httpcreateServer(function (req res)

29

Nodejs中文電子書

reswriteHead(200 rsquoContent-Typersquo rsquotextplainrsquo)

resend(rsquoHello Worldnrsquo)

)

serverlisten(port ip)

consolelog(rdquoServer running at httprdquo + ip + rdquordquo + port)

程式碼解講一開始需要有幾個基本的變數

bull ip 機器本身的 ip位置因為使用本地端因此設定為 127001

bull port 需要開通的阜號通常設定為 http port 80因範例不希望與基本 port相衝隨意設定為 1337

在 nodejs 的程式中有許多預設的模組可以使用因此需要使用require方法將模組引入在這邊我們需要使用 http這個模組因此將 http載入Http模組裡面內建有許多方法可以使用這邊採用 createServer創建一個基本的 http伺服器再將 http伺服器給予一個 server變數裡面的回呼函式 (call back function)可以載入 http伺服器的資料與回應方法 (requestresponse)在程式裡面就可以看到我們直接回應給瀏覽器端所需的 Header回應內容

reswriteHead(200 rsquoContent-Typersquo rsquotextplainrsquo)

resend(rsquoHello Worldnrsquo)

Http伺服器需要設定 port ip在最後需要設定 Http監聽需要使用到listen事件監聽所有 Http伺服器行為

httplisten(port ip)

所有事情都完成之後需要確認伺服器正確執行因此使用 console在javascript裡就有這個原生物件console所印出的資料都會顯示於 nodejs伺服器頁面這邊印出的資料並不會傳送到使用者頁面上之後許多除壞

(debug)都會用到 console物件

consolelog(rdquoServer running at httprdquo + ip + rdquordquo + port)

30 4 Nodejs基礎

Nodejs中文電子書

42 nodejs http路徑建立

前面已經介紹如何建立一個簡單的 http伺服器接下來本章節將會介紹如何處理伺服器路徑 (rout)問題在 http的協定下所有從瀏覽器發出的要求 (request)都需要經過處理路徑上的建立也是如此

路徑就是指伺服器 ip位置或者是網域名稱之後對於伺服器給予的要求修改剛才的 hello world檔案修改如下

server = httpcreateServer(function (req res)

consolelog(requrl)

reswriteHead(200 rsquoContent-Typersquo rsquotextplainrsquo)

resend(rsquohello worldnrsquo)

)

重新啟動 nodejs程式後在瀏覽器端測試一下路徑行為結果如下圖

當在瀏覽器輸入 http1270011337test 在伺服器端會收到兩個要求一個是我們輸入的test要求另外一個則是faviconicotest的路徑要求http伺服器本身需要經過程式設定才有辦法回應給瀏覽器端所需要的回應在伺服器中所有的路徑要求都是需要被解析才有辦法取得資料從

42 nodejs http路徑建立 31

Nodejs中文電子書

上面解說可以了解到在 nodejs當中所有的路徑都需要經過設定未經過設定的路由會讓瀏覽器無法取得任何資料導致錯誤頁面的發生底下將會解

說如何設定路由同時避免發生錯誤情形先前 nodejs程式需要增加一些修改才能讓使用者透過瀏覽器在不同路徑時有不同的結果根據剛才

的程式做如下的修改

var server

ip = rdquo127001rdquo

port = 1337

http = require(rsquohttprsquo)

url = require(rsquourlrsquo)

path

server = httpcreateServer(function (req res)

path = urlparse(requrl)

reswriteHead(200 rsquoContent-Typersquo rsquotextplainrsquo)

switch (pathpathname)

case rdquoindexrdquo

resend(rsquoI am indexnrsquo)

break

case rdquotestrdquo

resend(rsquothis is test pagenrsquo)

break

default

resend(rsquodefault pagenrsquo)

break

)

serverlisten(port ip)

consolelog(rdquoServer running at httprdquo + ip + rdquordquo + port)

程式做了片段的修改首先載入 url模組另外增加一個 path變數url模組就跟如同他的命名一般專門處理 url字串處理裡面提供了許多方法來解決路徑上的問題因為從瀏覽器發出的要求路徑可能會帶有多種需求

或者 GET參數組合等因此我們需要將路徑單純化取用路徑部分的資料即可例如使用者可能會送出 http1270011337testsend=1如果直接

32 4 Nodejs基礎

Nodejs中文電子書

信任 requrl就會收到結果為testsend=1所以需要透過 url模組的方法將路徑資料過濾

在這邊使用 urlparse的方法裡面帶入網址格式資料會回傳路徑資料為了後需方便使用將回傳的資料設定到 path變數當中在回傳的路徑資料裡面包含資訊如下圖

這邊只需要使用單純的路徑要求直接取用 pathpathname就可以達到我們的目的

最後要做路徑的判別在不同的路徑可以指定不同的輸出在範例中

有三個可能結果第一個從瀏覽器輸入index就會顯示 index結果test 就會呈現出 test頁面最後如果都不符合預期的輸入會直接顯示 default的頁面最後的預防可以讓瀏覽器不會出現非預期結果讓程式的可靠性提昇

底下為測試結果

42 nodejs http路徑建立 33

Nodejs中文電子書

43 nodejs檔案讀取

前面已經介紹如何使用路由(rount)做出不同的回應實際應用只有在瀏覽器只有輸出幾個文字資料總是不夠的在本章節中將介紹如何使

用檔案讀取輸出檔案資料讓使用者在前端瀏覽器也可以讀取到完整的

html css javascript檔案輸出

檔案管理最重要的部分就是 lsquoFile system lthttpnodejsorgdocslatestapifshtmlgtlsquo這個模組此模組可以針對檔案做管理監控讀取等行為裡面有許多預設的方法底下是檔案輸出的基本範例底下會有兩個檔案

第一個是靜態 html檔案

ltDOCTYPE htmlgt

lthtml xmlns=rdquohttpwwww3org1999xhtmlrdquo xmllang=rdquozh-CNrdquo lang=rdquozh-CNrdquogt

ltheadgt

ltmeta http-equiv=rdquoContent-Typerdquo content=rdquotexthtml charset=UTF-7rdquo gt

lttitlegtnodejs index html filelttitlegt

ltheadgt

ltbodygt

34 4 Nodejs基礎

Nodejs中文電子書

lth1gtnodejs index html filelth1gt

ltbodygt

lthtmlgt

另一個為 nodejs程式

var fs = require(rdquofsrdquo)

filename = rdquostaticindexhtmlrdquo

encode = rdquoutf8rdquo

fsreadFile(filename encode function(err file)

consolelog(file)

)

一開始直接載入 le system模組 載入名稱為 fs讀取檔案主要使

用的方法為 readFile裡面以三個參數路徑 ( le path) 編碼方式 (encoding)

回應函式 (callback)路徑必須要設定為靜態 html所在位置才能指定到正確的檔案靜態檔案的編碼方式也必須正確這邊使用靜態檔案的編

碼為 utf8如果編碼設定錯誤nodejs讀取出來檔案結果會使用 byte raw格式輸出如果錯誤編碼格式會導致輸出資料為 byte raw

回應函式中裡面會使用兩個變數error為錯誤資訊如果讀取的檔案不存在或者發生錯誤error數值會是 true如果成功讀取資料 error將會是 falsecontent則是檔案內容資料讀取後將會把資料全數丟到 content這個變數當中

最後程式的輸出結果畫面如下

43 nodejs檔案讀取 35

Nodejs中文電子書

44 nodejs http靜態檔案輸出

前面已經了解如何讀取本地端檔案接下來將配合 http伺服器路由讓每個路由都能夠輸出相對應的靜態 html檔案首先新增加幾個靜態 html檔案

ltDOCTYPE htmlgt

lthtml xmlns=rdquohttpwwww3org1999xhtmlrdquo xmllang=rdquozh-CNrdquo lang=rdquozh-CNrdquogt

ltheadgt

ltmeta http-equiv=rdquoContent-Typerdquo content=rdquotexthtml charset=UTF-7rdquo gt

lttitlegtnodejs index html filelttitlegt

ltheadgt

ltbodygt

lth1gtnodejs index html filelth1gt

ltbodygt

lthtmlgt

ltDOCTYPE htmlgt

lthtml xmlns=rdquohttpwwww3org1999xhtmlrdquo xmllang=rdquozh-CNrdquo lang=rdquozh-CNrdquogt

ltheadgt

ltmeta http-equiv=rdquoContent-Typerdquo content=rdquotexthtml charset=UTF-7rdquo gt

lttitlegtnodejs test html filelttitlegt

ltheadgt

ltbodygt

lth1gtnodejs test html filelth1gt

ltbodygt

lthtmlgt

ltDOCTYPE htmlgt

lthtml xmlns=rdquohttpwwww3org1999xhtmlrdquo xmllang=rdquozh-CNrdquo lang=rdquozh-CNrdquogt

ltheadgt

ltmeta http-equiv=rdquoContent-Typerdquo content=rdquotexthtml charset=UTF-7rdquo gt

lttitlegtnodejs static html filelttitlegt

ltheadgt

ltbodygt

lth1gtnodejs static html filelth1gt

ltbodygt

lthtmlgt

36 4 Nodejs基礎

Nodejs中文電子書

準備一個包含基本路由功能的 http伺服器

var server

ip = rdquo127001rdquo

port = 1337

http = require(rsquohttprsquo)

url = require(rsquourlrsquo)

server = httpcreateServer(function (req res)

var path = urlparse(requrl)

)

serverlisten(port ip)

consolelog(rdquoServer running at httprdquo + ip + rdquordquo + port)

加入 le system 模組使用 readFile 的功能將這一段程式放置於

createServer的回應函式中

fsreadFile(filePath encode function(err file)

)

readFile的回應函式裡面加入頁面輸出讓瀏覽器可以正確讀到檔案在這邊我們設定讀取的檔案為 html 靜態檔案所以 Content-type 設定為texthtml讀取到檔案的內容將會正確輸出成 html靜態檔案

fsreadFile(filePath encode function(err file)

reswriteHead(200 rsquoContent-Typersquo rsquotexthtmlrsquo)

reswrite(file)

resend()

)

到這邊為止基本的程式內容都已經完成剩下一些細節的調整首先

路徑上必須做調整目前的靜態檔案全部都放置於 static資料夾底下設定一個變數來記住資料夾位置

接著將瀏覽器發出要求路徑與資料夾組合讀取正確 html靜態檔案使用者有可能會輸入錯誤路徑所以在讀取檔案的時候要加入錯誤處理

同時回應 404伺服器無法正確回應的 http header格式

加入這些細節的修改一個基本的 http 靜態 html輸出伺服器就完成了完整程式碼如下

44 nodejs http靜態檔案輸出 37

Nodejs中文電子書

var server

ip = rdquo127001rdquo

port = 1337

http = require(rsquohttprsquo)

fs = require(rdquofsrdquo)

folderPath = rdquostaticrdquo

url = require(rsquourlrsquo)

path

filePath

encode = rdquoutf8rdquo

server = httpcreateServer(function (req res)

path = urlparse(requrl)

filePath = folderPath + pathpathname

fsreadFile(filePath encode function(err file)

if (err)

reswriteHead(404 rsquoContent-Typersquo rsquotextplainrsquo)

resend()

return

reswriteHead(200 rsquoContent-Typersquo rsquotextapplicationrsquo)

reswrite(file)

resend()

)

)

serverlisten(port ip)

consolelog(rdquoServer running at httprdquo + ip + rdquordquo + port)

45 nodejs http GET資料擷取

http伺服器中除了路由之外另一個最常使用的方法就是擷取 GET資料本單元將會介紹如何透過基本 http伺服器擷取瀏覽器傳來的要求擷取 GET資料

在 http協定中GET參數都是藉由 URL從瀏覽器發出要求送至伺服器

38 4 Nodejs基礎

Nodejs中文電子書

端基本的傳送網址格式可能如下

http127001testsend=1amptest=2

上面這段網址裡面的 GET參數就是 send而這個變數的數值就為 1如果想要在 http伺服器取得 GET資料需要在瀏覽器給予的要求 (request)做處理

首先需要載入 query string這個模組這個模組主要是用來將字串資料

過濾後轉換成javascript物件

qs = require(rsquoquerystringrsquo)

接著在第一階段利用 url模組過濾瀏覽器發出的 URL資料後將回應的物件裡面的 query這個變數是一個字串值資料過濾後如下

send=1amptest=2

透過 query string使用 parse這個方法將資料轉換成 javascript物件就表示 GET的資料已經被伺服器端正式擷取下來

path = urlparse(requrl)

parameter = qsparse(pathquery)

整個 nodejs http GET參數完整擷取程式碼如下

var server

ip = rdquo127001rdquo

port = 1337

http = require(rsquohttprsquo)

qs = require(rsquoquerystringrsquo)

url = require(rsquourlrsquo)

server = httpcreateServer(function (req res)

var path = urlparse(requrl)

parameter = qsparse(pathquery)

consoledir(parameter)

reswriteHead(200 rsquoContent-Typersquo rsquotextplainrsquo)

reswrite(rsquoBrowser test GET parameternrsquo)

resend()

)

45 nodejs http GET資料擷取 39

Nodejs中文電子書

serverlisten(port ip)

consolelog(rdquoServer running at httprdquo + ip + rdquordquo + port)

程式運作之後由瀏覽器輸入要求網址之後nodejs伺服器端回應資料為

Server running at http1270011337

send rsquo1rsquo test rsquo1rsquo

46 本章結語

前面所解說的部份一大部分主要是處理 http伺服器基本問題雖然在某些部分有牽扯到 http 伺服器基本運作原理主要還是希望可以藉由這些基本範例練習 nodejs練習回應函式與語法串接的特點習慣編寫javascript風格程式當然直接這樣開發 nodejs是非常辛苦的接下來在模組實戰開發的部份將會介紹特定的模組一步一步帶領各位從無到有進行

nodejs應用程式開發

40 4 Nodejs基礎

5

NPM套件管理工具

npm全名為 Node Package Manager是 Nodejs的套件(package)管理工具類似 Perl的 ppm或 PHP的 PEAR等安裝 npm後使用npm install

module name指令即可安裝新套件維護管理套件的工作會更加輕鬆

npm可以讓Nodejs的開發者直接利用擴充線上的套件庫(packagesregistry)加速軟體專案的開發npm提供很友善的搜尋功能可以快速找到安裝需要的套件當這些套件發行新版本時npm也可以協助開發者自動更新這些套件

npm不僅可用於安裝新的套件它也支援搜尋列出已安裝模組及更新的功能

51 安裝 NPM

Nodejs在 063版本開始內建 npm讀者安裝的版本若是此版本或更新的版本就可以略過以下安裝說明

若要檢查 npm是否正確安裝可以使用以下的指令

npm -v

41

Nodejs中文電子書

執行結果說明

若 npm正確安裝執行 npm -v將會看到類似 110-2的版本訊息

若讀者安裝的 Nodejs版本比較舊或是有興趣嘗試自己動手安裝 npm工具則可以參考以下的說明

安裝於Windows系統

Nodejs for Windows 於 062 版開始內建 npm使用 nodejsorg 官方提供的安裝程式不需要進一步的設定就可以立即使用 npm指令對於Windows的開發者來說大幅降低環境設定的問題與門檻

除了使用 Nodejs內建的 npm讀者也可以從 npm官方提供的以下網址

httpnpmjsorgdist

這是由 npm提供的 Fancy Windows Install版本請下載壓縮檔(例如npm-110-3zip)並將壓縮檔內容解壓縮至 Nodejs的安裝路徑(例如CProgram Filesnodejs)

解壓縮後在 Nodejs的安裝路徑下應該有以下的檔案及資料夾

bull npmcmd(檔案)

bull node modules(資料夾)

安裝於 Linux系統

Ubuntu Linux的使用者可以加入 NPM Unoffcial PPA這個 repository即可使用 apt-get完成 npm安裝

42 5 NPM套件管理工具

Nodejs中文電子書

Ubuntu Linux使用 apt-get安裝 npm

sudo apt-get install python-software-properties

sudo add-apt-repository ppagias-kay-leenpm

sudo apt-get update

sudo apt-get install npm

npm官方提供的安裝程式 installsh可以適用於大多數的 Linux系統使用這個安裝程式請先確認

1 系統已安裝 curl工具(請使用 curl --version查看版本訊息)

2 已安裝 Nodejs並且 PATH正確設置

3 Nodejs的版本必須大於 04x

以下為 npm提供的安裝指令

curl httpnpmjsorginstallsh | sh

安裝成功會看到如下訊息

installsh安裝成功的訊息

npm10105 homeUSERNAMElocalnodelibnode_modulesnpm

It worked

安裝於Mac OS X

建議採用與 Nodejs相同的方式進行 npm的安裝例如使用MacPorts安裝 Nodejs就同樣使用MacPorts安裝 npm這樣對日後的維護才會更方便容易

使用 MacPorts安裝 npm是本書比較建議的方式它可以讓 npm的安裝移除及更新工作自動化將會幫助開發者節省寶貴時間

51 安裝 NPM 43

Nodejs中文電子書

安裝MacPorts的提示

在MacPorts網站可以取得 OS X系統版本對應的安裝程式(例如 106或 107)httpwwwmacportsorg安裝過程會詢問系統管理者密碼使用預設的選項完成安裝即可安

裝MacPorts之後在終端機執行 port -v將會看到MacPorts的版本訊息

安裝 npm之前先更新MacPorts的套件清單以確保安裝的 npm是最新版本

sudo port -d selfupdate

接著安裝 npm

sudo port install npm

若讀者的 Nodejs並非使用MacPorts安裝則不建議使用MacPorts安裝npm因為 MacPorts會自動檢查並安裝相依套件而 npm相依 nodejs所以MacPorts也會一併將 nodejs套件安裝造成先前讀者使用其它方式安裝的 nodejs被覆蓋

讀者可以先使用 MacPorts安裝 curl(sudo port install curl)

再參考 Linux的 installsh安裝方式即可使用 npm官方提供的安裝程式

NPM安裝後測試

npm是指令列工具(command-line tool)使用時請先打開系統的文字終端機工具

測試 npm安裝與設定是否正確請輸入指令如下

npm -v

或是

npm --version

如果 npm已經正確安裝設定就會顯示版本訊息

44 5 NPM套件管理工具

Nodejs中文電子書

執行結果(範例)

110-2

52 使用 NPM安裝套件

npm目前擁有超過 6000種套件(packages)可以在 npm registry使用關鍵字搜尋套件

httpsearchnpmjsorg

舉例來說在關鍵字欄位輸入「coffee-script」下方的清單就會自動列出包含 coffee-script關鍵字的套件

接著我們回到終端機模式的操作npm的指令工具本身就可以完成套

件搜尋的任務

例如以下的指令同樣可以找出 coffee-script相關套件

npm search coffee-script

以下是搜尋結果的參考畫面

52 使用 NPM安裝套件 45

Nodejs中文電子書

找到需要的套件後(例如 express)即可使用以下指令安裝

npm install coffee-script

值得注意的一點是使用 npm install會將指定的套件安裝在工

作目錄(Working Directory)的 node modules資料夾下

以Windows為例如果執行 npm install的目錄位於

Cproject1

那麼 npm將會自動建立一個 node modules的子目錄(如果不存在)

Cproject1node modules

並且將下載的套件放置於這個子目錄例如

Cproject1node modulescoffee-script

這個設計讓專案可以個別管理相依的套件並且可以在專案佈署或發

行時將這些套件(位於 node modules)一併打包方便其它專案的使用者不必再重新下載套件

這個 npm install的預設安裝模式為 local(本地)只會變更當前專案的資料夾不會影響系統

另一種安裝模式稱為 global(全域)這種模式會將套件安裝到系統資

料夾也就是 npm安裝路徑的 node modules資料夾例如

CProgram Filesnodejsnode modules

46 5 NPM套件管理工具

Nodejs中文電子書

是否要使用全域安裝可以依照套件是否提供新指令來判斷舉例來說express 套件提供 express 這個指令而 coffee-script 則提供 coffee

指令

在 local 安 裝 模 式 中這 些 指 令 的 程 式 檔 案會 被 安 裝 到node modules的 bin這個隱藏資料夾下除非將bin的路徑加入 PATH環境變數否則要執行這些指令將會相當不便

為了方便指令的執行我們可以在 npm install 加上 -g 或 --

global參數啟用 global安裝模式例如

npm install -g coffee-script

npm install -g express

使用 global安裝模式需要注意執行權限的問題若權限不足可能會出現類似以下的錯誤訊息

npm ERR Error EACCES permission denied rsquorsquo

npm ERR

npm ERR Please try running this command again as rootAdministrator

要獲得足夠得執行權限請參考以下說明

bull Windows 7 或 2008 以上在「命令提示字元」的捷徑按右鍵選擇「以系統管理員身分執行」執行 npm指令時就會具有 Administrator身分

bull Mac OS X或 Linux系統可以使用 sudo指令例如

sudo npm install -g express

bull Linux系統可以使用 root權限登入或是以「sudo su -」切換成 root身分(使用 root權限操作系統相當危險因此並不建議使用這種方式)

若加上 -g參數使用 npm install -g coffee-script完成安裝

後就可以在終端機執行 coffee指令例如

coffee -v

52 使用 NPM安裝套件 47

Nodejs中文電子書

執行結果(範例)

CoffeeScript version 120

53 套件的更新及維護

除了前一節說明的 search 及 install 用法npm 還提供其他許多指令(commands)

使用 npm help可以查詢可用的指令

npm help

執行結果(部分)

where ltcommandgt is one of

adduser apihelp author bin bugs c cache completion

config deprecate docs edit explore faq find get

help help-search home i info init install la link

list ll ln login ls outdated owner pack prefix

prune publish r rb rebuild remove restart rm root

run-script s se search set show star start stop

submodule tag test un uninstall unlink unpublish

unstar up update version view whoami

使用 npm help command可以查詢指令的詳細用法例如

npm help list

接下來本節要介紹開發過程常用的 npm指令

使用 list可以列出已安裝套件

npm list

48 5 NPM套件管理工具

Nodejs中文電子書

執行結果(範例)

-- coffee-script120

- express256

- connect185

| -- formidable108

-- mime124

-- mkdirp007

-- qs041

檢視某個套件的詳細資訊例如

npm show express

升級所有套件(如果該套件已發佈更新版本)

npm update

升級指定的套件

npm update express

移除指定的套件

npm uninstall express

54 使用 packagejson

對於正式的 Nodejs專案可以建立一個命名為 packagejson的設

定檔(純文字格式)檔案內容參考範例如下

54 使用 packagejson 49

Nodejs中文電子書

packagejson(範例)

rdquonamerdquo rdquoapplication-namerdquo

rdquoversionrdquo rdquo001rdquo

rdquoprivaterdquo true

rdquodependenciesrdquo

rdquoexpressrdquo rdquo255rdquo

rdquocoffee-scriptrdquo rdquolatestrdquo

rdquomongooserdquo rdquogt= 253rdquo

其中 name與 version依照專案的需求設置

需要注意的是 dependencies的設定它用於指定專案相依的套件名

稱及版本

bull rdquoexpressrdquo rdquo255rdquo

代表此專案相依版本 255的 express套件

bull rdquocoffee-scriptrdquo rdquolatestrdquo

使用最新版的 coffee-script套件(每次更新都會檢查新版)

bull rdquomongooserdquo rdquogt= 253rdquo

使用版本大於 253的 mongoose套件

假設某個套件的新版可能造成專案無法正常運作就必須指定套件的

版本避免專案的程式碼來不及更新以相容新版套件通常在開發初期的

專案需要盡可能維持新套件的相容性(以取得套件的更新或修正)可以

用「gt=」設定最低相容的版本或是使用「latest」設定永遠保持最新

套件

50 5 NPM套件管理工具

6

Express介紹

在前面的 nodejs基礎當中介紹許多許多開設 http的使用方法及介紹以及許多基本的 nodejs基本應用

接下來要介紹一個套件稱為 express [Express](httpexpressjscom)這個套件主要幫忙解決許多 nodejs http server所需要的基本服務讓開發 httpservice變得更為容易不需要像之前需要透過層層模組(module)才有辦法開始編寫自己的程式

這個套件是由 TJ Holowaychuk 製作而成的套件裡面包含基本的路由處理 (route)http資料處理(GETPOSTPUT)另外還與樣板套件(jshtml template engine)搭配同時也可以處理許多複雜化的問題

61 Express安裝

安裝方式十分簡單只要透過之前介紹的 NPM就可以使用簡單的指令安裝指令如下

這邊建議需要將此套件安裝成為全域模組方便日後使用

51

Nodejs中文電子書

62 Express基本操作

express的使用也十分簡單先來建立一個基本的 hello world

var app = require(rsquoexpressrsquo)createServer()

port = 1337

applisten(port)

appget(rsquorsquo function(req res)

ressend(rsquohello worldrsquo)

)

consolelog(rsquostart express servernrsquo)

可以從上面的程式碼發現基本操作與 nodejs http的建立方式沒有太大差異主要差在當我們設定路由時可以直接透過 appget方式設定回應與接受方式

63 Express路由處理

Express對於 http服務上有許多包裝讓開發者使用及設定上更為方便例如有幾個路由設定那我們就統一藉由 appget來處理

Create http server

appget(rsquorsquo function(req res)

ressend(rsquohello worldrsquo)

)

appget(rsquotestrsquo function(req res)

ressend(rsquotest renderrsquo)

)

appget(rsquouserrsquo function(req res)

ressend(rsquouser pagersquo)

)

52 6 Express介紹

Nodejs中文電子書

如上面的程式碼所表示appget可以帶入兩個參數第一個是路徑名稱設定第二個為回應函式 (call back function)回應函式裡面就如同之前的 createServer方法裡面包含 requestresponse兩個物件可供使用使用者就可以透過瀏覽器輸入不同的 url切換到不同的頁面顯示不同的結果

路由設定上也有基本的配對方式讓使用者從瀏覽器輸入的網址可以

是一個變數只要符合型態就可以有對應的頁面產出例如

Create http server

appget(rsquouseridrsquo function(req res)

ressend(rsquouser rsquo + reqparamsid)

)

appget(rsquonumberrsquo function(req res)

ressend(rsquonumber rsquo + reqparamsnumber)

)

裡 面 使 用 到number 從 網 址 輸 入 之 後 就 可 以 直 接 使 用reqparamsnumber 取得所輸入的資料變成 url 參數使用當然前面也是可以加上路徑的設定userid在瀏覽器上路徑必須符合userxxx透

過 reqparamsid就可以取到 xxx這個字串值

另外express參數處理也提供了路由參數配對處理也可以透過正規表示法作為參數設定

var app = require(rsquoexpressrsquo)createServer()

port = 1337

applisten(port)

appget(^ip((d23)((d23))((d23))((d23))) function(req res)

ressend(reqparams)

)

上面程式碼可以發現後面路由設定的型態是正規表示法裡面設定

格式為ip之後必須要加上 ip型態才會符合資料格式同時取得 ip資料已經由正規表示法將資料做分群因此可以取得 ip的四個數字

此程式執行之後可以透過瀏覽器測試輸入網址為 localhost3000ip25525510010可以從頁面獲得資料

63 Express路由處理 53

Nodejs中文電子書

此章節全部範例程式碼如下

overview

author Caesar Chi

blog clonnblogspotcom

version 20120226

create server

var app = require(rsquoexpressrsquo)createServer()

port = 1337

applisten(port)

normal style

appget(rsquorsquo function(req res)

ressend(rsquohello worldrsquo)

)

appget(rsquotestrsquo function(req res)

ressend(rsquotest renderrsquo)

)

parameter style

appget(rsquouseridrsquo function(req res)

ressend(rsquouser rsquo + reqparamsid)

)

appget(rsquonumberrsquo function(req res)

ressend(rsquonumber rsquo + reqparamsnumber)

)

REGX style

appget(^ip((d23)((d23))((d23))) function(req res)

ressend(reqparams)

)

appget(rsquorsquo function(req res)

ressend(rsquoPage not foundrsquo 404)

54 6 Express介紹

Nodejs中文電子書

)

consolelog(rsquostart express servernrsquo)

64 Express middleware

Express 裡面有一個十分好用的應用概念稱為 middleware可以透過middleware做出複雜的效果同時上面也有介紹 next方法參數傳遞就是靠 middleware的概念來傳遞參數讓開發者可以明確的控制程式邏輯

create http server

appuse(expressbodyParser())

appuse(expressmethodOverride())

appuse(expresssession())

上面都是一種 middleware的使用方式透過 appuse方式裡面載入函式執行方法回應函式會包含三個基本參數responserequestnext其中next表示下一個 middleware執行函式同時會自動將預設三個參數繼續帶往下個函式執行底下有個實驗

上面的片段程式執行後開啟瀏覽器連結上 localhost1337會發現伺服器回應結果順序如下

first middle ware

second middle ware

execute middle ware

end middleware function

從上面的結果可以得知剛才設定的 middleware都生效了在 appuse設定的 middleware是所有 url皆會執行方法如果有指定特定方法就可以使用 appget的 middleware設定在 appget函式的第二個參數就可以帶入函式或者是匿名函式只要函式裡面最後會接受 request response next這三個參數同時也有正確指定 next函式的執行時機最後都會執行到最後一個方法當然開發者也可以評估程式邏輯要執行到哪一個階段讓邏輯

可以更為分明

64 Express middleware 55

Nodejs中文電子書

65 Express路由應用

在實際開發上可能會遇到需要使用參數等方式混和變數一起使用

express裡面提供了一個很棒的處理方法 appall這個方式可以先採用基本路由配對再將設定為每個不同的處理方式開發者可以透過這個方式簡

化自己的程式邏輯

overview

author

version 20120226

create server

var app = require(rsquoexpressrsquo)createServer()

port = 1337

users = [

name rsquoClonnrsquo

name rsquoChirsquo

]

applisten(port)

appall(rsquouseridoprsquo function(req res next)

requser = users[reqparamsid]

if (requser)

next()

else

next(new Error(rsquocannot find user rsquo + reqparamsid))

)

appget(rsquouseridrsquo function(req res)

ressend(rsquoviewing rsquo + requsername)

)

appget(rsquouserideditrsquo function(req res)

ressend(rsquoediting rsquo + requsername)

)

56 6 Express介紹

Nodejs中文電子書

appget(rsquouseriddeletersquo function(req res)

ressend(rsquodeleting rsquo + requsername)

)

appget(rsquorsquo function(req res)

ressend(rsquoPage not foundrsquo 404)

)

consolelog(rsquostart express servernrsquo)

內部宣告一組預設的使用者分別給予名稱設定藉由 appall這個方法可以先將路由雛形建立再接下來設定 appget的路徑格式只要符合格式就會分配進入對應的方法中像上面的程式當中如果使用者輸入路徑為user0除了執行 appall程式之後執行 next方法就會對應到路徑設定為userid的這個方法當中如果使用者輸入路徑為user0edit就會執行到useridedit的對應方法

66 Express GET應用範例

我們準備一個使用 GET方法傳送資料的表單

ltDOCTYPE htmlgt

lthtmlgt

ltheadgt

ltmeta http-equiv=rdquoContent-Typerdquo content=rdquotexthtml charset=utf8rdquogt

lttitlegtNodejs 菜鳥筆記 (1)lttitlegt

ltlink rel=rdquostylesheetrdquo href=rdquocssstylecssrdquo type=rdquotextcssrdquo media=rdquoallrdquo gt

ltheadgt

ltbodygt

lth1gtNodejs 菜鳥筆記-註冊lth1gt

ltform id=rdquosignuprdquo method=rdquoGETrdquo action=rdquohttplocalhost3000Signuprdquogt

ltlabelgt 使用者名稱ltlabelgtltinput type=rdquotextrdquo id=rdquousernamerdquo name=rdquousernamerdquo gtltbrgt

ltlabelgt 電子郵件ltlabelgtltinput type=rdquotextrdquo id=rdquoemailrdquo name=rdquoemailrdquo gtltbrgt

ltinput type=rdquosubmitrdquo value=rdquo 註冊我的帳號rdquo gtltbrgt

ltformgt

ltbodygt

lthtmlgt

66 Express GET應用範例 57

Nodejs中文電子書

這個表單沒有什麼特別的地方我們只需要看第 9 行form 使用的method是 GET然後 action是rdquohttplocalhost3000Signupldquo等一下我們要來撰寫Signup這個 URL Path的處理程式

處理 Signup行為

我們知道所謂的 GET方法會透過 URL來把表單的值給帶過去以上面的表單來說到時候 URL會以這樣的形式傳遞

httplocalhost3000Signupusername=xxxampemail=xxx

所以要能處理這樣的資料必須有以下功能

bull 解析 URL

bull 辨別動作是 Signup

bull 解析出 username和 email

一旦能取得 username和 email的值程式就能加以應用了

處理 Signup的程式碼雛形

load module

var url = require(rsquourlrsquo)

urlData = urlparse(requrltrue)

action = urlDatapathname

reswriteHead(200 rdquoContent-Typerdquordquotexthtml charset=utf-8rdquo)

if (action === rdquoSignuprdquo)

user = urlDataquery

resend(rdquolth1gtrdquo + userusername + rdquo 歡迎您的加入 lth1gtltpgt 我們已經將會員啟用信寄至rdquo + useremail + rdquoltpgtrdquo)

首先需要加載 url module它是用來協助我們解析 URL的模組接著使用 urlparse方法第一個傳入 url字串變數也就是 requrl另外第二個參數的用意是設為 ture則引進 querystring模組來協助處理預設是 false它影響到的是 urlDataquery設為 true會傳回物件不然就只是一般的字串urlparse會將字串內容整理成一個物件我們把它指定給 urlData

action變數作為記錄 pathname這是我們稍後要來判斷目前網頁的動作是什麼接著先將 html表頭資訊 (Header)準備好再來判斷路徑邏輯如

58 6 Express介紹

Nodejs中文電子書

果是 Signup這個動作就把 urlDataquery裡的資料指定給 user然後輸出userusername和 useremail把使用者從表單註冊的資料顯示於頁面中

最後進行程式測試啟動 nodejs主程式之後開啟瀏覽器就會看到表單填寫完畢按下送出就可以看到結果了

完整 nodejs程式碼如下

var http = require(rsquohttprsquo)

url = require(rsquourlrsquo)

fs = require(rdquofsrdquo)

server

server = httpcreateServer(function (reqres)

var urlData

encode = rdquoutf8rdquo

filePath = rdquoviewexpress_get_example_formhtmlrdquo

action

urlData = urlparse(requrltrue)

action = urlDatapathname

reswriteHead(200 rdquoContent-Typerdquordquotexthtml charset=utf-8rdquo)

if (action === rdquoSignuprdquo)

user = urlDataquery

resend(rdquolth1gtrdquo + userusername + rdquo 歡迎您的加入 lth1gtltpgt 我們已經將會員啟用信寄至rdquo + useremail + rdquoltpgtrdquo)

else

fsreadFile(filePath encode function(err file)

reswrite(file)

resend()

)

)

serverlisten(3000)

consolelog(rsquoServer 跑起來了現在時間是rsquo + new Date())

66 Express GET應用範例 59

Nodejs中文電子書

67 Express POST應用範例

一開始準備基本的 html表單傳送內容以 POST方式form的 action屬性設定為 POST其餘 html內容與前一個範例應用相同

ltDOCTYPE htmlgt

lthtmlgt

ltheadgt

ltmeta http-equiv=rdquoContent-Typerdquo content=rdquotexthtml charset=utf8rdquogt

lttitlegtNodejs 菜鳥筆記 (1)lttitlegt

ltlink rel=rdquostylesheetrdquo href=rdquocssstylecssrdquo type=rdquotextcssrdquo media=rdquoallrdquo gt

ltheadgt

ltbodygt

lth1gtNodejs 菜鳥筆記-註冊lth1gt

ltform id=rdquosignuprdquo method=rdquoPOSTrdquo action=rdquohttplocalhost3000Signuprdquogt

ltlabelgt 使用者名稱ltlabelgtltinput type=rdquotextrdquo id=rdquousernamerdquo name=rdquousernamerdquo gtltbrgt

ltlabelgt 電子郵件ltlabelgtltinput type=rdquotextrdquo id=rdquoemailrdquo name=rdquoemailrdquo gtltbrgt

ltinput type=rdquosubmitrdquo value=rdquo 註冊我的帳號rdquo gtltbrgt

ltformgt

ltbodygt

lthtmlgt

nodejs的程式處理邏輯與前面 GET範例類似部分程式碼如下

qs = require(rsquoquerystringrsquo)

if (action === rdquoSignuprdquo)

formData = rsquorsquo

reqon(rdquodatardquo function (data)

formData += data

)

reqon(rdquoendrdquo function ()

user = qsparse(formData)

resend(rdquolth1gtrdquo + userusername + rdquo 歡迎您的加入 lth1gtltpgt 我們已經將會員啟用信寄至rdquo + useremail + rdquoltpgtrdquo)

)

60 6 Express介紹

Nodejs中文電子書

主要加入了rsquoquerystringrsquo這個moduel方便我們等一下解析由表單 POST回來的資料另外加入一個 formData的變數用來搜集待等一下表單回傳的資料前面的 GET範例我們只從 req拿出 url的資料這次要在利用req身上的事件處理

JavaScript 在訂閱事件時使用 addEventListener而 nodejs 使用的則是on這邊加上了監聽 data的事件會在瀏覽器傳送資料到Web Server時被執行參數是它所接收到的資料型態是字串

接著再增加 end的事件當瀏覽器的請求事件結束時它就會動作

由於瀏覽器使用 POST在上傳資料時會將資料一塊塊地上傳因為我們在監聽 data事件時透過 formData變數將它累加起來lt不過由於我們

上傳的資料很少一次就結束不過如果日後需要傳的是資料比較大的檔

案這個累加動作就很重要

當資料傳完就進到 end 事件中會用到 qsparse 來解析 formDataformData的內容是字串內容是

username=wordsmithampemail=wordsmith40somewhere

而 qsparse可以幫我們把這個 querystring轉成物件的格式也就是

username=wordsmithampemail=wordsmith40somewhere

一旦轉成物件並指定給 user之後其他的事情就和 GET方法時操作的一樣寫 response的表頭將內容回傳並將 userusername和 useremail代入到內容中

修改完成後接著執行 nodejs程式啟動 web server開啟瀏覽器進入表單測試看看POST的方式能否順利運作

完整程式碼如下

var http = require(rsquohttprsquo)

url = require(rsquourlrsquo)

fs = require(rdquofsrdquo)

qs = require(rsquoquerystringrsquo)

server

server = httpcreateServer(function (reqres)

var urlData

67 Express POST應用範例 61

Nodejs中文電子書

encode = rdquoutf8rdquo

filePath = rdquoviewexpress_post_example_formhtmlrdquo

formData

action

urlData = urlparse(requrltrue)

action = urlDatapathname

reswriteHead(200 rdquoContent-Typerdquordquotexthtml charset=utf-8rdquo)

if (action === rdquoSignuprdquo)

formData = rsquorsquo

reqon(rdquodatardquo function (data)

formData += data

)

reqon(rdquoendrdquo function ()

user = qsparse(formData)

resend(rdquolth1gtrdquo + userusername + rdquo 歡迎您的加入 lth1gtltpgt 我們已經將會員啟用信寄至rdquo + useremail + rdquoltpgtrdquo)

)

else

fsreadFile(filePath encode function(err file)

reswrite(file)

resend()

)

)

serverlisten(3000)

consolelog(rsquoServer 跑起來了現在時間是rsquo + new Date())

62 6 Express介紹

Nodejs中文電子書

68 Express AJAX應用範例

在 Nodejs要使用 Ajax傳送資料並且與之互動在接受資料的部份沒有太大的差別client端不是用 GET就是用 POST來傳資料重點在處理完後用 JSON格式回傳當然 Ajax不見得只傳 JSON格式有時是回傳一段 HTML碼不過後者對伺服器來說基本上就和前兩篇沒有差別了所以我們還是以回傳 JSON做為這一回的主題

這一回其實大多數的工作都會落在前端 Ajax上面前端要負責發送與接收資料並在接收資料後撤掉原先發送資料的表單並將取得的資料

改成 HTML格式之後放上頁面

首先先準備 HTML靜態頁面資料

ltDOCTYPE htmlgt

lthtmlgt

ltheadgt

lttitlegtNodejs 菜鳥筆記 (3)lttitlegt

ltlink rel=rdquostylesheetrdquo href=rdquocssstylecssrdquo type=rdquotextcssrdquo media=rdquoallrdquo gt

ltscript src=rdquohttpsajaxgoogleapiscomajaxlibsjquery171jqueryminjsrdquogtltscriptgt

ltheadgt

ltbodygt

lth1gtNodejs 菜鳥筆記-註冊 (用 Ajax)lth1gt

ltform id=rdquosignuprdquo action=rdquoSignuprdquo method=rdquoPOSTrdquogt

ltlabelgt 使用者名稱ltlabelgtltinput type=rdquotextrdquo id=rdquousernamerdquo name=rdquousernamerdquo gtltbrgt

ltlabelgt 電子郵件ltlabelgtltinput type=rdquotextrdquo id=rdquoemailrdquo name=rdquoemailrdquo gtltbrgt

ltinput type=rdquosubmitrdquo value=rdquo 註冊我的帳號rdquo gtltbrgt

ltformgt

ltdiv id=rdquoresultrdquogt

ltdivgt

ltscript type=rdquotextjavascriptrdquogt

$(function()$(rsquosignup input[type=rdquosubmitrdquo]rsquo)click(function(e)

var user =

userusername = $(rdquousernamerdquo)val()useremail = $(rdquoemailrdquo)val()

$post(rdquoSignuprdquouserfunction(data)greet(data)

)

68 Express AJAX應用範例 63

Nodejs中文電子書

epreventDefault()

)

var greet = function(msg)

var resultNode = $(rsquoresultrsquo)greeting = $(rsquolth2gtHirsquo+msgusername+rsquo 你的會員 id 是rsquo+ msgid +rsquo 我們會將會員啟動信寄至rsquo+msgemail+rsquolth2gtrsquo)

$(rsquosignuprsquo)hide()resultNodehtml(rdquordquo)

resultNodeappend(greeting)

)

ltscriptgt

ltbodygt

lthtmlgt

HTML頁面上準備了一個表單用來傳送註冊資料接著直接引用了Google CDN來載入 jQuery用來幫我們處理 Ajax的工作這次要傳送和接收的工作很大的變動都在 HTML頁面上的 JavaScript當中我們要做的事有 (相關 jQuery處理這邊不多做贅述指提起主要功能解說)

bull 用 jQUery取得 submit按鈕綁定它的 click動作

bull 取得表單 username和 email的值存放在 user這個物件中

bull 用 jQuery的 $post方法將 user的資料傳到 Server

bull 一旦成功取得資料後透過 greet這個 function組成回報給 user的訊息

bull 清空原本給使用者填資料的表單

bull 將 Server回傳的 usernameemail和 id這 3個資料組成回應的訊息

bull 將訊息放到原本表單的位置

經過以上的處理後一個 Ajax的表單的基本功能已經完成

接著進行 nodejs主要程式的編輯部分程式碼如下

var fs = require(rdquofsrdquo)

64 6 Express介紹

Nodejs中文電子書

qs = require(rsquoquerystringrsquo)

if (action === rdquoSignuprdquo)

formData = rsquorsquo

reqon(rdquodatardquo function (data)

formData += data

)

reqon(rdquoendrdquo function ()

var msg

user = qsparse(formData)

userid = rdquo123456rdquo

msg = JSONstringify(user)

reswriteHead(200 rdquoContent-Typerdquordquoapplicationjson charset=utf-8rdquordquoContent-Lengthrdquomsglength)

resend(msg)

)

這裡的程式和前面 POST範例基本上大同小異差別在

bull 幫 user的資料加上 id隨意存放一些文字進去讓 Server回傳的資料多於 Client端傳上來的不然會覺得 Server都沒做事

bull 增加了 msg 這個變數存放將 user 物件 JSON 文字化的結果JSONstringify這個轉換函式是 V8引擎所提供的如果你好奇的話

bull 大重點來了我們要告訴 Client端這次回傳的資料格式是 JSON所在 Content-type和 Content-Length要提供給 Client

Server很輕鬆就完成任務了最後進行程式測試啟動 nodejs主程式之後開啟瀏覽器就會看到表單填寫完畢按下送出就可以看到結果了

最後 nodejs本篇範例程式碼如下

var http = require(rsquohttprsquo)

url = require(rsquourlrsquo)

fs = require(rdquofsrdquo)

qs = require(rsquoquerystringrsquo)

server

68 Express AJAX應用範例 65

Nodejs中文電子書

server = httpcreateServer(function (reqres)

var urlData

encode = rdquoutf8rdquo

filePath = rdquoviewexpress_ajax_example_formhtmlrdquo

formData

action

urlData = urlparse(requrltrue)

action = urlDatapathname

if (action === rdquoSignuprdquo)

formData = rsquorsquo

reqon(rdquodatardquo function (data)

formData += data

)

reqon(rdquoendrdquo function ()

var msg

user = qsparse(formData)

userid = rdquo123456rdquo

msg = JSONstringify(user)

reswriteHead(200 rdquoContent-TyperdquordquoapplicationjsonrdquordquoContent-Lengthrdquomsglength)

resend(msg)

)

else

fsreadFile(filePath encode function(err file)

reswriteHead(200 rdquoContent-Typerdquordquotexthtml charset=utf-8rdquo)

reswrite(file)

resend()

)

)

serverlisten(3000)

66 6 Express介紹

Nodejs中文電子書

consolelog(rsquoServer 跑起來了現在時間是rsquo + new Date())

69 原始資料提供

bull [NodeJS初學者筆記 (1)-用 GET傳送資料] (httpithelpithomecomtwquestion10087402)

bull [NodeJS初學者筆記 (2)-用 POST傳送資料] (httpithelpithomecomtwquestion10087489)

bull [NodeJS 初學者筆記 (3)-用 Ajax 傳送資料] (httpithelpithomecomtwquestion10087627)

69 原始資料提供 67

Nodejs中文電子書

68 6 Express介紹

7

CoffeeScript

bull Spine

bull Hem

bull Spineapp

Letrsquos Have a Cup of CoffeeScript

bull es5-shim ECMAScript 5 compatibility shims for legacy JavaScript engines

ESnext

node-iform - A middleware for node-validator httpsgithubcomguileennode-iform

69

Nodejs中文電子書

70 7 CoffeeScript

8

製作一個 Hubot的 Plurk Adapter

81 應用事項提醒

此應用範例以CoffeeScript編寫主要程式架構採用Github釋放的Hubot專案以下有幾個主要注意事項請讀者注意

bull Hubot 一開始的架構除了內建的兩個 Adapter 之外其餘都要以Nodejs的Module方式才能運作

bull Hubot的 binhubot裡面寫著 npm install所以不管你怎麼改原始碼也不能改變第一點的狀況

其實上述都在討論同一件事情NodeJS的模組而且模組不能設定相對路徑之類的來安裝一定要包裝成 npm相符和格式才有辦法正確執行

82 建立 Adapter

首先需要準備兩個檔案

bull packagejson

bull plurkcoffee

71

Nodejs中文電子書

有這兩個就足以變成 NodeJS的模組了在開始 Coding之前先來設定一下 packagejson相依性

rdquonamerdquo rdquohubot-plurkrdquo

rdquoversionrdquo rdquo011rdquo

rdquomainrdquo rdquoplurkrdquo

rdquodependenciesrdquo

rdquohubotrdquo rdquogt=205rdquo

rdquooauthrdquo rdquordquo

rdquocronrdquordquordquo

這邊會使用到NodeJS的 cron模組(Module)而登入 Plurk需要OAuth標準授權才行所以也需要加載 OAuth模組

注意Hubot在讀取非內建模組時會自動在前面加上 hubot-的前置

83 建立 Robot跟 API

本篇應用基本上程式碼大部分參考 Hubot - Twitter Adapter來製作主要差異只有在使用 OAuth這個模組Twitter有 Streaming可用而 Plurk則得用 Comet方式來達到即時讀取

plurkcoffee程式碼

Robot = require(rdquohubotrdquo)robot()

Adapter = require(rdquohubtordquo)adapter()

EventEmitter = require(rdquoeventsrdquo)EventEmitter

oauth = require(rdquooauthrdquo)

cronJob = require(rdquocronrdquo)CronJob

class Plurk exntends Adapter

class PlurkStreaming exnteds EventEmitter

72 8 製作一個 Hubot的 Plurk Adapter

Nodejs中文電子書

先弄個基本架構主要 Class皆參考 Twitter Adapter命名方式接著再將 Plurk這個 Class增加幾個Method基本上只要有 run send reply就夠了而 run用來做初始化的部分

class Plurk entends Adapter

send (plurk id strings⋯) -gt

reply (plurk id strings⋯) -gt

run -gt

看起來有點東西接著來處理主要的 Plurk API結合部分

class PlurkStreaming extends EventEmitter

consuctor (options) -gt

plurk (callback) -gt

觀察河道

getChannel -gt

取得 Comet 網址

reply (plurk id message) -gt

回噗

acceptFriends -gt

接受好友

get (path callback) -gt

GET 請求

post (path body callback)-gt

POST 請求(其實是裝飾)

request (method path body callback)-gt

主要的 OAuth 請求

comet (server callback)-gt

噗浪的 Comet 傳回是 JavaScript Callback 要另外處理後才會變成 JSON

然後把注意力集中到 constructor上先把建構子弄好

constructor (options) -gt

super()

if optionskey and optionssecret and optionstoken and optionstoken secret

key = optionskey

secret = optionssecret

token = optionstoken

token secret = optionstoken secret

83 建立 Robot跟 API 73

Nodejs中文電子書

建立 OAuth 連接

consumer = new oauthOAuth(

rdquohttpwwwplurkcomOAuthrequest tokenrdquo

rdquohttpwwwplurkcomOAuthaccess tokenrdquo

key

secret

rdquo10rdquo

rdquohttpwwwplurkcomOAuthauthorizerdquo

rdquoHMAC-SHA1rdquo

)

domain = rdquowwwplurkcomrdquo

初始化取得 Comet 網址

do getChannel

else

throw new Error(rdquo 參數不足需要 Key Secret Token Token Secretrdquo)

接著來處理 request這個 method

request (method path body callback) -gt

記錄一下這次的 Request

consolelog(rdquohttpdomainpathrdquo)

Callback 這邊先不丟進去要用另一種方式處理

request = consumerget(rdquohttpdomainpathrdquo token token secret null)

requeston rdquoresponserdquo (res) -gt

reson rdquodatardquo (chunk) -gt

parseResponse(chunk+rsquorsquo callback)

reson rdquoendrdquo (data) -gt

consolelog rdquoEnd Request pathrdquo

reson rdquoerrorrdquo (data) -gt

consolelog rdquoError rdquo + data

requestend()

處理資料

parseResponse = (data callback) -gt

if datalength gt 0

用 TryCatch 避免處理 JSON 出錯導致整個中斷

try

callback null JSONparse(data)

catch err

74 8 製作一個 Hubot的 Plurk Adapter

Nodejs中文電子書

consolelog(rdquoError Parse JSONrdquo + data err)

繼續執行

callback null data ||

大致上就是這樣上面程式的架構已經將整個 Hubot Plurk Adapter完成因為在測試時竟然因為噗浪 Lag而沒讀到完整的 Comet資料然後造成程式異常為了避免這個問題發生因此需要加上為了完美呈現需要再

加上 Comet的處理所以要使用到 EventEmitter的功能

comet (server callback) -gt

在 Callback 裡面會找不到自身所以設定區域變數

self =

記錄一下這次的 Request

consolelog(rdquo[Comet] serverrdquo)

Callback 這邊先不丟進去要用另一種方式處理

request = consumerget(rdquohttpdomainpathrdquo token token secret null)

requeston rdquoresponserdquo (res) -gt

reson rdquodatardquo (chunk) -gt

parseResponse(chunk+rsquorsquo callback)

reson rdquoendrdquo (data) -gt

consolelog rdquoEnd Request pathrdquo

請求結束發出事件通知可以進行下一次請求

selfemit rdquonextPlurkrdquo

reson rdquoerrorrdquo (data) -gt

consolelog rdquoError rdquo + data

requestend()

處理資料

parseResponse = (data callback) -gt

if datalength gt 0

用 trycatch 避免失敗中斷

try

去掉 JavaScript 的 Callback

data = datamatch(CometChannelscriptCallback((+))s)

jsonData = rdquordquo

if data

83 建立 Robot跟 API 75

Nodejs中文電子書

jsonData = JSONparse(data[1])

else

如果沒有任何 Match 嘗試直接 parse

jsonData = JSONparse(data)

catch err

consolelog(rdquo[Comet] Errorrdquo data err)

用 TryCatch 避免處理 JSON 出錯導致整個中斷

try

只傳入 json 的 data 部分

callback null jsonDatadata

catch err

consolelog(rdquo[Comet]Error Parse JSONrdquo + data err)

繼續執行

callback null data ||

後面的 get跟 post就簡單多了

get (path callback) -gt

request(rdquoGETrdquo path null callback)

post (path body callback) -gt

request(rdquoPOSTrdquo path body callback)

接著處理取的 Comet網址的 getChannel

getChannel -gt

self =

get rdquoAPPRealtimegetUserChannelrdquo (error data) -gt

if error

檢查是否有 comet server

if datacomet server

selfchannel = datacomet server

如果沒有 Channel Ready 就嘗試連接會失敗

selfemit(rsquochannel readyrsquo)

那麼先來處理 Plurk Adaper好處理的部份

send (plruk id strings⋯)-gt

跟 Reply 一樣直接交給 reply 做

reply plurk id strings⋯

76 8 製作一個 Hubot的 Plurk Adapter

Nodejs中文電子書

reply (plurk id strings⋯) -gt

stringsforEach (message) =gt

botreply(plruk id message)

接著把 run處理好就可以上線運作摟

run -gt

self =

options =

key processenvHUBOT PLURK KEY

secret processenvHUBOT PLURK SECRET

token processenvHUBOT PLURK TOKEN

token secret processenvHUBOT PLURK TOKEN SECRET

創建剛剛的 API

bot = new PlurkStreaming(options)

依照 Twitter 的 new RobotTextMessage 會沒有反應所以參考 hubot-minecraft 的方式

r = robotconstructor

處理噗浪河道訊息

doPlurk = (data)-gt

檢查是否為回噗

if dataresponse

datacontent raw = dataresponsecontent raw

datauser id = dataresponseuser id

確定有噗浪 ID 跟訊息

if dataplurk id and datacontent raw

selfreceive new rTextMessage(dataplurk id datacontent raw)

取得 Comet Server 完成開始第一次 Comet 連接

boton rdquochannel readyrdquo () -gt

botplurk selfdoPlurk

上一次 Comet 完成繼續 Polling

boton rdquonextPlurkrdquo ()-gt

botplurk selfdoPlurk

定時接受好友邀請

do botacceptFriends

bot = bot

83 建立 Robot跟 API 77

Nodejs中文電子書

終於完成 AdapterHubot專案裡面的 scripts資料夾內是互動部分不需要像 Adapter如此大費周章處理只新增檔案並且設計好對白之後就會回噗了我開發用的機器人在此大家可以去跟他玩玩[httpplurkcomelct9620 bot](httpplurkcomelct9620 bot)

84 原始資料提供

bull [製 作 一 個 Hubot 的 噗 浪 Adapter](http revo-skillfrosttw blog20120318create-a-hubot-plurk-adapter)

78 8 製作一個 Hubot的 Plurk Adapter

9

Nodejs好用工具介紹

91 logio

Real-time log monitoring in your browser

httplogioorg

nodejs (gt=0412 lt=0611)

npm config set unsafe-perm true

npm install -g --prefix=usrlocal logio

logio server start

httplocalhost8998

79

Nodejs中文電子書

80 9 Nodejs好用工具介紹

10

精選文章

101 Express

bull 使用 NodeJS + Express從 GETPOST Request取值 [邀稿中]

bull link2 [邀稿中]

bull link3 [邀稿中]

81

Nodejs中文電子書

82 10 精選文章

11

參考資源

111 Nodejs書籍

bull Programming Nodejs April 2012 (est) Travis Swicegood Pragmatic Bookshelf

bull e Little Book on CoffeeScript January 2012 Alex MacCaw OrsquoReilly Media

bull Node for Front-End Developers January 2012 Garann Means OrsquoReilly Media

bull Building Hypermedia APIs with HTML5 and Node November 2011 MikeAmundsen OrsquoReilly Media

bull Node Web Development August 2011 David Herron PacktPub

bull CoffeeScript Accelerated JavaScript Development July 2011 Trevor BurnhamPragmatic Bookshelf

bull Getting Started with GEO CouchDB and Nodejs July 2011 Mick omp-son OrsquoReilly Media

bull What Is Node July 2011 Brett McLaughlin OrsquoReilly Media

bull Node Up and Running May 2011 Tom Hughes-Croucher Mike WilsonOrsquoReilly Media

83

Nodejs中文電子書

bull Hands-on Nodejs e Nodejs introduction and API reference May 2011 Pe-dro Teixeira LeanPub

bull e Node Beginner Book Manuel Kiessling

bull Mastering Nodejs visionmedia

112 Nodejs影音教學

bull e Node Sessions e Best of OSCON 2011 August 2011 OrsquoReilly Media(Video)

bull Tom Hughes-Croucher on Node March 2011 OrsquoReilly Media (Video)

113 Nodejs教學網站

bull Node Tuts

114 Nodejs課程

bull Introduction To Nodejs Van Nguyen CodeLesson

84 11 參考資源

Page 8: Nodejs Wiki

Nodejs中文電子書

httpcreativecommonsorglicensesby-nc30legalcode

作者

本書由 Nodejs Taiwan社群成員協作以下名單依照字母排序

bull Caesar Chi (clonn)

bull Fillano Feng ( llano)

bull Kyle Lin (lyhcode)

Nodejs Taiwan是一個自由開放的技術學習社群我們歡迎您加入一起學習研究及分享

下載電子書

線上閱讀本書

httpbooknodejstw

PDF格式適合一般電腦及 7吋以上平板電腦閱讀

httpcontpuborgdownloadnodejs-wiki-bookpdf

EPUB格式適合 iPadiPhone行動裝置閱讀

httpcontpuborgdownloadnodejs-wiki-bookepub

MOBI格式適合 Kindle電子書閱讀器

httpcontpuborgdownloadnodejs-wiki-bookmobi

原始碼

本書最新的原始碼(中文版)網址如下

httpgithubcomnodejs-twnodejs-wiki-book

2 關於本書

Nodejs中文電子書

01 精選文章收錄流程

精選文章的用意是鼓勵作者在自已的網誌發表 Nodejs 教學再由Nodejs Taiwan社群挑選系列文章列入電子書的精選文集

1 將文章標題及連結貼到「精選文章」分類

2 社群工作小組以 E-Mail通知作者文章列入精選並邀請將內文授權給Nodejs Taiwan電子書分享

3 作者同意後由工作小組負責整理圖文發佈至電子書

4 以 E-Mail寄出感謝函通知原作者文章已收錄依作者意願調整文章內容

5 於 Nodejs Taiwan首頁及粉絲專頁推薦作者的文章

01 精選文章收錄流程 3

Nodejs中文電子書

4 關於本書

前言

Nodejs是 JavaScript程式語言的開發框架由 04x至目前 v060版本核心上已經有許多變更當然目的只有一個就是讓開發變得更簡單速

度能夠更快這是所有人奮鬥的目標而 nodeJS 華文維基平台主力由nodeJStw一群喜好 javascript開發者共同主筆內文以中文為主希望能夠降低開發者學習門檻藉由我們拋磚引玉讓更多華人開發者共同學習

討論

CoffeeScript好好玩

5

Nodejs中文電子書

6 前言

1

Nodejs簡介

Nodejs是一個高效能易擴充的網站應用程式開發框架 (Web Applica-tion Framework)它誕生的原因是為了讓開發者能夠更容易開發高延展性的網路服務不需要經過太多複雜的調校效能調整及程式修改就能

滿足網路服務在不同發展階段對效能的要求

Ryan Dahl是NodeJS的催生者目前任職於 Joyent (主機託管服務公司)他開發 NodeJS的目的就是希望能解決 Apache在連線數量過高時緩衝區 (buffer)和系統資源會很快被耗盡的問題希望能建立一個新的開發框架以解決這個問題因此嘗試使用效能十分優秀的 V8 JavaScript Engine讓網站開發人員熟悉的 JavaScript程式語言也能應用於後端服務程式的開發並且具有出色的執行效能

JavaScript是功能強大的物件導向程式語言但是在 JavaScript的官方規格中主要是定義網頁 (以瀏覽器為基礎) 應用程式需要的應用程式介面(API)對 JavaScript程式的應用範圍有所侷限為使 JavaScript能夠在更多用途發展CommonJS規範一組標準函式庫 (standard library)使 JavaScript的應用範圍能夠和 RubyPython及 Java等語言同樣豐富撰寫 NodeJS的JavaScript程式碼符合 CommonJS規範可以使用 CommonJS API為基礎開發程式並且在不同的 CommonJS兼容 (compliant) JavaScript執行環境中程式碼具有可攜性

7

Nodejs中文電子書

瀏覽器的 JavaScript與實現 CommonJS規範的 NodeJS有何不同呢瀏覽器的 JavaScript 提供 XMLHttpRequest 讓程式可以和網頁伺服器建立資料傳輸連線但這通常只能適用於網站開發的需求因為我們只能用

XMLHttpRequest與網頁伺服器通訊卻無法利用它建立其他類型如 Telnet FTP NTP的伺服器通訊如果我們想開發網路服務程式例如 SMTP電子郵件伺服器就必須使用 Sockets建立 TCP (某些服務則用 UDP)監聽及連線其他程式語言如 PHPJavaPythonPerl及 Ruby等在標準開發環境中皆有提供 Sockets API而瀏覽器的 JavaScript基於安全及貼近網站設計需求的考量下並未將 Sockets列入標準函式庫之中CommonJS的規範填補這種基礎函式庫功能的空缺遵循 CommonJS規範的 NodeJS可以直接使用 Sockets API建立各種網路服務程式

JavaScript語言本身支援 Lambda的特性因此一個匿名函式 (anonymousfunction)可以被儲存成一個變數並當作參數傳遞給另一個函式

var proc1 = function(op x) return op(x)

var op1 = function(x) return x+1 var op2 = function(x) return xx

proc1(op1 3) result is 4 proc1(op2 5) result is 25

另一個 JavaScript開發者必須掌握的語言特性稱為 Closure

NodeJS符合 CommonJS的規範使得 Callback方式易於實現也能夠讓更多同好基於 JavaScript開發符合 NodeJS的外掛模組 (Module)

回想以前要寫一個能夠同時容納上百人的上線的網路服務需要花費

多大的苦工可能 10人多就需要經過一次程式調整而 NodeJS就是為了解決這個困境NodeJS因此誕生它是一種利用 V8 Javascript編譯器所開發的產品利用 V8編譯器的高效能與 Javascript的程式開發特性所產生的網路程式

開發人員所編寫出來的 Javascript腳本程式怎麼可能會比其他語言寫出來的網路程式還要快上許多呢以前的網路程式原理是將使用者每次的

連線 (connection)都開啟一個執行緒 (thread)當連線爆增的時候將會快速耗盡系統效能並且容易產生阻塞 (block)的發生

8 1 Nodejs簡介

Nodejs中文電子書

NodeJS對於資源的調校有所不同當程式接收到一筆連線 (connection)會通知作業系統透過 epoll kqueue devpoll或 select將連線保留並且放入heap中配置先讓連線進入休眠 (Sleep)狀態當系統通知時才會觸發連線的 callback這種處理連線方式只會佔用掉記憶體並不會使用到 CPU資源另外因為採用 Javascript語言的特性每個 request都會有一個 callback如此可以避免發生 Block的狀況發生

基於 Callback特性目前NodeJS大多應用於 Comet(long pulling) RequestServer或者是高連線數量的網路服務上目前也有許多公司將 NodeJS設為內部核心網路服務之一在 NodeJS也提供了外掛管理 (Node packagemanagement)讓愛好 NodeJS輕易開發更多有趣的服務外掛並且提供到 npm讓全世界使用者快速安裝使用

本書最後執行測試版本為 nodejs v068相關 API文件可查詢 lsquohttpnodejsorg lthttpnodejsorggtlsquo本書所有範例均可於 linux windows上執行如遇到任何問題歡迎至 lsquohttpnodejstw lthttpnodejstwgtlsquo詢問對於nodejs相關問題

9

Nodejs中文電子書

10 1 Nodejs簡介

2

JavaScript與 NodeJS

其實使用 JavaScript 在網頁端與伺服器端的差距並不大但是為了使NodeJS可以發揮他最強大的能力有一些知識還是必要的所以還是針對這些主題介紹一下其中 Event LoopScope以及 Callback其實是比較需要了解的基本知識cpscurrying ow control是更進階的技巧與應用

21 Event Loop

可能很多人在寫 Javascript 時並不知道他是怎麼被執行的這個時候可以參考一下 jQuery作者 John Resig一篇好文章介紹事件及 timer怎麼在瀏覽器中執行How JavaScript Timers Work通常在網頁中所有的Javascript執行完畢後(這部份全部都在 global scope跑除非執行函數)接下來就是如 John Resig解釋的這樣所有的事件處理函數以及 timer執行的函數會排在一個 queue結構中利用一個無窮迴圈不斷從 queue中取出函數來執行這個就是 event loop

(除了 John Resig的那篇文章Nicholas C Zakas的 ldquoProfessional Javascriptfor Web Developer 2nd editionrdquo 有一個試閱本httpyuiblogcomassetspdfzakas-projs-2ed-ch18pdf598頁剛好也有簡短的說明)

所以在 Javascript中雖然有非同步但是他並不是使用執行緒所有

11

Nodejs中文電子書

的事件或是非同步執行的函數都是在同一個執行緒中利用 event loop的方式在執行至於一些比較慢的動作例如 IO網頁 render re ow等實際動作會在其他執行緒跑等到有結果時才利用事件來觸發處理函數來處理

這樣的模型有幾個好處沒有執行緒的額外成本所以反應速度很快不會

有任何程式同時用到同一個變數不必考慮 lock也不會產生 dead lock所以程式撰寫很簡單但是也有一些潛在問題任一個函數執行時間較長都

會讓其他函數更慢執行(因為一個跑完才會跑另一個)在多核心硬體普遍

的現在無法用單一的應用程式 instance發揮所有的硬體能力用 NodeJS撰寫伺服器程式碰到的也是一樣的狀況要讓系統發揮 event loop的效能就要盡量利用事件的方式來組織程式架構另外對於一些有可能較為耗

時的操作可以考慮使用 processnextTick函數來讓他以非同步的方式執行避免在同一個函數中執行太久擋住所有函數的執行

如果想要測試 event loop怎樣在「瀏覽器」中運行可以在函數中呼叫alert()這樣會讓所有 Javascript的執行停下來尤其會干擾所有使用 timer的函數執行有一個簡單的例子這是一個會依照設定的時間間隔嚴格執

行動作的動畫如果時間過了就會跳過要執行的動作點按圖片以後人

物會快速旋轉但是在旋轉執行完畢前按下「delay」按鈕讓 alert訊息等久一點接下來的動畫就完全不會出現了

22 Scope與 Closure

要快速理解 JavaScript的 Scope(變數作用範圍)原理只要記住他是Lexical Scope就差不多了簡單地說變數作用範圍是依照程式定義時(或者叫做程式文本)的上下文決定而不是執行時的上下文決定

為了維護程式執行時所依賴的變數即使執行時程式運行在原本的

scope之外他的變數作用範圍仍然維持不變這時程式依賴的自由變數(定義時不是 local的而是在上一層 scope定義的變數)一樣可以使用就好像被關閉起來所以叫做 Closure用程式看比較好懂

function outter(arg1)

arg1 及 free_variable1 對 inner 函數來說都是自由變數

var free_variable1 = 3

return function inner(arg2)

12 2 JavaScript與 NodeJS

Nodejs中文電子書

var local_variable1 =2arg2 及 local_variable1 對 inner 函數來說都是本地變數

return arg1 + arg2 + free_variable + local_variable1

var a = outter(1)變數 a就是 outter函數執行後返回的 inner函數 var b =a(4)執行 inner函數執行時上下文已經在 outter函數之外但是仍然能正常執行而且可以使用定義在 outter函數裡面的 arg1及 free variable1變數 consolelog(b)結果 10

在 Javascript中scope最主要的單位是函數(另外有 global及 eval)所以有可能製造出 closure的狀況通常在形式上都是有巢狀的函數定義而且內側的函數使用到定義在外側函數裡面的變數

Closure有可能會造成記憶體洩漏主要是因為被參考的變數無法被垃圾收集機制處理造成佔用的資源無法釋放所以使用上必須考慮清楚

不要造成意外的記憶體洩漏(在上面的例子中如果 a一直未執行使用到的記憶體就不會被釋放)

跟透過函數的參數把變數傳給函數比較起來Javascript Engine會比較難對 Closure進行最佳化如果有效能上的考量這一點也需要注意

23 Callback

要介紹 Callback之前要先提到 JavaScript的特色

JavaScript是一種函數式語言(functional language)所有 Javascript語言內的函數都是高階函數 (higher order function這是數學名詞計算機用語好像是 rst class function意指函數使用沒有任何限制與其他物件一樣)也就是說函數可以作為函數的參數傳給函數也可以當作函數的返回值這個特性讓 Javascript的函數使用上非常有彈性而且功能強大

callback在形式上其實就是把函數傳給函數然後在適當的時機呼叫傳入的函數Javascript使用的事件系統通常就是使用這種形式NodeJS中有一個物件叫做 EventEmitter這是 NodeJS事件處理的核心物件所有會使用事件處理的函數都會「繼承」這個物件(這裡說的繼承實

作上應該像是 mixin)他的使用很簡單可以使用物件on(事件名稱callback

23 Callback 13

Nodejs中文電子書

函數) 或是物件addListener(事件名稱callback 函數) 把你想要處理事件的函數傳入在物件中可以使用物件emit(事件名稱 參數) 呼叫傳入的callback函數這是 Observer Pattern的簡單實作而且跟在網頁中使用 DOM的 addEventListener使用上很類似也很容易上手不過 NodeJS是大量使用非同步方式執行的應用所以程式邏輯幾乎都是寫在 callback函數中當邏輯比較複雜時大量的 callback會讓程式看起來很複雜也比較難單元測試舉例來說

var p client = new Db(lsquointegration tests 20rsquo new Server(ldquo127001rdquo 27017) lsquopkrsquoCustomPKFactory) p clientopen(function(err p client)

p clientdropDatabase(function(err done)

p clientcreateCollection(lsquotest custom keyrsquo function(err collection)

collectioninsert(lsquoarsquo1 function(err docs)

collection nd(lsquo idrsquonew ObjectID(ldquoaaaaaaaaaaaardquo) function(err cursor)

cursortoArray(function(err items) testassertEquals(1 itemslength) p clientclose()

)

)

)

)

)

)

這是在網路上看到的一段操作 mongodb的程式碼為了循序操作所以必須在一個 callback裡面呼叫下一個動作要使用的函數這個函數裡面還是會使用 callback最後就形成一個非常深的巢狀

這樣的程式碼會比較難進行單元測試有一個簡單的解決方式是

盡量不要使用匿名函數來當作 callback或是 event handler透過這樣的方式

14 2 JavaScript與 NodeJS

Nodejs中文電子書

就可以對各個 handler做單元測試了例如

var http = require(lsquohttprsquo) var tools =

cookieParser function(request response) if(requestheaders[rsquoCookiersquo]) do parsing

var server = httpcreateServer(function(request response)

thisemit(lsquoinitrsquo request response)

) serveron(lsquoinitrsquo toolscookieParser) serverlisten(8080 lsquo127001rsquo)

更進一步可以把 tools改成外部 module例如叫做 toolsjs

moduleexports = cookieParser function(request response) if(requestheaders[rsquoCookiersquo]) do parsing

然後把程式改成

var http = require(lsquohttprsquo)

var server = httpcreateServer(function(request response) thisemit(lsquoinitrsquo re-quest response)

) serveron(lsquoinitrsquo require(lsquo toolsrsquo)cookieParser) serverlisten(8080lsquo127001rsquo)

這樣就可以單元測試 cookieParser了例如使用 nodeunit時可以這樣寫

var testCase = require(lsquonodeunitrsquo)testCase moduleexports = testCase(

ldquosetUprdquo function(cb) thisrequest = headers Cookielsquoname1val1 name2val2rsquo thisresponse = thisresult= name1rsquoval1rsquoname2rsquoval2rsquo

cb()

ldquotearDownrdquo function(cb)

cb()

23 Callback 15

Nodejs中文電子書

ldquonormal caserdquo function(test)

testexpect(1) var obj = require(lsquotoolsrsquo)cookieParser(thisrequest thisresponse)testdeepEqual(obj thisresult) testdone()

)

善於利用模組可以讓程式更好維護與測試

24 CPS(Continuation-Passing Style)

cps 是 callback 使用上的特例形式上就是在函數最後呼叫 callback這樣就好像把函數執行後把結果交給 callback 繼續運行所以稱作continuation-passing style利用 cps可以在非同步執行的情況下透過傳給 callback的這個 cps callback來獲知 callback執行完畢或是取得執行結果例如

lthtmlgt ltbodygt ltdiv id=rdquopanelrdquo style=rdquovisibilityhiddenrdquogtlt divgtlt bodygt lt htmlgt ltscriptgt var request = new XMLHttpRequest() re-questopen(lsquoGETrsquo lsquotest749txt timestamp=rsquo+new Date()getTime() true) re-questaddEventListener(lsquoreadystatechangersquo function(next)

return function() if(thisreadyState===4ampampthisstatus===200) next(thisresponseText)lt== 傳入的 cps callback 在動作完成時執行並取得結果進一步處理

(function(str)lt==這個匿名函數就是 cps callback

documentgetElementById(lsquopanelrsquo)innerHTML=str docu-mentgetElementById(lsquopanelrsquo)stylevisibility = lsquovisiblersquo

) false) requestsend() ltscriptgt

進一步的應用也可以參考 2-6流程控制

16 2 JavaScript與 NodeJS

Nodejs中文電子書

25 函數返回函數與 Currying

前面的 cps範例裡面使用了函數返回函數這是為了把 cps callback傳遞給 onreadystatechange事件處理函數的方法(因為這個事件處理函數並沒有設計好會傳送接收這樣的參數)實際會執行的事件處理函數其實是內層返回的那個函數之外包覆的這個函數主要是為了利用 Closure把 next傳給內層的事件處理函數這個方法更常使用的地方是為了解決一些

scope問題例如

ltscriptgt var accu=0count=10 for(var i=0 iltcount i++)

setTimeout(

function() countndash accu+=i if(countlt=0)

consolelog(accu)

50)

ltscriptgt

最後得出的結果會是 100而不是想像中的 45這是因為等到 setTime-out指定的函數執行時變數 i已經變成 10而離開迴圈了要解決這個問題就需要透過 Closure來保存變數 i

ltscriptgt var accu=0count=10 for(var i=0 iltcount i++)

setTimeout(

function(i) return function() countndash

accu+=i if(countlt=0)

consolelog(accu)

(i)

50)

25 函數返回函數與 Currying 17

Nodejs中文電子書

淺藍色底色的部份是跟上面例子不一樣的地方 ltscriptgt

函數返回函數的另外一個用途是可以暫緩函數執行例如

function add(m n) return m+n

var a = add(20 10) consolelog(a)

add這個函數必須同時輸入兩個參數才有辦法執行如果我希望這個函數可以先給它一個參數等一些處理過後再給一個參數然後得到結

果就必須用函數返回函數的方式做修改

function add(m)

return function(n) return m+n

var wait another arg = add(20)先給一個參數 var a = function(arr)

var ret=0 for(var i=0iltarrlengthi++) ret+=arr[i] return ret

([1234])計算一下另一個參數 var b = wait another arg(a)然後再繼續執行 consolelog(b)

像這樣利用函數返回函數使得原本接受多個參數的函數可以一次

接受一個參數直到參數接收完成才執行得到結果的方式有一個學名就

叫做Currying

綜合以上許多奇技淫巧就可以透過用函數來處理函數的方式調整

程式流程接下來看看

26 流程控制

(以 sync方式使用 async函數避開巢狀 callback循序呼叫 async callback等奇技淫巧)

建議參考

bull httphowtonodeorgcontrol- ow

bull httphowtonodeorgcontrol- ow-part-ii

18 2 JavaScript與 NodeJS

Nodejs中文電子書

bull httphowtonodeorgcontrol- ow-part-iii

bull httpblogmixunet20110202essential-node-js-patterns-and-snippets

這幾篇都是非常經典的 NodeJSJavascript流程控制好文章(阿mixu是在介紹一些 pattern時提到這方面的主題)不過我還是用幾個簡單的程式介紹一下做法跟概念

並發與等待

下面的程式參考了 mixu文章中的做法

var wait = function(callbacks done) consolelog(lsquowait startrsquo) var counter = call-backslength var results = [] var next = function(result) 接收函數執行結果並判斷是否結束執行 resultspush(result) if(ndashcounter == 0) done(results)如果結束執行就把所有執行結果傳給指定的 callback處理 for(var i = 0 i lt callbackslength i++) 依次呼叫所有要執行的函數 callbacks[i](next) consolelog(lsquowait endrsquo)

wait( [ function(next) setTimeout(function() consolelog(lsquodone arsquo) var result =500 next(result) 500) function(next) setTimeout(function() con-solelog(lsquodone brsquo) var result = 1000 next(result) 1000) function(next)setTimeout(function() consolelog(lsquodone crsquo) var result = 1500 next(1500) 1500) ] function(results) var ret = 0 i=0 for( iltresultslength i++) ret+= results[i] consolelog(lsquodone all result lsquo+ret)

)

執行結果wait start wait end done a done b done c done all result 3000

可以看出來其實 wait並不是真的等到所有函數執行完才結束執行而是在所有傳給他的函數執行完畢後(不論同步非同步)才執行處理結

果的函數(也就是 done())

不過這樣的寫法還不夠實用因為沒辦法實際讓函數可以等待執行

完畢又能當作事件處理函數來實際使用上面參考到的 Tim Caswell的文

26 流程控制 19

Nodejs中文電子書

章裡面有一種解法不過還需要額外包裝(在他的例子中)NodeJS核心的 fs物件把一些函數(例如 readFile)用 Currying處理類似像這樣

var fs = require(lsquofsrsquo) var readFile = function(path)

return function(callback errback)

fsreadFile(path function(err data)

if(err) errback()

else callback(data)

)

其他部份可以參考 Tim Caswell的文章他的 Doparallel跟上面的 wait差不多意思這裡只提示一下他沒說到的地方

另外一種做法是去修飾一下 callback當他作為事件處理函數執行後再用 cps的方式取得結果

ltscriptgt function Wait(fns done)

var count = 0 var results = [] thisgetCallback = function(index)

count++ return (function(waitback)

return function() var i=0args=[]for(iltargumentslengthi++)

argspush(arguments[i])

argspush(waitback) fns[index]apply(thisargs)

)(function(result) resultspush(result) if(ndashcount == 0)

20 2 JavaScript與 NodeJS

Nodejs中文電子書

done(results)

)

var a = new Wait(

[ function(waitback) consolelog(lsquodone arsquo) var result = 500 wait-back(result) function(waitback) consolelog(lsquodone brsquo) var result =1000 waitback(result) function(waitback) consolelog(lsquodone crsquo) varresult = 1500 waitback(result) ] function(results) var ret = 0 i=0for( iltresultslength i++) ret += results[i] consolelog(lsquodone allresult lsquo+ret)

) var callbacks = [agetCallback(0)agetCallback(1)agetCallback(0)agetCallback(2)]一次取出要使用的 callbacks避免結果提早送出 setTimeout(callbacks[0]500) setTimeout(callbacks[1] 1000) setTimeout(callbacks[2] 1500) setTime-out(callbacks[3] 2000) 當所有取出的 callbacks執行完畢就呼叫 done()來處理結果 ltscriptgt

執行結果

done a done b done a done c done all result 3500

上面只是一些小實驗更成熟的作品是 Tim Caswell 的 stephttpsgithubcomcreationixstep

如果希望真正使用同步的方式寫非同步則需要使用 Promisejs這一類的 library來轉換非同步函數不過他結構比較複雜 XD(見仁見智不過有些人認為 Promise有點過頭了)httpblogsmsdncombrbucktonarchive20110815promise-js-2-0-promise-framework-for-javascriptaspx

如果想不透過其他 Library做轉換又能直接用同步方式執行非同步函數大概就要使用一些需要額外 compile原始程式碼的方法了例如 BrunoJouhier的 streamlinejshttpsgithubcomSagestreamlinejs

26 流程控制 21

Nodejs中文電子書

循序執行

循序執行可以協助把非常深的巢狀 callback結構攤平例如用這樣的簡單模組來做(serialjs)

moduleexports = function(funs) var c = 0 if(isArrayOfFunctions(funs))

throw(lsquoArgument type was not matched Should be array of func-tionsrsquo)

return function()

var args = Arrayprototypeslicecall(arguments 0) if((cgt=funslength))

c++ return funs[c-1]apply(this args)

function isArrayOfFunctions(f ) if(typeof f == lsquoobjectrsquo) return false if(flength)return false if(fconcat) return false if(fsplice) return false var i = 0 for(iltflength i++)

if(typeof f[i] == lsquofunctionrsquo) return false

return true

簡單的測試範例(testSerialjs)使用 fs 模組確定某個 path 是檔案然後讀取印出檔案內容這樣會用到兩層的 callback所以測試中有使用serial的版本與 nested callbacks的版本做對照

var serial = require(lsquoserialrsquo) fs = require(lsquofsrsquo) path = lsquodclientjsrsquo cb = serial([ func-tion(err data)

if(err)

if(dataisFile) fsreadFile(path cb)

22 2 JavaScript與 NodeJS

Nodejs中文電子書

else consolelog(err)

function(err data)

if(err) consolelog(lsquo[ attened by searial]rsquo) con-solelog(datatoString(lsquoutf8rsquo))

else consolelog(err)

]) fsstat(path cb)

fsstat(path function(err data) 第一層 callback if(err)

if(dataisFile)

fsreadFile(path function(err data) 第二層 callback if(err)

consolelog(lsquo[nested callbacks]rsquo) con-solelog(datatoString(lsquoutf8rsquo))

else consolelog(err)

)

else consolelog(err)

)

關鍵在於這些 callback的執行是有順序性的所以利用 serial返回的一個函數 cb來取代這些 callback然後在 cb中控制每次會循序呼叫的函數

26 流程控制 23

Nodejs中文電子書

就可以把巢狀的 callback攤平成循序的 function陣列(就是傳給 serial函數的參數)

測試中的dclientjs 是一個簡單的 dnode 測試程式放在跟 testSerialjs同一個目錄

var dnode = require(lsquodnodersquo)

dnodeconnect(8000 lsquolocalhostrsquo function(remote)

remoterestart(function(str) consolelog(str) processexit()

)

)

執行測試程式後出現結果

[ attened by searial] var dnode = require(lsquodnodersquo)

dnodeconnect(8000 lsquolocalhostrsquo function(remote)

remoterestart(function(str) consolelog(str) processexit()

)

)

[nested callbacks] var dnode = require(lsquodnodersquo)

dnodeconnect(8000 lsquolocalhostrsquo function(remote)

remoterestart(function(str) consolelog(str) processexit()

)

)

對照起來看兩種寫法的結果其實是一樣的但是利用 serialjs巢狀的 callback結構就會消失

不過這樣也只限於順序單純的狀況如果函數執行的順序比較複雜

(不只是一直線)還是需要用功能更完整的流程控制模組比較好例如

httpsgithubcomcaolanasync

24 2 JavaScript與 NodeJS

3

Nodejs安裝與設定

本篇將講解如何在各個不同 OS建立 NodeJS環境目前 NodeJS 048版本環境架設方式需依賴 Linux指令才可編譯完成當然在不同作業系統中也已經有 NodeJS package可以直接使用指令快速架設以下各不同作業系統解說如何安裝 NodeJS

31 Ubuntu Linux

更新推薦使用 nvm

git clone gitgithubcomcreationixnvmgit ~nvm

echo rdquo ~nvmnvmshrdquo gtgt ~bashrc

nvm install v0614

nvm alias default v0614

以 上 可 參 考 http dreamerslabcom blog tw how-to-setup-a-node-js-development-environment-on-ubuntu-11-04

使用 APT套件管理工具是常見的方法以下是使用社群提供的 PPA安裝方式

sudo apt-get install python-software-properties

sudo add-apt-repository ppachris-leanodejs-devel

25

Nodejs中文電子書

sudo apt-get update

sudo apt-get install nodejs

32 Other Linux

Linux很適合作為 NodeJS的伺服器作業系統及開發環境安裝前請先確認以下套件已正確安裝

bull curl (wget)用來下載檔案的工具

bull git先進的版本控制工具

bull g++ GNU C++軟體編譯工具

bull make GNU軟體專案建置工具

安裝指令如下如設有權限問題請在指令前面加上 sudo

git clone httpsgithubcomjoyentnodegit

cd node

git checkout v067

configure

make

sudo make install

接著測試 nodeJS是否正常執行

node --version

出現版本訊息即表示安裝成功

33 Windows

nodeJS 在 v060 版本之後開始正式支援 windows native直接使用nodeexe 就可以執行程式支援性完全與 linux 相同更棒的部份就是不需經過編譯經過下載之後簡單設定完成立即開發 node程式

26 3 Nodejs安裝與設定

Nodejs中文電子書

下載 nodejs安裝檔案 lthttpnodejsorgdownloadgt

如此完成 windows native nodeexe安裝接著可以進入 command line執行測試在 command line輸入rdquonoderdquo會出現 nodejs版本訊息畫面表示安裝完成

33 Windows 27

Nodejs中文電子書

28 3 Nodejs安裝與設定

4

Nodejs基礎

前篇文章已經由介紹安裝至設定都有完整介紹nodeJS 內部除了javascript常用的函式 (function)物件 (object)之外也有許多不同的自訂物件nodeJS預設建立這些物件為核心物件是為了要讓開發流程更為這些資料在官方文件已經具有許多具體說明接下來將會介紹在開發 nodeJS程式時常見的物件特性與使用方法

41 nodejs http伺服器建立

在 lsquonodejs官方網站 lthttpnodejsorggtlsquo裡面有舉一個最簡單的 HTTP伺服器建立一開始初步就是建立一個伺服器平台讓 nodejs可以與瀏覽器互相行為每種語言一開始的程式建立都是以 Hello world開始最初也從 Hello world帶各位進入 nodejs的世界

輸入以下程式碼儲存檔案為 node basic http hello worldjs

var server

ip = rdquo127001rdquo

port = 1337

http = require(rsquohttprsquo)

server = httpcreateServer(function (req res)

29

Nodejs中文電子書

reswriteHead(200 rsquoContent-Typersquo rsquotextplainrsquo)

resend(rsquoHello Worldnrsquo)

)

serverlisten(port ip)

consolelog(rdquoServer running at httprdquo + ip + rdquordquo + port)

程式碼解講一開始需要有幾個基本的變數

bull ip 機器本身的 ip位置因為使用本地端因此設定為 127001

bull port 需要開通的阜號通常設定為 http port 80因範例不希望與基本 port相衝隨意設定為 1337

在 nodejs 的程式中有許多預設的模組可以使用因此需要使用require方法將模組引入在這邊我們需要使用 http這個模組因此將 http載入Http模組裡面內建有許多方法可以使用這邊採用 createServer創建一個基本的 http伺服器再將 http伺服器給予一個 server變數裡面的回呼函式 (call back function)可以載入 http伺服器的資料與回應方法 (requestresponse)在程式裡面就可以看到我們直接回應給瀏覽器端所需的 Header回應內容

reswriteHead(200 rsquoContent-Typersquo rsquotextplainrsquo)

resend(rsquoHello Worldnrsquo)

Http伺服器需要設定 port ip在最後需要設定 Http監聽需要使用到listen事件監聽所有 Http伺服器行為

httplisten(port ip)

所有事情都完成之後需要確認伺服器正確執行因此使用 console在javascript裡就有這個原生物件console所印出的資料都會顯示於 nodejs伺服器頁面這邊印出的資料並不會傳送到使用者頁面上之後許多除壞

(debug)都會用到 console物件

consolelog(rdquoServer running at httprdquo + ip + rdquordquo + port)

30 4 Nodejs基礎

Nodejs中文電子書

42 nodejs http路徑建立

前面已經介紹如何建立一個簡單的 http伺服器接下來本章節將會介紹如何處理伺服器路徑 (rout)問題在 http的協定下所有從瀏覽器發出的要求 (request)都需要經過處理路徑上的建立也是如此

路徑就是指伺服器 ip位置或者是網域名稱之後對於伺服器給予的要求修改剛才的 hello world檔案修改如下

server = httpcreateServer(function (req res)

consolelog(requrl)

reswriteHead(200 rsquoContent-Typersquo rsquotextplainrsquo)

resend(rsquohello worldnrsquo)

)

重新啟動 nodejs程式後在瀏覽器端測試一下路徑行為結果如下圖

當在瀏覽器輸入 http1270011337test 在伺服器端會收到兩個要求一個是我們輸入的test要求另外一個則是faviconicotest的路徑要求http伺服器本身需要經過程式設定才有辦法回應給瀏覽器端所需要的回應在伺服器中所有的路徑要求都是需要被解析才有辦法取得資料從

42 nodejs http路徑建立 31

Nodejs中文電子書

上面解說可以了解到在 nodejs當中所有的路徑都需要經過設定未經過設定的路由會讓瀏覽器無法取得任何資料導致錯誤頁面的發生底下將會解

說如何設定路由同時避免發生錯誤情形先前 nodejs程式需要增加一些修改才能讓使用者透過瀏覽器在不同路徑時有不同的結果根據剛才

的程式做如下的修改

var server

ip = rdquo127001rdquo

port = 1337

http = require(rsquohttprsquo)

url = require(rsquourlrsquo)

path

server = httpcreateServer(function (req res)

path = urlparse(requrl)

reswriteHead(200 rsquoContent-Typersquo rsquotextplainrsquo)

switch (pathpathname)

case rdquoindexrdquo

resend(rsquoI am indexnrsquo)

break

case rdquotestrdquo

resend(rsquothis is test pagenrsquo)

break

default

resend(rsquodefault pagenrsquo)

break

)

serverlisten(port ip)

consolelog(rdquoServer running at httprdquo + ip + rdquordquo + port)

程式做了片段的修改首先載入 url模組另外增加一個 path變數url模組就跟如同他的命名一般專門處理 url字串處理裡面提供了許多方法來解決路徑上的問題因為從瀏覽器發出的要求路徑可能會帶有多種需求

或者 GET參數組合等因此我們需要將路徑單純化取用路徑部分的資料即可例如使用者可能會送出 http1270011337testsend=1如果直接

32 4 Nodejs基礎

Nodejs中文電子書

信任 requrl就會收到結果為testsend=1所以需要透過 url模組的方法將路徑資料過濾

在這邊使用 urlparse的方法裡面帶入網址格式資料會回傳路徑資料為了後需方便使用將回傳的資料設定到 path變數當中在回傳的路徑資料裡面包含資訊如下圖

這邊只需要使用單純的路徑要求直接取用 pathpathname就可以達到我們的目的

最後要做路徑的判別在不同的路徑可以指定不同的輸出在範例中

有三個可能結果第一個從瀏覽器輸入index就會顯示 index結果test 就會呈現出 test頁面最後如果都不符合預期的輸入會直接顯示 default的頁面最後的預防可以讓瀏覽器不會出現非預期結果讓程式的可靠性提昇

底下為測試結果

42 nodejs http路徑建立 33

Nodejs中文電子書

43 nodejs檔案讀取

前面已經介紹如何使用路由(rount)做出不同的回應實際應用只有在瀏覽器只有輸出幾個文字資料總是不夠的在本章節中將介紹如何使

用檔案讀取輸出檔案資料讓使用者在前端瀏覽器也可以讀取到完整的

html css javascript檔案輸出

檔案管理最重要的部分就是 lsquoFile system lthttpnodejsorgdocslatestapifshtmlgtlsquo這個模組此模組可以針對檔案做管理監控讀取等行為裡面有許多預設的方法底下是檔案輸出的基本範例底下會有兩個檔案

第一個是靜態 html檔案

ltDOCTYPE htmlgt

lthtml xmlns=rdquohttpwwww3org1999xhtmlrdquo xmllang=rdquozh-CNrdquo lang=rdquozh-CNrdquogt

ltheadgt

ltmeta http-equiv=rdquoContent-Typerdquo content=rdquotexthtml charset=UTF-7rdquo gt

lttitlegtnodejs index html filelttitlegt

ltheadgt

ltbodygt

34 4 Nodejs基礎

Nodejs中文電子書

lth1gtnodejs index html filelth1gt

ltbodygt

lthtmlgt

另一個為 nodejs程式

var fs = require(rdquofsrdquo)

filename = rdquostaticindexhtmlrdquo

encode = rdquoutf8rdquo

fsreadFile(filename encode function(err file)

consolelog(file)

)

一開始直接載入 le system模組 載入名稱為 fs讀取檔案主要使

用的方法為 readFile裡面以三個參數路徑 ( le path) 編碼方式 (encoding)

回應函式 (callback)路徑必須要設定為靜態 html所在位置才能指定到正確的檔案靜態檔案的編碼方式也必須正確這邊使用靜態檔案的編

碼為 utf8如果編碼設定錯誤nodejs讀取出來檔案結果會使用 byte raw格式輸出如果錯誤編碼格式會導致輸出資料為 byte raw

回應函式中裡面會使用兩個變數error為錯誤資訊如果讀取的檔案不存在或者發生錯誤error數值會是 true如果成功讀取資料 error將會是 falsecontent則是檔案內容資料讀取後將會把資料全數丟到 content這個變數當中

最後程式的輸出結果畫面如下

43 nodejs檔案讀取 35

Nodejs中文電子書

44 nodejs http靜態檔案輸出

前面已經了解如何讀取本地端檔案接下來將配合 http伺服器路由讓每個路由都能夠輸出相對應的靜態 html檔案首先新增加幾個靜態 html檔案

ltDOCTYPE htmlgt

lthtml xmlns=rdquohttpwwww3org1999xhtmlrdquo xmllang=rdquozh-CNrdquo lang=rdquozh-CNrdquogt

ltheadgt

ltmeta http-equiv=rdquoContent-Typerdquo content=rdquotexthtml charset=UTF-7rdquo gt

lttitlegtnodejs index html filelttitlegt

ltheadgt

ltbodygt

lth1gtnodejs index html filelth1gt

ltbodygt

lthtmlgt

ltDOCTYPE htmlgt

lthtml xmlns=rdquohttpwwww3org1999xhtmlrdquo xmllang=rdquozh-CNrdquo lang=rdquozh-CNrdquogt

ltheadgt

ltmeta http-equiv=rdquoContent-Typerdquo content=rdquotexthtml charset=UTF-7rdquo gt

lttitlegtnodejs test html filelttitlegt

ltheadgt

ltbodygt

lth1gtnodejs test html filelth1gt

ltbodygt

lthtmlgt

ltDOCTYPE htmlgt

lthtml xmlns=rdquohttpwwww3org1999xhtmlrdquo xmllang=rdquozh-CNrdquo lang=rdquozh-CNrdquogt

ltheadgt

ltmeta http-equiv=rdquoContent-Typerdquo content=rdquotexthtml charset=UTF-7rdquo gt

lttitlegtnodejs static html filelttitlegt

ltheadgt

ltbodygt

lth1gtnodejs static html filelth1gt

ltbodygt

lthtmlgt

36 4 Nodejs基礎

Nodejs中文電子書

準備一個包含基本路由功能的 http伺服器

var server

ip = rdquo127001rdquo

port = 1337

http = require(rsquohttprsquo)

url = require(rsquourlrsquo)

server = httpcreateServer(function (req res)

var path = urlparse(requrl)

)

serverlisten(port ip)

consolelog(rdquoServer running at httprdquo + ip + rdquordquo + port)

加入 le system 模組使用 readFile 的功能將這一段程式放置於

createServer的回應函式中

fsreadFile(filePath encode function(err file)

)

readFile的回應函式裡面加入頁面輸出讓瀏覽器可以正確讀到檔案在這邊我們設定讀取的檔案為 html 靜態檔案所以 Content-type 設定為texthtml讀取到檔案的內容將會正確輸出成 html靜態檔案

fsreadFile(filePath encode function(err file)

reswriteHead(200 rsquoContent-Typersquo rsquotexthtmlrsquo)

reswrite(file)

resend()

)

到這邊為止基本的程式內容都已經完成剩下一些細節的調整首先

路徑上必須做調整目前的靜態檔案全部都放置於 static資料夾底下設定一個變數來記住資料夾位置

接著將瀏覽器發出要求路徑與資料夾組合讀取正確 html靜態檔案使用者有可能會輸入錯誤路徑所以在讀取檔案的時候要加入錯誤處理

同時回應 404伺服器無法正確回應的 http header格式

加入這些細節的修改一個基本的 http 靜態 html輸出伺服器就完成了完整程式碼如下

44 nodejs http靜態檔案輸出 37

Nodejs中文電子書

var server

ip = rdquo127001rdquo

port = 1337

http = require(rsquohttprsquo)

fs = require(rdquofsrdquo)

folderPath = rdquostaticrdquo

url = require(rsquourlrsquo)

path

filePath

encode = rdquoutf8rdquo

server = httpcreateServer(function (req res)

path = urlparse(requrl)

filePath = folderPath + pathpathname

fsreadFile(filePath encode function(err file)

if (err)

reswriteHead(404 rsquoContent-Typersquo rsquotextplainrsquo)

resend()

return

reswriteHead(200 rsquoContent-Typersquo rsquotextapplicationrsquo)

reswrite(file)

resend()

)

)

serverlisten(port ip)

consolelog(rdquoServer running at httprdquo + ip + rdquordquo + port)

45 nodejs http GET資料擷取

http伺服器中除了路由之外另一個最常使用的方法就是擷取 GET資料本單元將會介紹如何透過基本 http伺服器擷取瀏覽器傳來的要求擷取 GET資料

在 http協定中GET參數都是藉由 URL從瀏覽器發出要求送至伺服器

38 4 Nodejs基礎

Nodejs中文電子書

端基本的傳送網址格式可能如下

http127001testsend=1amptest=2

上面這段網址裡面的 GET參數就是 send而這個變數的數值就為 1如果想要在 http伺服器取得 GET資料需要在瀏覽器給予的要求 (request)做處理

首先需要載入 query string這個模組這個模組主要是用來將字串資料

過濾後轉換成javascript物件

qs = require(rsquoquerystringrsquo)

接著在第一階段利用 url模組過濾瀏覽器發出的 URL資料後將回應的物件裡面的 query這個變數是一個字串值資料過濾後如下

send=1amptest=2

透過 query string使用 parse這個方法將資料轉換成 javascript物件就表示 GET的資料已經被伺服器端正式擷取下來

path = urlparse(requrl)

parameter = qsparse(pathquery)

整個 nodejs http GET參數完整擷取程式碼如下

var server

ip = rdquo127001rdquo

port = 1337

http = require(rsquohttprsquo)

qs = require(rsquoquerystringrsquo)

url = require(rsquourlrsquo)

server = httpcreateServer(function (req res)

var path = urlparse(requrl)

parameter = qsparse(pathquery)

consoledir(parameter)

reswriteHead(200 rsquoContent-Typersquo rsquotextplainrsquo)

reswrite(rsquoBrowser test GET parameternrsquo)

resend()

)

45 nodejs http GET資料擷取 39

Nodejs中文電子書

serverlisten(port ip)

consolelog(rdquoServer running at httprdquo + ip + rdquordquo + port)

程式運作之後由瀏覽器輸入要求網址之後nodejs伺服器端回應資料為

Server running at http1270011337

send rsquo1rsquo test rsquo1rsquo

46 本章結語

前面所解說的部份一大部分主要是處理 http伺服器基本問題雖然在某些部分有牽扯到 http 伺服器基本運作原理主要還是希望可以藉由這些基本範例練習 nodejs練習回應函式與語法串接的特點習慣編寫javascript風格程式當然直接這樣開發 nodejs是非常辛苦的接下來在模組實戰開發的部份將會介紹特定的模組一步一步帶領各位從無到有進行

nodejs應用程式開發

40 4 Nodejs基礎

5

NPM套件管理工具

npm全名為 Node Package Manager是 Nodejs的套件(package)管理工具類似 Perl的 ppm或 PHP的 PEAR等安裝 npm後使用npm install

module name指令即可安裝新套件維護管理套件的工作會更加輕鬆

npm可以讓Nodejs的開發者直接利用擴充線上的套件庫(packagesregistry)加速軟體專案的開發npm提供很友善的搜尋功能可以快速找到安裝需要的套件當這些套件發行新版本時npm也可以協助開發者自動更新這些套件

npm不僅可用於安裝新的套件它也支援搜尋列出已安裝模組及更新的功能

51 安裝 NPM

Nodejs在 063版本開始內建 npm讀者安裝的版本若是此版本或更新的版本就可以略過以下安裝說明

若要檢查 npm是否正確安裝可以使用以下的指令

npm -v

41

Nodejs中文電子書

執行結果說明

若 npm正確安裝執行 npm -v將會看到類似 110-2的版本訊息

若讀者安裝的 Nodejs版本比較舊或是有興趣嘗試自己動手安裝 npm工具則可以參考以下的說明

安裝於Windows系統

Nodejs for Windows 於 062 版開始內建 npm使用 nodejsorg 官方提供的安裝程式不需要進一步的設定就可以立即使用 npm指令對於Windows的開發者來說大幅降低環境設定的問題與門檻

除了使用 Nodejs內建的 npm讀者也可以從 npm官方提供的以下網址

httpnpmjsorgdist

這是由 npm提供的 Fancy Windows Install版本請下載壓縮檔(例如npm-110-3zip)並將壓縮檔內容解壓縮至 Nodejs的安裝路徑(例如CProgram Filesnodejs)

解壓縮後在 Nodejs的安裝路徑下應該有以下的檔案及資料夾

bull npmcmd(檔案)

bull node modules(資料夾)

安裝於 Linux系統

Ubuntu Linux的使用者可以加入 NPM Unoffcial PPA這個 repository即可使用 apt-get完成 npm安裝

42 5 NPM套件管理工具

Nodejs中文電子書

Ubuntu Linux使用 apt-get安裝 npm

sudo apt-get install python-software-properties

sudo add-apt-repository ppagias-kay-leenpm

sudo apt-get update

sudo apt-get install npm

npm官方提供的安裝程式 installsh可以適用於大多數的 Linux系統使用這個安裝程式請先確認

1 系統已安裝 curl工具(請使用 curl --version查看版本訊息)

2 已安裝 Nodejs並且 PATH正確設置

3 Nodejs的版本必須大於 04x

以下為 npm提供的安裝指令

curl httpnpmjsorginstallsh | sh

安裝成功會看到如下訊息

installsh安裝成功的訊息

npm10105 homeUSERNAMElocalnodelibnode_modulesnpm

It worked

安裝於Mac OS X

建議採用與 Nodejs相同的方式進行 npm的安裝例如使用MacPorts安裝 Nodejs就同樣使用MacPorts安裝 npm這樣對日後的維護才會更方便容易

使用 MacPorts安裝 npm是本書比較建議的方式它可以讓 npm的安裝移除及更新工作自動化將會幫助開發者節省寶貴時間

51 安裝 NPM 43

Nodejs中文電子書

安裝MacPorts的提示

在MacPorts網站可以取得 OS X系統版本對應的安裝程式(例如 106或 107)httpwwwmacportsorg安裝過程會詢問系統管理者密碼使用預設的選項完成安裝即可安

裝MacPorts之後在終端機執行 port -v將會看到MacPorts的版本訊息

安裝 npm之前先更新MacPorts的套件清單以確保安裝的 npm是最新版本

sudo port -d selfupdate

接著安裝 npm

sudo port install npm

若讀者的 Nodejs並非使用MacPorts安裝則不建議使用MacPorts安裝npm因為 MacPorts會自動檢查並安裝相依套件而 npm相依 nodejs所以MacPorts也會一併將 nodejs套件安裝造成先前讀者使用其它方式安裝的 nodejs被覆蓋

讀者可以先使用 MacPorts安裝 curl(sudo port install curl)

再參考 Linux的 installsh安裝方式即可使用 npm官方提供的安裝程式

NPM安裝後測試

npm是指令列工具(command-line tool)使用時請先打開系統的文字終端機工具

測試 npm安裝與設定是否正確請輸入指令如下

npm -v

或是

npm --version

如果 npm已經正確安裝設定就會顯示版本訊息

44 5 NPM套件管理工具

Nodejs中文電子書

執行結果(範例)

110-2

52 使用 NPM安裝套件

npm目前擁有超過 6000種套件(packages)可以在 npm registry使用關鍵字搜尋套件

httpsearchnpmjsorg

舉例來說在關鍵字欄位輸入「coffee-script」下方的清單就會自動列出包含 coffee-script關鍵字的套件

接著我們回到終端機模式的操作npm的指令工具本身就可以完成套

件搜尋的任務

例如以下的指令同樣可以找出 coffee-script相關套件

npm search coffee-script

以下是搜尋結果的參考畫面

52 使用 NPM安裝套件 45

Nodejs中文電子書

找到需要的套件後(例如 express)即可使用以下指令安裝

npm install coffee-script

值得注意的一點是使用 npm install會將指定的套件安裝在工

作目錄(Working Directory)的 node modules資料夾下

以Windows為例如果執行 npm install的目錄位於

Cproject1

那麼 npm將會自動建立一個 node modules的子目錄(如果不存在)

Cproject1node modules

並且將下載的套件放置於這個子目錄例如

Cproject1node modulescoffee-script

這個設計讓專案可以個別管理相依的套件並且可以在專案佈署或發

行時將這些套件(位於 node modules)一併打包方便其它專案的使用者不必再重新下載套件

這個 npm install的預設安裝模式為 local(本地)只會變更當前專案的資料夾不會影響系統

另一種安裝模式稱為 global(全域)這種模式會將套件安裝到系統資

料夾也就是 npm安裝路徑的 node modules資料夾例如

CProgram Filesnodejsnode modules

46 5 NPM套件管理工具

Nodejs中文電子書

是否要使用全域安裝可以依照套件是否提供新指令來判斷舉例來說express 套件提供 express 這個指令而 coffee-script 則提供 coffee

指令

在 local 安 裝 模 式 中這 些 指 令 的 程 式 檔 案會 被 安 裝 到node modules的 bin這個隱藏資料夾下除非將bin的路徑加入 PATH環境變數否則要執行這些指令將會相當不便

為了方便指令的執行我們可以在 npm install 加上 -g 或 --

global參數啟用 global安裝模式例如

npm install -g coffee-script

npm install -g express

使用 global安裝模式需要注意執行權限的問題若權限不足可能會出現類似以下的錯誤訊息

npm ERR Error EACCES permission denied rsquorsquo

npm ERR

npm ERR Please try running this command again as rootAdministrator

要獲得足夠得執行權限請參考以下說明

bull Windows 7 或 2008 以上在「命令提示字元」的捷徑按右鍵選擇「以系統管理員身分執行」執行 npm指令時就會具有 Administrator身分

bull Mac OS X或 Linux系統可以使用 sudo指令例如

sudo npm install -g express

bull Linux系統可以使用 root權限登入或是以「sudo su -」切換成 root身分(使用 root權限操作系統相當危險因此並不建議使用這種方式)

若加上 -g參數使用 npm install -g coffee-script完成安裝

後就可以在終端機執行 coffee指令例如

coffee -v

52 使用 NPM安裝套件 47

Nodejs中文電子書

執行結果(範例)

CoffeeScript version 120

53 套件的更新及維護

除了前一節說明的 search 及 install 用法npm 還提供其他許多指令(commands)

使用 npm help可以查詢可用的指令

npm help

執行結果(部分)

where ltcommandgt is one of

adduser apihelp author bin bugs c cache completion

config deprecate docs edit explore faq find get

help help-search home i info init install la link

list ll ln login ls outdated owner pack prefix

prune publish r rb rebuild remove restart rm root

run-script s se search set show star start stop

submodule tag test un uninstall unlink unpublish

unstar up update version view whoami

使用 npm help command可以查詢指令的詳細用法例如

npm help list

接下來本節要介紹開發過程常用的 npm指令

使用 list可以列出已安裝套件

npm list

48 5 NPM套件管理工具

Nodejs中文電子書

執行結果(範例)

-- coffee-script120

- express256

- connect185

| -- formidable108

-- mime124

-- mkdirp007

-- qs041

檢視某個套件的詳細資訊例如

npm show express

升級所有套件(如果該套件已發佈更新版本)

npm update

升級指定的套件

npm update express

移除指定的套件

npm uninstall express

54 使用 packagejson

對於正式的 Nodejs專案可以建立一個命名為 packagejson的設

定檔(純文字格式)檔案內容參考範例如下

54 使用 packagejson 49

Nodejs中文電子書

packagejson(範例)

rdquonamerdquo rdquoapplication-namerdquo

rdquoversionrdquo rdquo001rdquo

rdquoprivaterdquo true

rdquodependenciesrdquo

rdquoexpressrdquo rdquo255rdquo

rdquocoffee-scriptrdquo rdquolatestrdquo

rdquomongooserdquo rdquogt= 253rdquo

其中 name與 version依照專案的需求設置

需要注意的是 dependencies的設定它用於指定專案相依的套件名

稱及版本

bull rdquoexpressrdquo rdquo255rdquo

代表此專案相依版本 255的 express套件

bull rdquocoffee-scriptrdquo rdquolatestrdquo

使用最新版的 coffee-script套件(每次更新都會檢查新版)

bull rdquomongooserdquo rdquogt= 253rdquo

使用版本大於 253的 mongoose套件

假設某個套件的新版可能造成專案無法正常運作就必須指定套件的

版本避免專案的程式碼來不及更新以相容新版套件通常在開發初期的

專案需要盡可能維持新套件的相容性(以取得套件的更新或修正)可以

用「gt=」設定最低相容的版本或是使用「latest」設定永遠保持最新

套件

50 5 NPM套件管理工具

6

Express介紹

在前面的 nodejs基礎當中介紹許多許多開設 http的使用方法及介紹以及許多基本的 nodejs基本應用

接下來要介紹一個套件稱為 express [Express](httpexpressjscom)這個套件主要幫忙解決許多 nodejs http server所需要的基本服務讓開發 httpservice變得更為容易不需要像之前需要透過層層模組(module)才有辦法開始編寫自己的程式

這個套件是由 TJ Holowaychuk 製作而成的套件裡面包含基本的路由處理 (route)http資料處理(GETPOSTPUT)另外還與樣板套件(jshtml template engine)搭配同時也可以處理許多複雜化的問題

61 Express安裝

安裝方式十分簡單只要透過之前介紹的 NPM就可以使用簡單的指令安裝指令如下

這邊建議需要將此套件安裝成為全域模組方便日後使用

51

Nodejs中文電子書

62 Express基本操作

express的使用也十分簡單先來建立一個基本的 hello world

var app = require(rsquoexpressrsquo)createServer()

port = 1337

applisten(port)

appget(rsquorsquo function(req res)

ressend(rsquohello worldrsquo)

)

consolelog(rsquostart express servernrsquo)

可以從上面的程式碼發現基本操作與 nodejs http的建立方式沒有太大差異主要差在當我們設定路由時可以直接透過 appget方式設定回應與接受方式

63 Express路由處理

Express對於 http服務上有許多包裝讓開發者使用及設定上更為方便例如有幾個路由設定那我們就統一藉由 appget來處理

Create http server

appget(rsquorsquo function(req res)

ressend(rsquohello worldrsquo)

)

appget(rsquotestrsquo function(req res)

ressend(rsquotest renderrsquo)

)

appget(rsquouserrsquo function(req res)

ressend(rsquouser pagersquo)

)

52 6 Express介紹

Nodejs中文電子書

如上面的程式碼所表示appget可以帶入兩個參數第一個是路徑名稱設定第二個為回應函式 (call back function)回應函式裡面就如同之前的 createServer方法裡面包含 requestresponse兩個物件可供使用使用者就可以透過瀏覽器輸入不同的 url切換到不同的頁面顯示不同的結果

路由設定上也有基本的配對方式讓使用者從瀏覽器輸入的網址可以

是一個變數只要符合型態就可以有對應的頁面產出例如

Create http server

appget(rsquouseridrsquo function(req res)

ressend(rsquouser rsquo + reqparamsid)

)

appget(rsquonumberrsquo function(req res)

ressend(rsquonumber rsquo + reqparamsnumber)

)

裡 面 使 用 到number 從 網 址 輸 入 之 後 就 可 以 直 接 使 用reqparamsnumber 取得所輸入的資料變成 url 參數使用當然前面也是可以加上路徑的設定userid在瀏覽器上路徑必須符合userxxx透

過 reqparamsid就可以取到 xxx這個字串值

另外express參數處理也提供了路由參數配對處理也可以透過正規表示法作為參數設定

var app = require(rsquoexpressrsquo)createServer()

port = 1337

applisten(port)

appget(^ip((d23)((d23))((d23))((d23))) function(req res)

ressend(reqparams)

)

上面程式碼可以發現後面路由設定的型態是正規表示法裡面設定

格式為ip之後必須要加上 ip型態才會符合資料格式同時取得 ip資料已經由正規表示法將資料做分群因此可以取得 ip的四個數字

此程式執行之後可以透過瀏覽器測試輸入網址為 localhost3000ip25525510010可以從頁面獲得資料

63 Express路由處理 53

Nodejs中文電子書

此章節全部範例程式碼如下

overview

author Caesar Chi

blog clonnblogspotcom

version 20120226

create server

var app = require(rsquoexpressrsquo)createServer()

port = 1337

applisten(port)

normal style

appget(rsquorsquo function(req res)

ressend(rsquohello worldrsquo)

)

appget(rsquotestrsquo function(req res)

ressend(rsquotest renderrsquo)

)

parameter style

appget(rsquouseridrsquo function(req res)

ressend(rsquouser rsquo + reqparamsid)

)

appget(rsquonumberrsquo function(req res)

ressend(rsquonumber rsquo + reqparamsnumber)

)

REGX style

appget(^ip((d23)((d23))((d23))) function(req res)

ressend(reqparams)

)

appget(rsquorsquo function(req res)

ressend(rsquoPage not foundrsquo 404)

54 6 Express介紹

Nodejs中文電子書

)

consolelog(rsquostart express servernrsquo)

64 Express middleware

Express 裡面有一個十分好用的應用概念稱為 middleware可以透過middleware做出複雜的效果同時上面也有介紹 next方法參數傳遞就是靠 middleware的概念來傳遞參數讓開發者可以明確的控制程式邏輯

create http server

appuse(expressbodyParser())

appuse(expressmethodOverride())

appuse(expresssession())

上面都是一種 middleware的使用方式透過 appuse方式裡面載入函式執行方法回應函式會包含三個基本參數responserequestnext其中next表示下一個 middleware執行函式同時會自動將預設三個參數繼續帶往下個函式執行底下有個實驗

上面的片段程式執行後開啟瀏覽器連結上 localhost1337會發現伺服器回應結果順序如下

first middle ware

second middle ware

execute middle ware

end middleware function

從上面的結果可以得知剛才設定的 middleware都生效了在 appuse設定的 middleware是所有 url皆會執行方法如果有指定特定方法就可以使用 appget的 middleware設定在 appget函式的第二個參數就可以帶入函式或者是匿名函式只要函式裡面最後會接受 request response next這三個參數同時也有正確指定 next函式的執行時機最後都會執行到最後一個方法當然開發者也可以評估程式邏輯要執行到哪一個階段讓邏輯

可以更為分明

64 Express middleware 55

Nodejs中文電子書

65 Express路由應用

在實際開發上可能會遇到需要使用參數等方式混和變數一起使用

express裡面提供了一個很棒的處理方法 appall這個方式可以先採用基本路由配對再將設定為每個不同的處理方式開發者可以透過這個方式簡

化自己的程式邏輯

overview

author

version 20120226

create server

var app = require(rsquoexpressrsquo)createServer()

port = 1337

users = [

name rsquoClonnrsquo

name rsquoChirsquo

]

applisten(port)

appall(rsquouseridoprsquo function(req res next)

requser = users[reqparamsid]

if (requser)

next()

else

next(new Error(rsquocannot find user rsquo + reqparamsid))

)

appget(rsquouseridrsquo function(req res)

ressend(rsquoviewing rsquo + requsername)

)

appget(rsquouserideditrsquo function(req res)

ressend(rsquoediting rsquo + requsername)

)

56 6 Express介紹

Nodejs中文電子書

appget(rsquouseriddeletersquo function(req res)

ressend(rsquodeleting rsquo + requsername)

)

appget(rsquorsquo function(req res)

ressend(rsquoPage not foundrsquo 404)

)

consolelog(rsquostart express servernrsquo)

內部宣告一組預設的使用者分別給予名稱設定藉由 appall這個方法可以先將路由雛形建立再接下來設定 appget的路徑格式只要符合格式就會分配進入對應的方法中像上面的程式當中如果使用者輸入路徑為user0除了執行 appall程式之後執行 next方法就會對應到路徑設定為userid的這個方法當中如果使用者輸入路徑為user0edit就會執行到useridedit的對應方法

66 Express GET應用範例

我們準備一個使用 GET方法傳送資料的表單

ltDOCTYPE htmlgt

lthtmlgt

ltheadgt

ltmeta http-equiv=rdquoContent-Typerdquo content=rdquotexthtml charset=utf8rdquogt

lttitlegtNodejs 菜鳥筆記 (1)lttitlegt

ltlink rel=rdquostylesheetrdquo href=rdquocssstylecssrdquo type=rdquotextcssrdquo media=rdquoallrdquo gt

ltheadgt

ltbodygt

lth1gtNodejs 菜鳥筆記-註冊lth1gt

ltform id=rdquosignuprdquo method=rdquoGETrdquo action=rdquohttplocalhost3000Signuprdquogt

ltlabelgt 使用者名稱ltlabelgtltinput type=rdquotextrdquo id=rdquousernamerdquo name=rdquousernamerdquo gtltbrgt

ltlabelgt 電子郵件ltlabelgtltinput type=rdquotextrdquo id=rdquoemailrdquo name=rdquoemailrdquo gtltbrgt

ltinput type=rdquosubmitrdquo value=rdquo 註冊我的帳號rdquo gtltbrgt

ltformgt

ltbodygt

lthtmlgt

66 Express GET應用範例 57

Nodejs中文電子書

這個表單沒有什麼特別的地方我們只需要看第 9 行form 使用的method是 GET然後 action是rdquohttplocalhost3000Signupldquo等一下我們要來撰寫Signup這個 URL Path的處理程式

處理 Signup行為

我們知道所謂的 GET方法會透過 URL來把表單的值給帶過去以上面的表單來說到時候 URL會以這樣的形式傳遞

httplocalhost3000Signupusername=xxxampemail=xxx

所以要能處理這樣的資料必須有以下功能

bull 解析 URL

bull 辨別動作是 Signup

bull 解析出 username和 email

一旦能取得 username和 email的值程式就能加以應用了

處理 Signup的程式碼雛形

load module

var url = require(rsquourlrsquo)

urlData = urlparse(requrltrue)

action = urlDatapathname

reswriteHead(200 rdquoContent-Typerdquordquotexthtml charset=utf-8rdquo)

if (action === rdquoSignuprdquo)

user = urlDataquery

resend(rdquolth1gtrdquo + userusername + rdquo 歡迎您的加入 lth1gtltpgt 我們已經將會員啟用信寄至rdquo + useremail + rdquoltpgtrdquo)

首先需要加載 url module它是用來協助我們解析 URL的模組接著使用 urlparse方法第一個傳入 url字串變數也就是 requrl另外第二個參數的用意是設為 ture則引進 querystring模組來協助處理預設是 false它影響到的是 urlDataquery設為 true會傳回物件不然就只是一般的字串urlparse會將字串內容整理成一個物件我們把它指定給 urlData

action變數作為記錄 pathname這是我們稍後要來判斷目前網頁的動作是什麼接著先將 html表頭資訊 (Header)準備好再來判斷路徑邏輯如

58 6 Express介紹

Nodejs中文電子書

果是 Signup這個動作就把 urlDataquery裡的資料指定給 user然後輸出userusername和 useremail把使用者從表單註冊的資料顯示於頁面中

最後進行程式測試啟動 nodejs主程式之後開啟瀏覽器就會看到表單填寫完畢按下送出就可以看到結果了

完整 nodejs程式碼如下

var http = require(rsquohttprsquo)

url = require(rsquourlrsquo)

fs = require(rdquofsrdquo)

server

server = httpcreateServer(function (reqres)

var urlData

encode = rdquoutf8rdquo

filePath = rdquoviewexpress_get_example_formhtmlrdquo

action

urlData = urlparse(requrltrue)

action = urlDatapathname

reswriteHead(200 rdquoContent-Typerdquordquotexthtml charset=utf-8rdquo)

if (action === rdquoSignuprdquo)

user = urlDataquery

resend(rdquolth1gtrdquo + userusername + rdquo 歡迎您的加入 lth1gtltpgt 我們已經將會員啟用信寄至rdquo + useremail + rdquoltpgtrdquo)

else

fsreadFile(filePath encode function(err file)

reswrite(file)

resend()

)

)

serverlisten(3000)

consolelog(rsquoServer 跑起來了現在時間是rsquo + new Date())

66 Express GET應用範例 59

Nodejs中文電子書

67 Express POST應用範例

一開始準備基本的 html表單傳送內容以 POST方式form的 action屬性設定為 POST其餘 html內容與前一個範例應用相同

ltDOCTYPE htmlgt

lthtmlgt

ltheadgt

ltmeta http-equiv=rdquoContent-Typerdquo content=rdquotexthtml charset=utf8rdquogt

lttitlegtNodejs 菜鳥筆記 (1)lttitlegt

ltlink rel=rdquostylesheetrdquo href=rdquocssstylecssrdquo type=rdquotextcssrdquo media=rdquoallrdquo gt

ltheadgt

ltbodygt

lth1gtNodejs 菜鳥筆記-註冊lth1gt

ltform id=rdquosignuprdquo method=rdquoPOSTrdquo action=rdquohttplocalhost3000Signuprdquogt

ltlabelgt 使用者名稱ltlabelgtltinput type=rdquotextrdquo id=rdquousernamerdquo name=rdquousernamerdquo gtltbrgt

ltlabelgt 電子郵件ltlabelgtltinput type=rdquotextrdquo id=rdquoemailrdquo name=rdquoemailrdquo gtltbrgt

ltinput type=rdquosubmitrdquo value=rdquo 註冊我的帳號rdquo gtltbrgt

ltformgt

ltbodygt

lthtmlgt

nodejs的程式處理邏輯與前面 GET範例類似部分程式碼如下

qs = require(rsquoquerystringrsquo)

if (action === rdquoSignuprdquo)

formData = rsquorsquo

reqon(rdquodatardquo function (data)

formData += data

)

reqon(rdquoendrdquo function ()

user = qsparse(formData)

resend(rdquolth1gtrdquo + userusername + rdquo 歡迎您的加入 lth1gtltpgt 我們已經將會員啟用信寄至rdquo + useremail + rdquoltpgtrdquo)

)

60 6 Express介紹

Nodejs中文電子書

主要加入了rsquoquerystringrsquo這個moduel方便我們等一下解析由表單 POST回來的資料另外加入一個 formData的變數用來搜集待等一下表單回傳的資料前面的 GET範例我們只從 req拿出 url的資料這次要在利用req身上的事件處理

JavaScript 在訂閱事件時使用 addEventListener而 nodejs 使用的則是on這邊加上了監聽 data的事件會在瀏覽器傳送資料到Web Server時被執行參數是它所接收到的資料型態是字串

接著再增加 end的事件當瀏覽器的請求事件結束時它就會動作

由於瀏覽器使用 POST在上傳資料時會將資料一塊塊地上傳因為我們在監聽 data事件時透過 formData變數將它累加起來lt不過由於我們

上傳的資料很少一次就結束不過如果日後需要傳的是資料比較大的檔

案這個累加動作就很重要

當資料傳完就進到 end 事件中會用到 qsparse 來解析 formDataformData的內容是字串內容是

username=wordsmithampemail=wordsmith40somewhere

而 qsparse可以幫我們把這個 querystring轉成物件的格式也就是

username=wordsmithampemail=wordsmith40somewhere

一旦轉成物件並指定給 user之後其他的事情就和 GET方法時操作的一樣寫 response的表頭將內容回傳並將 userusername和 useremail代入到內容中

修改完成後接著執行 nodejs程式啟動 web server開啟瀏覽器進入表單測試看看POST的方式能否順利運作

完整程式碼如下

var http = require(rsquohttprsquo)

url = require(rsquourlrsquo)

fs = require(rdquofsrdquo)

qs = require(rsquoquerystringrsquo)

server

server = httpcreateServer(function (reqres)

var urlData

67 Express POST應用範例 61

Nodejs中文電子書

encode = rdquoutf8rdquo

filePath = rdquoviewexpress_post_example_formhtmlrdquo

formData

action

urlData = urlparse(requrltrue)

action = urlDatapathname

reswriteHead(200 rdquoContent-Typerdquordquotexthtml charset=utf-8rdquo)

if (action === rdquoSignuprdquo)

formData = rsquorsquo

reqon(rdquodatardquo function (data)

formData += data

)

reqon(rdquoendrdquo function ()

user = qsparse(formData)

resend(rdquolth1gtrdquo + userusername + rdquo 歡迎您的加入 lth1gtltpgt 我們已經將會員啟用信寄至rdquo + useremail + rdquoltpgtrdquo)

)

else

fsreadFile(filePath encode function(err file)

reswrite(file)

resend()

)

)

serverlisten(3000)

consolelog(rsquoServer 跑起來了現在時間是rsquo + new Date())

62 6 Express介紹

Nodejs中文電子書

68 Express AJAX應用範例

在 Nodejs要使用 Ajax傳送資料並且與之互動在接受資料的部份沒有太大的差別client端不是用 GET就是用 POST來傳資料重點在處理完後用 JSON格式回傳當然 Ajax不見得只傳 JSON格式有時是回傳一段 HTML碼不過後者對伺服器來說基本上就和前兩篇沒有差別了所以我們還是以回傳 JSON做為這一回的主題

這一回其實大多數的工作都會落在前端 Ajax上面前端要負責發送與接收資料並在接收資料後撤掉原先發送資料的表單並將取得的資料

改成 HTML格式之後放上頁面

首先先準備 HTML靜態頁面資料

ltDOCTYPE htmlgt

lthtmlgt

ltheadgt

lttitlegtNodejs 菜鳥筆記 (3)lttitlegt

ltlink rel=rdquostylesheetrdquo href=rdquocssstylecssrdquo type=rdquotextcssrdquo media=rdquoallrdquo gt

ltscript src=rdquohttpsajaxgoogleapiscomajaxlibsjquery171jqueryminjsrdquogtltscriptgt

ltheadgt

ltbodygt

lth1gtNodejs 菜鳥筆記-註冊 (用 Ajax)lth1gt

ltform id=rdquosignuprdquo action=rdquoSignuprdquo method=rdquoPOSTrdquogt

ltlabelgt 使用者名稱ltlabelgtltinput type=rdquotextrdquo id=rdquousernamerdquo name=rdquousernamerdquo gtltbrgt

ltlabelgt 電子郵件ltlabelgtltinput type=rdquotextrdquo id=rdquoemailrdquo name=rdquoemailrdquo gtltbrgt

ltinput type=rdquosubmitrdquo value=rdquo 註冊我的帳號rdquo gtltbrgt

ltformgt

ltdiv id=rdquoresultrdquogt

ltdivgt

ltscript type=rdquotextjavascriptrdquogt

$(function()$(rsquosignup input[type=rdquosubmitrdquo]rsquo)click(function(e)

var user =

userusername = $(rdquousernamerdquo)val()useremail = $(rdquoemailrdquo)val()

$post(rdquoSignuprdquouserfunction(data)greet(data)

)

68 Express AJAX應用範例 63

Nodejs中文電子書

epreventDefault()

)

var greet = function(msg)

var resultNode = $(rsquoresultrsquo)greeting = $(rsquolth2gtHirsquo+msgusername+rsquo 你的會員 id 是rsquo+ msgid +rsquo 我們會將會員啟動信寄至rsquo+msgemail+rsquolth2gtrsquo)

$(rsquosignuprsquo)hide()resultNodehtml(rdquordquo)

resultNodeappend(greeting)

)

ltscriptgt

ltbodygt

lthtmlgt

HTML頁面上準備了一個表單用來傳送註冊資料接著直接引用了Google CDN來載入 jQuery用來幫我們處理 Ajax的工作這次要傳送和接收的工作很大的變動都在 HTML頁面上的 JavaScript當中我們要做的事有 (相關 jQuery處理這邊不多做贅述指提起主要功能解說)

bull 用 jQUery取得 submit按鈕綁定它的 click動作

bull 取得表單 username和 email的值存放在 user這個物件中

bull 用 jQuery的 $post方法將 user的資料傳到 Server

bull 一旦成功取得資料後透過 greet這個 function組成回報給 user的訊息

bull 清空原本給使用者填資料的表單

bull 將 Server回傳的 usernameemail和 id這 3個資料組成回應的訊息

bull 將訊息放到原本表單的位置

經過以上的處理後一個 Ajax的表單的基本功能已經完成

接著進行 nodejs主要程式的編輯部分程式碼如下

var fs = require(rdquofsrdquo)

64 6 Express介紹

Nodejs中文電子書

qs = require(rsquoquerystringrsquo)

if (action === rdquoSignuprdquo)

formData = rsquorsquo

reqon(rdquodatardquo function (data)

formData += data

)

reqon(rdquoendrdquo function ()

var msg

user = qsparse(formData)

userid = rdquo123456rdquo

msg = JSONstringify(user)

reswriteHead(200 rdquoContent-Typerdquordquoapplicationjson charset=utf-8rdquordquoContent-Lengthrdquomsglength)

resend(msg)

)

這裡的程式和前面 POST範例基本上大同小異差別在

bull 幫 user的資料加上 id隨意存放一些文字進去讓 Server回傳的資料多於 Client端傳上來的不然會覺得 Server都沒做事

bull 增加了 msg 這個變數存放將 user 物件 JSON 文字化的結果JSONstringify這個轉換函式是 V8引擎所提供的如果你好奇的話

bull 大重點來了我們要告訴 Client端這次回傳的資料格式是 JSON所在 Content-type和 Content-Length要提供給 Client

Server很輕鬆就完成任務了最後進行程式測試啟動 nodejs主程式之後開啟瀏覽器就會看到表單填寫完畢按下送出就可以看到結果了

最後 nodejs本篇範例程式碼如下

var http = require(rsquohttprsquo)

url = require(rsquourlrsquo)

fs = require(rdquofsrdquo)

qs = require(rsquoquerystringrsquo)

server

68 Express AJAX應用範例 65

Nodejs中文電子書

server = httpcreateServer(function (reqres)

var urlData

encode = rdquoutf8rdquo

filePath = rdquoviewexpress_ajax_example_formhtmlrdquo

formData

action

urlData = urlparse(requrltrue)

action = urlDatapathname

if (action === rdquoSignuprdquo)

formData = rsquorsquo

reqon(rdquodatardquo function (data)

formData += data

)

reqon(rdquoendrdquo function ()

var msg

user = qsparse(formData)

userid = rdquo123456rdquo

msg = JSONstringify(user)

reswriteHead(200 rdquoContent-TyperdquordquoapplicationjsonrdquordquoContent-Lengthrdquomsglength)

resend(msg)

)

else

fsreadFile(filePath encode function(err file)

reswriteHead(200 rdquoContent-Typerdquordquotexthtml charset=utf-8rdquo)

reswrite(file)

resend()

)

)

serverlisten(3000)

66 6 Express介紹

Nodejs中文電子書

consolelog(rsquoServer 跑起來了現在時間是rsquo + new Date())

69 原始資料提供

bull [NodeJS初學者筆記 (1)-用 GET傳送資料] (httpithelpithomecomtwquestion10087402)

bull [NodeJS初學者筆記 (2)-用 POST傳送資料] (httpithelpithomecomtwquestion10087489)

bull [NodeJS 初學者筆記 (3)-用 Ajax 傳送資料] (httpithelpithomecomtwquestion10087627)

69 原始資料提供 67

Nodejs中文電子書

68 6 Express介紹

7

CoffeeScript

bull Spine

bull Hem

bull Spineapp

Letrsquos Have a Cup of CoffeeScript

bull es5-shim ECMAScript 5 compatibility shims for legacy JavaScript engines

ESnext

node-iform - A middleware for node-validator httpsgithubcomguileennode-iform

69

Nodejs中文電子書

70 7 CoffeeScript

8

製作一個 Hubot的 Plurk Adapter

81 應用事項提醒

此應用範例以CoffeeScript編寫主要程式架構採用Github釋放的Hubot專案以下有幾個主要注意事項請讀者注意

bull Hubot 一開始的架構除了內建的兩個 Adapter 之外其餘都要以Nodejs的Module方式才能運作

bull Hubot的 binhubot裡面寫著 npm install所以不管你怎麼改原始碼也不能改變第一點的狀況

其實上述都在討論同一件事情NodeJS的模組而且模組不能設定相對路徑之類的來安裝一定要包裝成 npm相符和格式才有辦法正確執行

82 建立 Adapter

首先需要準備兩個檔案

bull packagejson

bull plurkcoffee

71

Nodejs中文電子書

有這兩個就足以變成 NodeJS的模組了在開始 Coding之前先來設定一下 packagejson相依性

rdquonamerdquo rdquohubot-plurkrdquo

rdquoversionrdquo rdquo011rdquo

rdquomainrdquo rdquoplurkrdquo

rdquodependenciesrdquo

rdquohubotrdquo rdquogt=205rdquo

rdquooauthrdquo rdquordquo

rdquocronrdquordquordquo

這邊會使用到NodeJS的 cron模組(Module)而登入 Plurk需要OAuth標準授權才行所以也需要加載 OAuth模組

注意Hubot在讀取非內建模組時會自動在前面加上 hubot-的前置

83 建立 Robot跟 API

本篇應用基本上程式碼大部分參考 Hubot - Twitter Adapter來製作主要差異只有在使用 OAuth這個模組Twitter有 Streaming可用而 Plurk則得用 Comet方式來達到即時讀取

plurkcoffee程式碼

Robot = require(rdquohubotrdquo)robot()

Adapter = require(rdquohubtordquo)adapter()

EventEmitter = require(rdquoeventsrdquo)EventEmitter

oauth = require(rdquooauthrdquo)

cronJob = require(rdquocronrdquo)CronJob

class Plurk exntends Adapter

class PlurkStreaming exnteds EventEmitter

72 8 製作一個 Hubot的 Plurk Adapter

Nodejs中文電子書

先弄個基本架構主要 Class皆參考 Twitter Adapter命名方式接著再將 Plurk這個 Class增加幾個Method基本上只要有 run send reply就夠了而 run用來做初始化的部分

class Plurk entends Adapter

send (plurk id strings⋯) -gt

reply (plurk id strings⋯) -gt

run -gt

看起來有點東西接著來處理主要的 Plurk API結合部分

class PlurkStreaming extends EventEmitter

consuctor (options) -gt

plurk (callback) -gt

觀察河道

getChannel -gt

取得 Comet 網址

reply (plurk id message) -gt

回噗

acceptFriends -gt

接受好友

get (path callback) -gt

GET 請求

post (path body callback)-gt

POST 請求(其實是裝飾)

request (method path body callback)-gt

主要的 OAuth 請求

comet (server callback)-gt

噗浪的 Comet 傳回是 JavaScript Callback 要另外處理後才會變成 JSON

然後把注意力集中到 constructor上先把建構子弄好

constructor (options) -gt

super()

if optionskey and optionssecret and optionstoken and optionstoken secret

key = optionskey

secret = optionssecret

token = optionstoken

token secret = optionstoken secret

83 建立 Robot跟 API 73

Nodejs中文電子書

建立 OAuth 連接

consumer = new oauthOAuth(

rdquohttpwwwplurkcomOAuthrequest tokenrdquo

rdquohttpwwwplurkcomOAuthaccess tokenrdquo

key

secret

rdquo10rdquo

rdquohttpwwwplurkcomOAuthauthorizerdquo

rdquoHMAC-SHA1rdquo

)

domain = rdquowwwplurkcomrdquo

初始化取得 Comet 網址

do getChannel

else

throw new Error(rdquo 參數不足需要 Key Secret Token Token Secretrdquo)

接著來處理 request這個 method

request (method path body callback) -gt

記錄一下這次的 Request

consolelog(rdquohttpdomainpathrdquo)

Callback 這邊先不丟進去要用另一種方式處理

request = consumerget(rdquohttpdomainpathrdquo token token secret null)

requeston rdquoresponserdquo (res) -gt

reson rdquodatardquo (chunk) -gt

parseResponse(chunk+rsquorsquo callback)

reson rdquoendrdquo (data) -gt

consolelog rdquoEnd Request pathrdquo

reson rdquoerrorrdquo (data) -gt

consolelog rdquoError rdquo + data

requestend()

處理資料

parseResponse = (data callback) -gt

if datalength gt 0

用 TryCatch 避免處理 JSON 出錯導致整個中斷

try

callback null JSONparse(data)

catch err

74 8 製作一個 Hubot的 Plurk Adapter

Nodejs中文電子書

consolelog(rdquoError Parse JSONrdquo + data err)

繼續執行

callback null data ||

大致上就是這樣上面程式的架構已經將整個 Hubot Plurk Adapter完成因為在測試時竟然因為噗浪 Lag而沒讀到完整的 Comet資料然後造成程式異常為了避免這個問題發生因此需要加上為了完美呈現需要再

加上 Comet的處理所以要使用到 EventEmitter的功能

comet (server callback) -gt

在 Callback 裡面會找不到自身所以設定區域變數

self =

記錄一下這次的 Request

consolelog(rdquo[Comet] serverrdquo)

Callback 這邊先不丟進去要用另一種方式處理

request = consumerget(rdquohttpdomainpathrdquo token token secret null)

requeston rdquoresponserdquo (res) -gt

reson rdquodatardquo (chunk) -gt

parseResponse(chunk+rsquorsquo callback)

reson rdquoendrdquo (data) -gt

consolelog rdquoEnd Request pathrdquo

請求結束發出事件通知可以進行下一次請求

selfemit rdquonextPlurkrdquo

reson rdquoerrorrdquo (data) -gt

consolelog rdquoError rdquo + data

requestend()

處理資料

parseResponse = (data callback) -gt

if datalength gt 0

用 trycatch 避免失敗中斷

try

去掉 JavaScript 的 Callback

data = datamatch(CometChannelscriptCallback((+))s)

jsonData = rdquordquo

if data

83 建立 Robot跟 API 75

Nodejs中文電子書

jsonData = JSONparse(data[1])

else

如果沒有任何 Match 嘗試直接 parse

jsonData = JSONparse(data)

catch err

consolelog(rdquo[Comet] Errorrdquo data err)

用 TryCatch 避免處理 JSON 出錯導致整個中斷

try

只傳入 json 的 data 部分

callback null jsonDatadata

catch err

consolelog(rdquo[Comet]Error Parse JSONrdquo + data err)

繼續執行

callback null data ||

後面的 get跟 post就簡單多了

get (path callback) -gt

request(rdquoGETrdquo path null callback)

post (path body callback) -gt

request(rdquoPOSTrdquo path body callback)

接著處理取的 Comet網址的 getChannel

getChannel -gt

self =

get rdquoAPPRealtimegetUserChannelrdquo (error data) -gt

if error

檢查是否有 comet server

if datacomet server

selfchannel = datacomet server

如果沒有 Channel Ready 就嘗試連接會失敗

selfemit(rsquochannel readyrsquo)

那麼先來處理 Plurk Adaper好處理的部份

send (plruk id strings⋯)-gt

跟 Reply 一樣直接交給 reply 做

reply plurk id strings⋯

76 8 製作一個 Hubot的 Plurk Adapter

Nodejs中文電子書

reply (plurk id strings⋯) -gt

stringsforEach (message) =gt

botreply(plruk id message)

接著把 run處理好就可以上線運作摟

run -gt

self =

options =

key processenvHUBOT PLURK KEY

secret processenvHUBOT PLURK SECRET

token processenvHUBOT PLURK TOKEN

token secret processenvHUBOT PLURK TOKEN SECRET

創建剛剛的 API

bot = new PlurkStreaming(options)

依照 Twitter 的 new RobotTextMessage 會沒有反應所以參考 hubot-minecraft 的方式

r = robotconstructor

處理噗浪河道訊息

doPlurk = (data)-gt

檢查是否為回噗

if dataresponse

datacontent raw = dataresponsecontent raw

datauser id = dataresponseuser id

確定有噗浪 ID 跟訊息

if dataplurk id and datacontent raw

selfreceive new rTextMessage(dataplurk id datacontent raw)

取得 Comet Server 完成開始第一次 Comet 連接

boton rdquochannel readyrdquo () -gt

botplurk selfdoPlurk

上一次 Comet 完成繼續 Polling

boton rdquonextPlurkrdquo ()-gt

botplurk selfdoPlurk

定時接受好友邀請

do botacceptFriends

bot = bot

83 建立 Robot跟 API 77

Nodejs中文電子書

終於完成 AdapterHubot專案裡面的 scripts資料夾內是互動部分不需要像 Adapter如此大費周章處理只新增檔案並且設計好對白之後就會回噗了我開發用的機器人在此大家可以去跟他玩玩[httpplurkcomelct9620 bot](httpplurkcomelct9620 bot)

84 原始資料提供

bull [製 作 一 個 Hubot 的 噗 浪 Adapter](http revo-skillfrosttw blog20120318create-a-hubot-plurk-adapter)

78 8 製作一個 Hubot的 Plurk Adapter

9

Nodejs好用工具介紹

91 logio

Real-time log monitoring in your browser

httplogioorg

nodejs (gt=0412 lt=0611)

npm config set unsafe-perm true

npm install -g --prefix=usrlocal logio

logio server start

httplocalhost8998

79

Nodejs中文電子書

80 9 Nodejs好用工具介紹

10

精選文章

101 Express

bull 使用 NodeJS + Express從 GETPOST Request取值 [邀稿中]

bull link2 [邀稿中]

bull link3 [邀稿中]

81

Nodejs中文電子書

82 10 精選文章

11

參考資源

111 Nodejs書籍

bull Programming Nodejs April 2012 (est) Travis Swicegood Pragmatic Bookshelf

bull e Little Book on CoffeeScript January 2012 Alex MacCaw OrsquoReilly Media

bull Node for Front-End Developers January 2012 Garann Means OrsquoReilly Media

bull Building Hypermedia APIs with HTML5 and Node November 2011 MikeAmundsen OrsquoReilly Media

bull Node Web Development August 2011 David Herron PacktPub

bull CoffeeScript Accelerated JavaScript Development July 2011 Trevor BurnhamPragmatic Bookshelf

bull Getting Started with GEO CouchDB and Nodejs July 2011 Mick omp-son OrsquoReilly Media

bull What Is Node July 2011 Brett McLaughlin OrsquoReilly Media

bull Node Up and Running May 2011 Tom Hughes-Croucher Mike WilsonOrsquoReilly Media

83

Nodejs中文電子書

bull Hands-on Nodejs e Nodejs introduction and API reference May 2011 Pe-dro Teixeira LeanPub

bull e Node Beginner Book Manuel Kiessling

bull Mastering Nodejs visionmedia

112 Nodejs影音教學

bull e Node Sessions e Best of OSCON 2011 August 2011 OrsquoReilly Media(Video)

bull Tom Hughes-Croucher on Node March 2011 OrsquoReilly Media (Video)

113 Nodejs教學網站

bull Node Tuts

114 Nodejs課程

bull Introduction To Nodejs Van Nguyen CodeLesson

84 11 參考資源

Page 9: Nodejs Wiki

Nodejs中文電子書

01 精選文章收錄流程

精選文章的用意是鼓勵作者在自已的網誌發表 Nodejs 教學再由Nodejs Taiwan社群挑選系列文章列入電子書的精選文集

1 將文章標題及連結貼到「精選文章」分類

2 社群工作小組以 E-Mail通知作者文章列入精選並邀請將內文授權給Nodejs Taiwan電子書分享

3 作者同意後由工作小組負責整理圖文發佈至電子書

4 以 E-Mail寄出感謝函通知原作者文章已收錄依作者意願調整文章內容

5 於 Nodejs Taiwan首頁及粉絲專頁推薦作者的文章

01 精選文章收錄流程 3

Nodejs中文電子書

4 關於本書

前言

Nodejs是 JavaScript程式語言的開發框架由 04x至目前 v060版本核心上已經有許多變更當然目的只有一個就是讓開發變得更簡單速

度能夠更快這是所有人奮鬥的目標而 nodeJS 華文維基平台主力由nodeJStw一群喜好 javascript開發者共同主筆內文以中文為主希望能夠降低開發者學習門檻藉由我們拋磚引玉讓更多華人開發者共同學習

討論

CoffeeScript好好玩

5

Nodejs中文電子書

6 前言

1

Nodejs簡介

Nodejs是一個高效能易擴充的網站應用程式開發框架 (Web Applica-tion Framework)它誕生的原因是為了讓開發者能夠更容易開發高延展性的網路服務不需要經過太多複雜的調校效能調整及程式修改就能

滿足網路服務在不同發展階段對效能的要求

Ryan Dahl是NodeJS的催生者目前任職於 Joyent (主機託管服務公司)他開發 NodeJS的目的就是希望能解決 Apache在連線數量過高時緩衝區 (buffer)和系統資源會很快被耗盡的問題希望能建立一個新的開發框架以解決這個問題因此嘗試使用效能十分優秀的 V8 JavaScript Engine讓網站開發人員熟悉的 JavaScript程式語言也能應用於後端服務程式的開發並且具有出色的執行效能

JavaScript是功能強大的物件導向程式語言但是在 JavaScript的官方規格中主要是定義網頁 (以瀏覽器為基礎) 應用程式需要的應用程式介面(API)對 JavaScript程式的應用範圍有所侷限為使 JavaScript能夠在更多用途發展CommonJS規範一組標準函式庫 (standard library)使 JavaScript的應用範圍能夠和 RubyPython及 Java等語言同樣豐富撰寫 NodeJS的JavaScript程式碼符合 CommonJS規範可以使用 CommonJS API為基礎開發程式並且在不同的 CommonJS兼容 (compliant) JavaScript執行環境中程式碼具有可攜性

7

Nodejs中文電子書

瀏覽器的 JavaScript與實現 CommonJS規範的 NodeJS有何不同呢瀏覽器的 JavaScript 提供 XMLHttpRequest 讓程式可以和網頁伺服器建立資料傳輸連線但這通常只能適用於網站開發的需求因為我們只能用

XMLHttpRequest與網頁伺服器通訊卻無法利用它建立其他類型如 Telnet FTP NTP的伺服器通訊如果我們想開發網路服務程式例如 SMTP電子郵件伺服器就必須使用 Sockets建立 TCP (某些服務則用 UDP)監聽及連線其他程式語言如 PHPJavaPythonPerl及 Ruby等在標準開發環境中皆有提供 Sockets API而瀏覽器的 JavaScript基於安全及貼近網站設計需求的考量下並未將 Sockets列入標準函式庫之中CommonJS的規範填補這種基礎函式庫功能的空缺遵循 CommonJS規範的 NodeJS可以直接使用 Sockets API建立各種網路服務程式

JavaScript語言本身支援 Lambda的特性因此一個匿名函式 (anonymousfunction)可以被儲存成一個變數並當作參數傳遞給另一個函式

var proc1 = function(op x) return op(x)

var op1 = function(x) return x+1 var op2 = function(x) return xx

proc1(op1 3) result is 4 proc1(op2 5) result is 25

另一個 JavaScript開發者必須掌握的語言特性稱為 Closure

NodeJS符合 CommonJS的規範使得 Callback方式易於實現也能夠讓更多同好基於 JavaScript開發符合 NodeJS的外掛模組 (Module)

回想以前要寫一個能夠同時容納上百人的上線的網路服務需要花費

多大的苦工可能 10人多就需要經過一次程式調整而 NodeJS就是為了解決這個困境NodeJS因此誕生它是一種利用 V8 Javascript編譯器所開發的產品利用 V8編譯器的高效能與 Javascript的程式開發特性所產生的網路程式

開發人員所編寫出來的 Javascript腳本程式怎麼可能會比其他語言寫出來的網路程式還要快上許多呢以前的網路程式原理是將使用者每次的

連線 (connection)都開啟一個執行緒 (thread)當連線爆增的時候將會快速耗盡系統效能並且容易產生阻塞 (block)的發生

8 1 Nodejs簡介

Nodejs中文電子書

NodeJS對於資源的調校有所不同當程式接收到一筆連線 (connection)會通知作業系統透過 epoll kqueue devpoll或 select將連線保留並且放入heap中配置先讓連線進入休眠 (Sleep)狀態當系統通知時才會觸發連線的 callback這種處理連線方式只會佔用掉記憶體並不會使用到 CPU資源另外因為採用 Javascript語言的特性每個 request都會有一個 callback如此可以避免發生 Block的狀況發生

基於 Callback特性目前NodeJS大多應用於 Comet(long pulling) RequestServer或者是高連線數量的網路服務上目前也有許多公司將 NodeJS設為內部核心網路服務之一在 NodeJS也提供了外掛管理 (Node packagemanagement)讓愛好 NodeJS輕易開發更多有趣的服務外掛並且提供到 npm讓全世界使用者快速安裝使用

本書最後執行測試版本為 nodejs v068相關 API文件可查詢 lsquohttpnodejsorg lthttpnodejsorggtlsquo本書所有範例均可於 linux windows上執行如遇到任何問題歡迎至 lsquohttpnodejstw lthttpnodejstwgtlsquo詢問對於nodejs相關問題

9

Nodejs中文電子書

10 1 Nodejs簡介

2

JavaScript與 NodeJS

其實使用 JavaScript 在網頁端與伺服器端的差距並不大但是為了使NodeJS可以發揮他最強大的能力有一些知識還是必要的所以還是針對這些主題介紹一下其中 Event LoopScope以及 Callback其實是比較需要了解的基本知識cpscurrying ow control是更進階的技巧與應用

21 Event Loop

可能很多人在寫 Javascript 時並不知道他是怎麼被執行的這個時候可以參考一下 jQuery作者 John Resig一篇好文章介紹事件及 timer怎麼在瀏覽器中執行How JavaScript Timers Work通常在網頁中所有的Javascript執行完畢後(這部份全部都在 global scope跑除非執行函數)接下來就是如 John Resig解釋的這樣所有的事件處理函數以及 timer執行的函數會排在一個 queue結構中利用一個無窮迴圈不斷從 queue中取出函數來執行這個就是 event loop

(除了 John Resig的那篇文章Nicholas C Zakas的 ldquoProfessional Javascriptfor Web Developer 2nd editionrdquo 有一個試閱本httpyuiblogcomassetspdfzakas-projs-2ed-ch18pdf598頁剛好也有簡短的說明)

所以在 Javascript中雖然有非同步但是他並不是使用執行緒所有

11

Nodejs中文電子書

的事件或是非同步執行的函數都是在同一個執行緒中利用 event loop的方式在執行至於一些比較慢的動作例如 IO網頁 render re ow等實際動作會在其他執行緒跑等到有結果時才利用事件來觸發處理函數來處理

這樣的模型有幾個好處沒有執行緒的額外成本所以反應速度很快不會

有任何程式同時用到同一個變數不必考慮 lock也不會產生 dead lock所以程式撰寫很簡單但是也有一些潛在問題任一個函數執行時間較長都

會讓其他函數更慢執行(因為一個跑完才會跑另一個)在多核心硬體普遍

的現在無法用單一的應用程式 instance發揮所有的硬體能力用 NodeJS撰寫伺服器程式碰到的也是一樣的狀況要讓系統發揮 event loop的效能就要盡量利用事件的方式來組織程式架構另外對於一些有可能較為耗

時的操作可以考慮使用 processnextTick函數來讓他以非同步的方式執行避免在同一個函數中執行太久擋住所有函數的執行

如果想要測試 event loop怎樣在「瀏覽器」中運行可以在函數中呼叫alert()這樣會讓所有 Javascript的執行停下來尤其會干擾所有使用 timer的函數執行有一個簡單的例子這是一個會依照設定的時間間隔嚴格執

行動作的動畫如果時間過了就會跳過要執行的動作點按圖片以後人

物會快速旋轉但是在旋轉執行完畢前按下「delay」按鈕讓 alert訊息等久一點接下來的動畫就完全不會出現了

22 Scope與 Closure

要快速理解 JavaScript的 Scope(變數作用範圍)原理只要記住他是Lexical Scope就差不多了簡單地說變數作用範圍是依照程式定義時(或者叫做程式文本)的上下文決定而不是執行時的上下文決定

為了維護程式執行時所依賴的變數即使執行時程式運行在原本的

scope之外他的變數作用範圍仍然維持不變這時程式依賴的自由變數(定義時不是 local的而是在上一層 scope定義的變數)一樣可以使用就好像被關閉起來所以叫做 Closure用程式看比較好懂

function outter(arg1)

arg1 及 free_variable1 對 inner 函數來說都是自由變數

var free_variable1 = 3

return function inner(arg2)

12 2 JavaScript與 NodeJS

Nodejs中文電子書

var local_variable1 =2arg2 及 local_variable1 對 inner 函數來說都是本地變數

return arg1 + arg2 + free_variable + local_variable1

var a = outter(1)變數 a就是 outter函數執行後返回的 inner函數 var b =a(4)執行 inner函數執行時上下文已經在 outter函數之外但是仍然能正常執行而且可以使用定義在 outter函數裡面的 arg1及 free variable1變數 consolelog(b)結果 10

在 Javascript中scope最主要的單位是函數(另外有 global及 eval)所以有可能製造出 closure的狀況通常在形式上都是有巢狀的函數定義而且內側的函數使用到定義在外側函數裡面的變數

Closure有可能會造成記憶體洩漏主要是因為被參考的變數無法被垃圾收集機制處理造成佔用的資源無法釋放所以使用上必須考慮清楚

不要造成意外的記憶體洩漏(在上面的例子中如果 a一直未執行使用到的記憶體就不會被釋放)

跟透過函數的參數把變數傳給函數比較起來Javascript Engine會比較難對 Closure進行最佳化如果有效能上的考量這一點也需要注意

23 Callback

要介紹 Callback之前要先提到 JavaScript的特色

JavaScript是一種函數式語言(functional language)所有 Javascript語言內的函數都是高階函數 (higher order function這是數學名詞計算機用語好像是 rst class function意指函數使用沒有任何限制與其他物件一樣)也就是說函數可以作為函數的參數傳給函數也可以當作函數的返回值這個特性讓 Javascript的函數使用上非常有彈性而且功能強大

callback在形式上其實就是把函數傳給函數然後在適當的時機呼叫傳入的函數Javascript使用的事件系統通常就是使用這種形式NodeJS中有一個物件叫做 EventEmitter這是 NodeJS事件處理的核心物件所有會使用事件處理的函數都會「繼承」這個物件(這裡說的繼承實

作上應該像是 mixin)他的使用很簡單可以使用物件on(事件名稱callback

23 Callback 13

Nodejs中文電子書

函數) 或是物件addListener(事件名稱callback 函數) 把你想要處理事件的函數傳入在物件中可以使用物件emit(事件名稱 參數) 呼叫傳入的callback函數這是 Observer Pattern的簡單實作而且跟在網頁中使用 DOM的 addEventListener使用上很類似也很容易上手不過 NodeJS是大量使用非同步方式執行的應用所以程式邏輯幾乎都是寫在 callback函數中當邏輯比較複雜時大量的 callback會讓程式看起來很複雜也比較難單元測試舉例來說

var p client = new Db(lsquointegration tests 20rsquo new Server(ldquo127001rdquo 27017) lsquopkrsquoCustomPKFactory) p clientopen(function(err p client)

p clientdropDatabase(function(err done)

p clientcreateCollection(lsquotest custom keyrsquo function(err collection)

collectioninsert(lsquoarsquo1 function(err docs)

collection nd(lsquo idrsquonew ObjectID(ldquoaaaaaaaaaaaardquo) function(err cursor)

cursortoArray(function(err items) testassertEquals(1 itemslength) p clientclose()

)

)

)

)

)

)

這是在網路上看到的一段操作 mongodb的程式碼為了循序操作所以必須在一個 callback裡面呼叫下一個動作要使用的函數這個函數裡面還是會使用 callback最後就形成一個非常深的巢狀

這樣的程式碼會比較難進行單元測試有一個簡單的解決方式是

盡量不要使用匿名函數來當作 callback或是 event handler透過這樣的方式

14 2 JavaScript與 NodeJS

Nodejs中文電子書

就可以對各個 handler做單元測試了例如

var http = require(lsquohttprsquo) var tools =

cookieParser function(request response) if(requestheaders[rsquoCookiersquo]) do parsing

var server = httpcreateServer(function(request response)

thisemit(lsquoinitrsquo request response)

) serveron(lsquoinitrsquo toolscookieParser) serverlisten(8080 lsquo127001rsquo)

更進一步可以把 tools改成外部 module例如叫做 toolsjs

moduleexports = cookieParser function(request response) if(requestheaders[rsquoCookiersquo]) do parsing

然後把程式改成

var http = require(lsquohttprsquo)

var server = httpcreateServer(function(request response) thisemit(lsquoinitrsquo re-quest response)

) serveron(lsquoinitrsquo require(lsquo toolsrsquo)cookieParser) serverlisten(8080lsquo127001rsquo)

這樣就可以單元測試 cookieParser了例如使用 nodeunit時可以這樣寫

var testCase = require(lsquonodeunitrsquo)testCase moduleexports = testCase(

ldquosetUprdquo function(cb) thisrequest = headers Cookielsquoname1val1 name2val2rsquo thisresponse = thisresult= name1rsquoval1rsquoname2rsquoval2rsquo

cb()

ldquotearDownrdquo function(cb)

cb()

23 Callback 15

Nodejs中文電子書

ldquonormal caserdquo function(test)

testexpect(1) var obj = require(lsquotoolsrsquo)cookieParser(thisrequest thisresponse)testdeepEqual(obj thisresult) testdone()

)

善於利用模組可以讓程式更好維護與測試

24 CPS(Continuation-Passing Style)

cps 是 callback 使用上的特例形式上就是在函數最後呼叫 callback這樣就好像把函數執行後把結果交給 callback 繼續運行所以稱作continuation-passing style利用 cps可以在非同步執行的情況下透過傳給 callback的這個 cps callback來獲知 callback執行完畢或是取得執行結果例如

lthtmlgt ltbodygt ltdiv id=rdquopanelrdquo style=rdquovisibilityhiddenrdquogtlt divgtlt bodygt lt htmlgt ltscriptgt var request = new XMLHttpRequest() re-questopen(lsquoGETrsquo lsquotest749txt timestamp=rsquo+new Date()getTime() true) re-questaddEventListener(lsquoreadystatechangersquo function(next)

return function() if(thisreadyState===4ampampthisstatus===200) next(thisresponseText)lt== 傳入的 cps callback 在動作完成時執行並取得結果進一步處理

(function(str)lt==這個匿名函數就是 cps callback

documentgetElementById(lsquopanelrsquo)innerHTML=str docu-mentgetElementById(lsquopanelrsquo)stylevisibility = lsquovisiblersquo

) false) requestsend() ltscriptgt

進一步的應用也可以參考 2-6流程控制

16 2 JavaScript與 NodeJS

Nodejs中文電子書

25 函數返回函數與 Currying

前面的 cps範例裡面使用了函數返回函數這是為了把 cps callback傳遞給 onreadystatechange事件處理函數的方法(因為這個事件處理函數並沒有設計好會傳送接收這樣的參數)實際會執行的事件處理函數其實是內層返回的那個函數之外包覆的這個函數主要是為了利用 Closure把 next傳給內層的事件處理函數這個方法更常使用的地方是為了解決一些

scope問題例如

ltscriptgt var accu=0count=10 for(var i=0 iltcount i++)

setTimeout(

function() countndash accu+=i if(countlt=0)

consolelog(accu)

50)

ltscriptgt

最後得出的結果會是 100而不是想像中的 45這是因為等到 setTime-out指定的函數執行時變數 i已經變成 10而離開迴圈了要解決這個問題就需要透過 Closure來保存變數 i

ltscriptgt var accu=0count=10 for(var i=0 iltcount i++)

setTimeout(

function(i) return function() countndash

accu+=i if(countlt=0)

consolelog(accu)

(i)

50)

25 函數返回函數與 Currying 17

Nodejs中文電子書

淺藍色底色的部份是跟上面例子不一樣的地方 ltscriptgt

函數返回函數的另外一個用途是可以暫緩函數執行例如

function add(m n) return m+n

var a = add(20 10) consolelog(a)

add這個函數必須同時輸入兩個參數才有辦法執行如果我希望這個函數可以先給它一個參數等一些處理過後再給一個參數然後得到結

果就必須用函數返回函數的方式做修改

function add(m)

return function(n) return m+n

var wait another arg = add(20)先給一個參數 var a = function(arr)

var ret=0 for(var i=0iltarrlengthi++) ret+=arr[i] return ret

([1234])計算一下另一個參數 var b = wait another arg(a)然後再繼續執行 consolelog(b)

像這樣利用函數返回函數使得原本接受多個參數的函數可以一次

接受一個參數直到參數接收完成才執行得到結果的方式有一個學名就

叫做Currying

綜合以上許多奇技淫巧就可以透過用函數來處理函數的方式調整

程式流程接下來看看

26 流程控制

(以 sync方式使用 async函數避開巢狀 callback循序呼叫 async callback等奇技淫巧)

建議參考

bull httphowtonodeorgcontrol- ow

bull httphowtonodeorgcontrol- ow-part-ii

18 2 JavaScript與 NodeJS

Nodejs中文電子書

bull httphowtonodeorgcontrol- ow-part-iii

bull httpblogmixunet20110202essential-node-js-patterns-and-snippets

這幾篇都是非常經典的 NodeJSJavascript流程控制好文章(阿mixu是在介紹一些 pattern時提到這方面的主題)不過我還是用幾個簡單的程式介紹一下做法跟概念

並發與等待

下面的程式參考了 mixu文章中的做法

var wait = function(callbacks done) consolelog(lsquowait startrsquo) var counter = call-backslength var results = [] var next = function(result) 接收函數執行結果並判斷是否結束執行 resultspush(result) if(ndashcounter == 0) done(results)如果結束執行就把所有執行結果傳給指定的 callback處理 for(var i = 0 i lt callbackslength i++) 依次呼叫所有要執行的函數 callbacks[i](next) consolelog(lsquowait endrsquo)

wait( [ function(next) setTimeout(function() consolelog(lsquodone arsquo) var result =500 next(result) 500) function(next) setTimeout(function() con-solelog(lsquodone brsquo) var result = 1000 next(result) 1000) function(next)setTimeout(function() consolelog(lsquodone crsquo) var result = 1500 next(1500) 1500) ] function(results) var ret = 0 i=0 for( iltresultslength i++) ret+= results[i] consolelog(lsquodone all result lsquo+ret)

)

執行結果wait start wait end done a done b done c done all result 3000

可以看出來其實 wait並不是真的等到所有函數執行完才結束執行而是在所有傳給他的函數執行完畢後(不論同步非同步)才執行處理結

果的函數(也就是 done())

不過這樣的寫法還不夠實用因為沒辦法實際讓函數可以等待執行

完畢又能當作事件處理函數來實際使用上面參考到的 Tim Caswell的文

26 流程控制 19

Nodejs中文電子書

章裡面有一種解法不過還需要額外包裝(在他的例子中)NodeJS核心的 fs物件把一些函數(例如 readFile)用 Currying處理類似像這樣

var fs = require(lsquofsrsquo) var readFile = function(path)

return function(callback errback)

fsreadFile(path function(err data)

if(err) errback()

else callback(data)

)

其他部份可以參考 Tim Caswell的文章他的 Doparallel跟上面的 wait差不多意思這裡只提示一下他沒說到的地方

另外一種做法是去修飾一下 callback當他作為事件處理函數執行後再用 cps的方式取得結果

ltscriptgt function Wait(fns done)

var count = 0 var results = [] thisgetCallback = function(index)

count++ return (function(waitback)

return function() var i=0args=[]for(iltargumentslengthi++)

argspush(arguments[i])

argspush(waitback) fns[index]apply(thisargs)

)(function(result) resultspush(result) if(ndashcount == 0)

20 2 JavaScript與 NodeJS

Nodejs中文電子書

done(results)

)

var a = new Wait(

[ function(waitback) consolelog(lsquodone arsquo) var result = 500 wait-back(result) function(waitback) consolelog(lsquodone brsquo) var result =1000 waitback(result) function(waitback) consolelog(lsquodone crsquo) varresult = 1500 waitback(result) ] function(results) var ret = 0 i=0for( iltresultslength i++) ret += results[i] consolelog(lsquodone allresult lsquo+ret)

) var callbacks = [agetCallback(0)agetCallback(1)agetCallback(0)agetCallback(2)]一次取出要使用的 callbacks避免結果提早送出 setTimeout(callbacks[0]500) setTimeout(callbacks[1] 1000) setTimeout(callbacks[2] 1500) setTime-out(callbacks[3] 2000) 當所有取出的 callbacks執行完畢就呼叫 done()來處理結果 ltscriptgt

執行結果

done a done b done a done c done all result 3500

上面只是一些小實驗更成熟的作品是 Tim Caswell 的 stephttpsgithubcomcreationixstep

如果希望真正使用同步的方式寫非同步則需要使用 Promisejs這一類的 library來轉換非同步函數不過他結構比較複雜 XD(見仁見智不過有些人認為 Promise有點過頭了)httpblogsmsdncombrbucktonarchive20110815promise-js-2-0-promise-framework-for-javascriptaspx

如果想不透過其他 Library做轉換又能直接用同步方式執行非同步函數大概就要使用一些需要額外 compile原始程式碼的方法了例如 BrunoJouhier的 streamlinejshttpsgithubcomSagestreamlinejs

26 流程控制 21

Nodejs中文電子書

循序執行

循序執行可以協助把非常深的巢狀 callback結構攤平例如用這樣的簡單模組來做(serialjs)

moduleexports = function(funs) var c = 0 if(isArrayOfFunctions(funs))

throw(lsquoArgument type was not matched Should be array of func-tionsrsquo)

return function()

var args = Arrayprototypeslicecall(arguments 0) if((cgt=funslength))

c++ return funs[c-1]apply(this args)

function isArrayOfFunctions(f ) if(typeof f == lsquoobjectrsquo) return false if(flength)return false if(fconcat) return false if(fsplice) return false var i = 0 for(iltflength i++)

if(typeof f[i] == lsquofunctionrsquo) return false

return true

簡單的測試範例(testSerialjs)使用 fs 模組確定某個 path 是檔案然後讀取印出檔案內容這樣會用到兩層的 callback所以測試中有使用serial的版本與 nested callbacks的版本做對照

var serial = require(lsquoserialrsquo) fs = require(lsquofsrsquo) path = lsquodclientjsrsquo cb = serial([ func-tion(err data)

if(err)

if(dataisFile) fsreadFile(path cb)

22 2 JavaScript與 NodeJS

Nodejs中文電子書

else consolelog(err)

function(err data)

if(err) consolelog(lsquo[ attened by searial]rsquo) con-solelog(datatoString(lsquoutf8rsquo))

else consolelog(err)

]) fsstat(path cb)

fsstat(path function(err data) 第一層 callback if(err)

if(dataisFile)

fsreadFile(path function(err data) 第二層 callback if(err)

consolelog(lsquo[nested callbacks]rsquo) con-solelog(datatoString(lsquoutf8rsquo))

else consolelog(err)

)

else consolelog(err)

)

關鍵在於這些 callback的執行是有順序性的所以利用 serial返回的一個函數 cb來取代這些 callback然後在 cb中控制每次會循序呼叫的函數

26 流程控制 23

Nodejs中文電子書

就可以把巢狀的 callback攤平成循序的 function陣列(就是傳給 serial函數的參數)

測試中的dclientjs 是一個簡單的 dnode 測試程式放在跟 testSerialjs同一個目錄

var dnode = require(lsquodnodersquo)

dnodeconnect(8000 lsquolocalhostrsquo function(remote)

remoterestart(function(str) consolelog(str) processexit()

)

)

執行測試程式後出現結果

[ attened by searial] var dnode = require(lsquodnodersquo)

dnodeconnect(8000 lsquolocalhostrsquo function(remote)

remoterestart(function(str) consolelog(str) processexit()

)

)

[nested callbacks] var dnode = require(lsquodnodersquo)

dnodeconnect(8000 lsquolocalhostrsquo function(remote)

remoterestart(function(str) consolelog(str) processexit()

)

)

對照起來看兩種寫法的結果其實是一樣的但是利用 serialjs巢狀的 callback結構就會消失

不過這樣也只限於順序單純的狀況如果函數執行的順序比較複雜

(不只是一直線)還是需要用功能更完整的流程控制模組比較好例如

httpsgithubcomcaolanasync

24 2 JavaScript與 NodeJS

3

Nodejs安裝與設定

本篇將講解如何在各個不同 OS建立 NodeJS環境目前 NodeJS 048版本環境架設方式需依賴 Linux指令才可編譯完成當然在不同作業系統中也已經有 NodeJS package可以直接使用指令快速架設以下各不同作業系統解說如何安裝 NodeJS

31 Ubuntu Linux

更新推薦使用 nvm

git clone gitgithubcomcreationixnvmgit ~nvm

echo rdquo ~nvmnvmshrdquo gtgt ~bashrc

nvm install v0614

nvm alias default v0614

以 上 可 參 考 http dreamerslabcom blog tw how-to-setup-a-node-js-development-environment-on-ubuntu-11-04

使用 APT套件管理工具是常見的方法以下是使用社群提供的 PPA安裝方式

sudo apt-get install python-software-properties

sudo add-apt-repository ppachris-leanodejs-devel

25

Nodejs中文電子書

sudo apt-get update

sudo apt-get install nodejs

32 Other Linux

Linux很適合作為 NodeJS的伺服器作業系統及開發環境安裝前請先確認以下套件已正確安裝

bull curl (wget)用來下載檔案的工具

bull git先進的版本控制工具

bull g++ GNU C++軟體編譯工具

bull make GNU軟體專案建置工具

安裝指令如下如設有權限問題請在指令前面加上 sudo

git clone httpsgithubcomjoyentnodegit

cd node

git checkout v067

configure

make

sudo make install

接著測試 nodeJS是否正常執行

node --version

出現版本訊息即表示安裝成功

33 Windows

nodeJS 在 v060 版本之後開始正式支援 windows native直接使用nodeexe 就可以執行程式支援性完全與 linux 相同更棒的部份就是不需經過編譯經過下載之後簡單設定完成立即開發 node程式

26 3 Nodejs安裝與設定

Nodejs中文電子書

下載 nodejs安裝檔案 lthttpnodejsorgdownloadgt

如此完成 windows native nodeexe安裝接著可以進入 command line執行測試在 command line輸入rdquonoderdquo會出現 nodejs版本訊息畫面表示安裝完成

33 Windows 27

Nodejs中文電子書

28 3 Nodejs安裝與設定

4

Nodejs基礎

前篇文章已經由介紹安裝至設定都有完整介紹nodeJS 內部除了javascript常用的函式 (function)物件 (object)之外也有許多不同的自訂物件nodeJS預設建立這些物件為核心物件是為了要讓開發流程更為這些資料在官方文件已經具有許多具體說明接下來將會介紹在開發 nodeJS程式時常見的物件特性與使用方法

41 nodejs http伺服器建立

在 lsquonodejs官方網站 lthttpnodejsorggtlsquo裡面有舉一個最簡單的 HTTP伺服器建立一開始初步就是建立一個伺服器平台讓 nodejs可以與瀏覽器互相行為每種語言一開始的程式建立都是以 Hello world開始最初也從 Hello world帶各位進入 nodejs的世界

輸入以下程式碼儲存檔案為 node basic http hello worldjs

var server

ip = rdquo127001rdquo

port = 1337

http = require(rsquohttprsquo)

server = httpcreateServer(function (req res)

29

Nodejs中文電子書

reswriteHead(200 rsquoContent-Typersquo rsquotextplainrsquo)

resend(rsquoHello Worldnrsquo)

)

serverlisten(port ip)

consolelog(rdquoServer running at httprdquo + ip + rdquordquo + port)

程式碼解講一開始需要有幾個基本的變數

bull ip 機器本身的 ip位置因為使用本地端因此設定為 127001

bull port 需要開通的阜號通常設定為 http port 80因範例不希望與基本 port相衝隨意設定為 1337

在 nodejs 的程式中有許多預設的模組可以使用因此需要使用require方法將模組引入在這邊我們需要使用 http這個模組因此將 http載入Http模組裡面內建有許多方法可以使用這邊採用 createServer創建一個基本的 http伺服器再將 http伺服器給予一個 server變數裡面的回呼函式 (call back function)可以載入 http伺服器的資料與回應方法 (requestresponse)在程式裡面就可以看到我們直接回應給瀏覽器端所需的 Header回應內容

reswriteHead(200 rsquoContent-Typersquo rsquotextplainrsquo)

resend(rsquoHello Worldnrsquo)

Http伺服器需要設定 port ip在最後需要設定 Http監聽需要使用到listen事件監聽所有 Http伺服器行為

httplisten(port ip)

所有事情都完成之後需要確認伺服器正確執行因此使用 console在javascript裡就有這個原生物件console所印出的資料都會顯示於 nodejs伺服器頁面這邊印出的資料並不會傳送到使用者頁面上之後許多除壞

(debug)都會用到 console物件

consolelog(rdquoServer running at httprdquo + ip + rdquordquo + port)

30 4 Nodejs基礎

Nodejs中文電子書

42 nodejs http路徑建立

前面已經介紹如何建立一個簡單的 http伺服器接下來本章節將會介紹如何處理伺服器路徑 (rout)問題在 http的協定下所有從瀏覽器發出的要求 (request)都需要經過處理路徑上的建立也是如此

路徑就是指伺服器 ip位置或者是網域名稱之後對於伺服器給予的要求修改剛才的 hello world檔案修改如下

server = httpcreateServer(function (req res)

consolelog(requrl)

reswriteHead(200 rsquoContent-Typersquo rsquotextplainrsquo)

resend(rsquohello worldnrsquo)

)

重新啟動 nodejs程式後在瀏覽器端測試一下路徑行為結果如下圖

當在瀏覽器輸入 http1270011337test 在伺服器端會收到兩個要求一個是我們輸入的test要求另外一個則是faviconicotest的路徑要求http伺服器本身需要經過程式設定才有辦法回應給瀏覽器端所需要的回應在伺服器中所有的路徑要求都是需要被解析才有辦法取得資料從

42 nodejs http路徑建立 31

Nodejs中文電子書

上面解說可以了解到在 nodejs當中所有的路徑都需要經過設定未經過設定的路由會讓瀏覽器無法取得任何資料導致錯誤頁面的發生底下將會解

說如何設定路由同時避免發生錯誤情形先前 nodejs程式需要增加一些修改才能讓使用者透過瀏覽器在不同路徑時有不同的結果根據剛才

的程式做如下的修改

var server

ip = rdquo127001rdquo

port = 1337

http = require(rsquohttprsquo)

url = require(rsquourlrsquo)

path

server = httpcreateServer(function (req res)

path = urlparse(requrl)

reswriteHead(200 rsquoContent-Typersquo rsquotextplainrsquo)

switch (pathpathname)

case rdquoindexrdquo

resend(rsquoI am indexnrsquo)

break

case rdquotestrdquo

resend(rsquothis is test pagenrsquo)

break

default

resend(rsquodefault pagenrsquo)

break

)

serverlisten(port ip)

consolelog(rdquoServer running at httprdquo + ip + rdquordquo + port)

程式做了片段的修改首先載入 url模組另外增加一個 path變數url模組就跟如同他的命名一般專門處理 url字串處理裡面提供了許多方法來解決路徑上的問題因為從瀏覽器發出的要求路徑可能會帶有多種需求

或者 GET參數組合等因此我們需要將路徑單純化取用路徑部分的資料即可例如使用者可能會送出 http1270011337testsend=1如果直接

32 4 Nodejs基礎

Nodejs中文電子書

信任 requrl就會收到結果為testsend=1所以需要透過 url模組的方法將路徑資料過濾

在這邊使用 urlparse的方法裡面帶入網址格式資料會回傳路徑資料為了後需方便使用將回傳的資料設定到 path變數當中在回傳的路徑資料裡面包含資訊如下圖

這邊只需要使用單純的路徑要求直接取用 pathpathname就可以達到我們的目的

最後要做路徑的判別在不同的路徑可以指定不同的輸出在範例中

有三個可能結果第一個從瀏覽器輸入index就會顯示 index結果test 就會呈現出 test頁面最後如果都不符合預期的輸入會直接顯示 default的頁面最後的預防可以讓瀏覽器不會出現非預期結果讓程式的可靠性提昇

底下為測試結果

42 nodejs http路徑建立 33

Nodejs中文電子書

43 nodejs檔案讀取

前面已經介紹如何使用路由(rount)做出不同的回應實際應用只有在瀏覽器只有輸出幾個文字資料總是不夠的在本章節中將介紹如何使

用檔案讀取輸出檔案資料讓使用者在前端瀏覽器也可以讀取到完整的

html css javascript檔案輸出

檔案管理最重要的部分就是 lsquoFile system lthttpnodejsorgdocslatestapifshtmlgtlsquo這個模組此模組可以針對檔案做管理監控讀取等行為裡面有許多預設的方法底下是檔案輸出的基本範例底下會有兩個檔案

第一個是靜態 html檔案

ltDOCTYPE htmlgt

lthtml xmlns=rdquohttpwwww3org1999xhtmlrdquo xmllang=rdquozh-CNrdquo lang=rdquozh-CNrdquogt

ltheadgt

ltmeta http-equiv=rdquoContent-Typerdquo content=rdquotexthtml charset=UTF-7rdquo gt

lttitlegtnodejs index html filelttitlegt

ltheadgt

ltbodygt

34 4 Nodejs基礎

Nodejs中文電子書

lth1gtnodejs index html filelth1gt

ltbodygt

lthtmlgt

另一個為 nodejs程式

var fs = require(rdquofsrdquo)

filename = rdquostaticindexhtmlrdquo

encode = rdquoutf8rdquo

fsreadFile(filename encode function(err file)

consolelog(file)

)

一開始直接載入 le system模組 載入名稱為 fs讀取檔案主要使

用的方法為 readFile裡面以三個參數路徑 ( le path) 編碼方式 (encoding)

回應函式 (callback)路徑必須要設定為靜態 html所在位置才能指定到正確的檔案靜態檔案的編碼方式也必須正確這邊使用靜態檔案的編

碼為 utf8如果編碼設定錯誤nodejs讀取出來檔案結果會使用 byte raw格式輸出如果錯誤編碼格式會導致輸出資料為 byte raw

回應函式中裡面會使用兩個變數error為錯誤資訊如果讀取的檔案不存在或者發生錯誤error數值會是 true如果成功讀取資料 error將會是 falsecontent則是檔案內容資料讀取後將會把資料全數丟到 content這個變數當中

最後程式的輸出結果畫面如下

43 nodejs檔案讀取 35

Nodejs中文電子書

44 nodejs http靜態檔案輸出

前面已經了解如何讀取本地端檔案接下來將配合 http伺服器路由讓每個路由都能夠輸出相對應的靜態 html檔案首先新增加幾個靜態 html檔案

ltDOCTYPE htmlgt

lthtml xmlns=rdquohttpwwww3org1999xhtmlrdquo xmllang=rdquozh-CNrdquo lang=rdquozh-CNrdquogt

ltheadgt

ltmeta http-equiv=rdquoContent-Typerdquo content=rdquotexthtml charset=UTF-7rdquo gt

lttitlegtnodejs index html filelttitlegt

ltheadgt

ltbodygt

lth1gtnodejs index html filelth1gt

ltbodygt

lthtmlgt

ltDOCTYPE htmlgt

lthtml xmlns=rdquohttpwwww3org1999xhtmlrdquo xmllang=rdquozh-CNrdquo lang=rdquozh-CNrdquogt

ltheadgt

ltmeta http-equiv=rdquoContent-Typerdquo content=rdquotexthtml charset=UTF-7rdquo gt

lttitlegtnodejs test html filelttitlegt

ltheadgt

ltbodygt

lth1gtnodejs test html filelth1gt

ltbodygt

lthtmlgt

ltDOCTYPE htmlgt

lthtml xmlns=rdquohttpwwww3org1999xhtmlrdquo xmllang=rdquozh-CNrdquo lang=rdquozh-CNrdquogt

ltheadgt

ltmeta http-equiv=rdquoContent-Typerdquo content=rdquotexthtml charset=UTF-7rdquo gt

lttitlegtnodejs static html filelttitlegt

ltheadgt

ltbodygt

lth1gtnodejs static html filelth1gt

ltbodygt

lthtmlgt

36 4 Nodejs基礎

Nodejs中文電子書

準備一個包含基本路由功能的 http伺服器

var server

ip = rdquo127001rdquo

port = 1337

http = require(rsquohttprsquo)

url = require(rsquourlrsquo)

server = httpcreateServer(function (req res)

var path = urlparse(requrl)

)

serverlisten(port ip)

consolelog(rdquoServer running at httprdquo + ip + rdquordquo + port)

加入 le system 模組使用 readFile 的功能將這一段程式放置於

createServer的回應函式中

fsreadFile(filePath encode function(err file)

)

readFile的回應函式裡面加入頁面輸出讓瀏覽器可以正確讀到檔案在這邊我們設定讀取的檔案為 html 靜態檔案所以 Content-type 設定為texthtml讀取到檔案的內容將會正確輸出成 html靜態檔案

fsreadFile(filePath encode function(err file)

reswriteHead(200 rsquoContent-Typersquo rsquotexthtmlrsquo)

reswrite(file)

resend()

)

到這邊為止基本的程式內容都已經完成剩下一些細節的調整首先

路徑上必須做調整目前的靜態檔案全部都放置於 static資料夾底下設定一個變數來記住資料夾位置

接著將瀏覽器發出要求路徑與資料夾組合讀取正確 html靜態檔案使用者有可能會輸入錯誤路徑所以在讀取檔案的時候要加入錯誤處理

同時回應 404伺服器無法正確回應的 http header格式

加入這些細節的修改一個基本的 http 靜態 html輸出伺服器就完成了完整程式碼如下

44 nodejs http靜態檔案輸出 37

Nodejs中文電子書

var server

ip = rdquo127001rdquo

port = 1337

http = require(rsquohttprsquo)

fs = require(rdquofsrdquo)

folderPath = rdquostaticrdquo

url = require(rsquourlrsquo)

path

filePath

encode = rdquoutf8rdquo

server = httpcreateServer(function (req res)

path = urlparse(requrl)

filePath = folderPath + pathpathname

fsreadFile(filePath encode function(err file)

if (err)

reswriteHead(404 rsquoContent-Typersquo rsquotextplainrsquo)

resend()

return

reswriteHead(200 rsquoContent-Typersquo rsquotextapplicationrsquo)

reswrite(file)

resend()

)

)

serverlisten(port ip)

consolelog(rdquoServer running at httprdquo + ip + rdquordquo + port)

45 nodejs http GET資料擷取

http伺服器中除了路由之外另一個最常使用的方法就是擷取 GET資料本單元將會介紹如何透過基本 http伺服器擷取瀏覽器傳來的要求擷取 GET資料

在 http協定中GET參數都是藉由 URL從瀏覽器發出要求送至伺服器

38 4 Nodejs基礎

Nodejs中文電子書

端基本的傳送網址格式可能如下

http127001testsend=1amptest=2

上面這段網址裡面的 GET參數就是 send而這個變數的數值就為 1如果想要在 http伺服器取得 GET資料需要在瀏覽器給予的要求 (request)做處理

首先需要載入 query string這個模組這個模組主要是用來將字串資料

過濾後轉換成javascript物件

qs = require(rsquoquerystringrsquo)

接著在第一階段利用 url模組過濾瀏覽器發出的 URL資料後將回應的物件裡面的 query這個變數是一個字串值資料過濾後如下

send=1amptest=2

透過 query string使用 parse這個方法將資料轉換成 javascript物件就表示 GET的資料已經被伺服器端正式擷取下來

path = urlparse(requrl)

parameter = qsparse(pathquery)

整個 nodejs http GET參數完整擷取程式碼如下

var server

ip = rdquo127001rdquo

port = 1337

http = require(rsquohttprsquo)

qs = require(rsquoquerystringrsquo)

url = require(rsquourlrsquo)

server = httpcreateServer(function (req res)

var path = urlparse(requrl)

parameter = qsparse(pathquery)

consoledir(parameter)

reswriteHead(200 rsquoContent-Typersquo rsquotextplainrsquo)

reswrite(rsquoBrowser test GET parameternrsquo)

resend()

)

45 nodejs http GET資料擷取 39

Nodejs中文電子書

serverlisten(port ip)

consolelog(rdquoServer running at httprdquo + ip + rdquordquo + port)

程式運作之後由瀏覽器輸入要求網址之後nodejs伺服器端回應資料為

Server running at http1270011337

send rsquo1rsquo test rsquo1rsquo

46 本章結語

前面所解說的部份一大部分主要是處理 http伺服器基本問題雖然在某些部分有牽扯到 http 伺服器基本運作原理主要還是希望可以藉由這些基本範例練習 nodejs練習回應函式與語法串接的特點習慣編寫javascript風格程式當然直接這樣開發 nodejs是非常辛苦的接下來在模組實戰開發的部份將會介紹特定的模組一步一步帶領各位從無到有進行

nodejs應用程式開發

40 4 Nodejs基礎

5

NPM套件管理工具

npm全名為 Node Package Manager是 Nodejs的套件(package)管理工具類似 Perl的 ppm或 PHP的 PEAR等安裝 npm後使用npm install

module name指令即可安裝新套件維護管理套件的工作會更加輕鬆

npm可以讓Nodejs的開發者直接利用擴充線上的套件庫(packagesregistry)加速軟體專案的開發npm提供很友善的搜尋功能可以快速找到安裝需要的套件當這些套件發行新版本時npm也可以協助開發者自動更新這些套件

npm不僅可用於安裝新的套件它也支援搜尋列出已安裝模組及更新的功能

51 安裝 NPM

Nodejs在 063版本開始內建 npm讀者安裝的版本若是此版本或更新的版本就可以略過以下安裝說明

若要檢查 npm是否正確安裝可以使用以下的指令

npm -v

41

Nodejs中文電子書

執行結果說明

若 npm正確安裝執行 npm -v將會看到類似 110-2的版本訊息

若讀者安裝的 Nodejs版本比較舊或是有興趣嘗試自己動手安裝 npm工具則可以參考以下的說明

安裝於Windows系統

Nodejs for Windows 於 062 版開始內建 npm使用 nodejsorg 官方提供的安裝程式不需要進一步的設定就可以立即使用 npm指令對於Windows的開發者來說大幅降低環境設定的問題與門檻

除了使用 Nodejs內建的 npm讀者也可以從 npm官方提供的以下網址

httpnpmjsorgdist

這是由 npm提供的 Fancy Windows Install版本請下載壓縮檔(例如npm-110-3zip)並將壓縮檔內容解壓縮至 Nodejs的安裝路徑(例如CProgram Filesnodejs)

解壓縮後在 Nodejs的安裝路徑下應該有以下的檔案及資料夾

bull npmcmd(檔案)

bull node modules(資料夾)

安裝於 Linux系統

Ubuntu Linux的使用者可以加入 NPM Unoffcial PPA這個 repository即可使用 apt-get完成 npm安裝

42 5 NPM套件管理工具

Nodejs中文電子書

Ubuntu Linux使用 apt-get安裝 npm

sudo apt-get install python-software-properties

sudo add-apt-repository ppagias-kay-leenpm

sudo apt-get update

sudo apt-get install npm

npm官方提供的安裝程式 installsh可以適用於大多數的 Linux系統使用這個安裝程式請先確認

1 系統已安裝 curl工具(請使用 curl --version查看版本訊息)

2 已安裝 Nodejs並且 PATH正確設置

3 Nodejs的版本必須大於 04x

以下為 npm提供的安裝指令

curl httpnpmjsorginstallsh | sh

安裝成功會看到如下訊息

installsh安裝成功的訊息

npm10105 homeUSERNAMElocalnodelibnode_modulesnpm

It worked

安裝於Mac OS X

建議採用與 Nodejs相同的方式進行 npm的安裝例如使用MacPorts安裝 Nodejs就同樣使用MacPorts安裝 npm這樣對日後的維護才會更方便容易

使用 MacPorts安裝 npm是本書比較建議的方式它可以讓 npm的安裝移除及更新工作自動化將會幫助開發者節省寶貴時間

51 安裝 NPM 43

Nodejs中文電子書

安裝MacPorts的提示

在MacPorts網站可以取得 OS X系統版本對應的安裝程式(例如 106或 107)httpwwwmacportsorg安裝過程會詢問系統管理者密碼使用預設的選項完成安裝即可安

裝MacPorts之後在終端機執行 port -v將會看到MacPorts的版本訊息

安裝 npm之前先更新MacPorts的套件清單以確保安裝的 npm是最新版本

sudo port -d selfupdate

接著安裝 npm

sudo port install npm

若讀者的 Nodejs並非使用MacPorts安裝則不建議使用MacPorts安裝npm因為 MacPorts會自動檢查並安裝相依套件而 npm相依 nodejs所以MacPorts也會一併將 nodejs套件安裝造成先前讀者使用其它方式安裝的 nodejs被覆蓋

讀者可以先使用 MacPorts安裝 curl(sudo port install curl)

再參考 Linux的 installsh安裝方式即可使用 npm官方提供的安裝程式

NPM安裝後測試

npm是指令列工具(command-line tool)使用時請先打開系統的文字終端機工具

測試 npm安裝與設定是否正確請輸入指令如下

npm -v

或是

npm --version

如果 npm已經正確安裝設定就會顯示版本訊息

44 5 NPM套件管理工具

Nodejs中文電子書

執行結果(範例)

110-2

52 使用 NPM安裝套件

npm目前擁有超過 6000種套件(packages)可以在 npm registry使用關鍵字搜尋套件

httpsearchnpmjsorg

舉例來說在關鍵字欄位輸入「coffee-script」下方的清單就會自動列出包含 coffee-script關鍵字的套件

接著我們回到終端機模式的操作npm的指令工具本身就可以完成套

件搜尋的任務

例如以下的指令同樣可以找出 coffee-script相關套件

npm search coffee-script

以下是搜尋結果的參考畫面

52 使用 NPM安裝套件 45

Nodejs中文電子書

找到需要的套件後(例如 express)即可使用以下指令安裝

npm install coffee-script

值得注意的一點是使用 npm install會將指定的套件安裝在工

作目錄(Working Directory)的 node modules資料夾下

以Windows為例如果執行 npm install的目錄位於

Cproject1

那麼 npm將會自動建立一個 node modules的子目錄(如果不存在)

Cproject1node modules

並且將下載的套件放置於這個子目錄例如

Cproject1node modulescoffee-script

這個設計讓專案可以個別管理相依的套件並且可以在專案佈署或發

行時將這些套件(位於 node modules)一併打包方便其它專案的使用者不必再重新下載套件

這個 npm install的預設安裝模式為 local(本地)只會變更當前專案的資料夾不會影響系統

另一種安裝模式稱為 global(全域)這種模式會將套件安裝到系統資

料夾也就是 npm安裝路徑的 node modules資料夾例如

CProgram Filesnodejsnode modules

46 5 NPM套件管理工具

Nodejs中文電子書

是否要使用全域安裝可以依照套件是否提供新指令來判斷舉例來說express 套件提供 express 這個指令而 coffee-script 則提供 coffee

指令

在 local 安 裝 模 式 中這 些 指 令 的 程 式 檔 案會 被 安 裝 到node modules的 bin這個隱藏資料夾下除非將bin的路徑加入 PATH環境變數否則要執行這些指令將會相當不便

為了方便指令的執行我們可以在 npm install 加上 -g 或 --

global參數啟用 global安裝模式例如

npm install -g coffee-script

npm install -g express

使用 global安裝模式需要注意執行權限的問題若權限不足可能會出現類似以下的錯誤訊息

npm ERR Error EACCES permission denied rsquorsquo

npm ERR

npm ERR Please try running this command again as rootAdministrator

要獲得足夠得執行權限請參考以下說明

bull Windows 7 或 2008 以上在「命令提示字元」的捷徑按右鍵選擇「以系統管理員身分執行」執行 npm指令時就會具有 Administrator身分

bull Mac OS X或 Linux系統可以使用 sudo指令例如

sudo npm install -g express

bull Linux系統可以使用 root權限登入或是以「sudo su -」切換成 root身分(使用 root權限操作系統相當危險因此並不建議使用這種方式)

若加上 -g參數使用 npm install -g coffee-script完成安裝

後就可以在終端機執行 coffee指令例如

coffee -v

52 使用 NPM安裝套件 47

Nodejs中文電子書

執行結果(範例)

CoffeeScript version 120

53 套件的更新及維護

除了前一節說明的 search 及 install 用法npm 還提供其他許多指令(commands)

使用 npm help可以查詢可用的指令

npm help

執行結果(部分)

where ltcommandgt is one of

adduser apihelp author bin bugs c cache completion

config deprecate docs edit explore faq find get

help help-search home i info init install la link

list ll ln login ls outdated owner pack prefix

prune publish r rb rebuild remove restart rm root

run-script s se search set show star start stop

submodule tag test un uninstall unlink unpublish

unstar up update version view whoami

使用 npm help command可以查詢指令的詳細用法例如

npm help list

接下來本節要介紹開發過程常用的 npm指令

使用 list可以列出已安裝套件

npm list

48 5 NPM套件管理工具

Nodejs中文電子書

執行結果(範例)

-- coffee-script120

- express256

- connect185

| -- formidable108

-- mime124

-- mkdirp007

-- qs041

檢視某個套件的詳細資訊例如

npm show express

升級所有套件(如果該套件已發佈更新版本)

npm update

升級指定的套件

npm update express

移除指定的套件

npm uninstall express

54 使用 packagejson

對於正式的 Nodejs專案可以建立一個命名為 packagejson的設

定檔(純文字格式)檔案內容參考範例如下

54 使用 packagejson 49

Nodejs中文電子書

packagejson(範例)

rdquonamerdquo rdquoapplication-namerdquo

rdquoversionrdquo rdquo001rdquo

rdquoprivaterdquo true

rdquodependenciesrdquo

rdquoexpressrdquo rdquo255rdquo

rdquocoffee-scriptrdquo rdquolatestrdquo

rdquomongooserdquo rdquogt= 253rdquo

其中 name與 version依照專案的需求設置

需要注意的是 dependencies的設定它用於指定專案相依的套件名

稱及版本

bull rdquoexpressrdquo rdquo255rdquo

代表此專案相依版本 255的 express套件

bull rdquocoffee-scriptrdquo rdquolatestrdquo

使用最新版的 coffee-script套件(每次更新都會檢查新版)

bull rdquomongooserdquo rdquogt= 253rdquo

使用版本大於 253的 mongoose套件

假設某個套件的新版可能造成專案無法正常運作就必須指定套件的

版本避免專案的程式碼來不及更新以相容新版套件通常在開發初期的

專案需要盡可能維持新套件的相容性(以取得套件的更新或修正)可以

用「gt=」設定最低相容的版本或是使用「latest」設定永遠保持最新

套件

50 5 NPM套件管理工具

6

Express介紹

在前面的 nodejs基礎當中介紹許多許多開設 http的使用方法及介紹以及許多基本的 nodejs基本應用

接下來要介紹一個套件稱為 express [Express](httpexpressjscom)這個套件主要幫忙解決許多 nodejs http server所需要的基本服務讓開發 httpservice變得更為容易不需要像之前需要透過層層模組(module)才有辦法開始編寫自己的程式

這個套件是由 TJ Holowaychuk 製作而成的套件裡面包含基本的路由處理 (route)http資料處理(GETPOSTPUT)另外還與樣板套件(jshtml template engine)搭配同時也可以處理許多複雜化的問題

61 Express安裝

安裝方式十分簡單只要透過之前介紹的 NPM就可以使用簡單的指令安裝指令如下

這邊建議需要將此套件安裝成為全域模組方便日後使用

51

Nodejs中文電子書

62 Express基本操作

express的使用也十分簡單先來建立一個基本的 hello world

var app = require(rsquoexpressrsquo)createServer()

port = 1337

applisten(port)

appget(rsquorsquo function(req res)

ressend(rsquohello worldrsquo)

)

consolelog(rsquostart express servernrsquo)

可以從上面的程式碼發現基本操作與 nodejs http的建立方式沒有太大差異主要差在當我們設定路由時可以直接透過 appget方式設定回應與接受方式

63 Express路由處理

Express對於 http服務上有許多包裝讓開發者使用及設定上更為方便例如有幾個路由設定那我們就統一藉由 appget來處理

Create http server

appget(rsquorsquo function(req res)

ressend(rsquohello worldrsquo)

)

appget(rsquotestrsquo function(req res)

ressend(rsquotest renderrsquo)

)

appget(rsquouserrsquo function(req res)

ressend(rsquouser pagersquo)

)

52 6 Express介紹

Nodejs中文電子書

如上面的程式碼所表示appget可以帶入兩個參數第一個是路徑名稱設定第二個為回應函式 (call back function)回應函式裡面就如同之前的 createServer方法裡面包含 requestresponse兩個物件可供使用使用者就可以透過瀏覽器輸入不同的 url切換到不同的頁面顯示不同的結果

路由設定上也有基本的配對方式讓使用者從瀏覽器輸入的網址可以

是一個變數只要符合型態就可以有對應的頁面產出例如

Create http server

appget(rsquouseridrsquo function(req res)

ressend(rsquouser rsquo + reqparamsid)

)

appget(rsquonumberrsquo function(req res)

ressend(rsquonumber rsquo + reqparamsnumber)

)

裡 面 使 用 到number 從 網 址 輸 入 之 後 就 可 以 直 接 使 用reqparamsnumber 取得所輸入的資料變成 url 參數使用當然前面也是可以加上路徑的設定userid在瀏覽器上路徑必須符合userxxx透

過 reqparamsid就可以取到 xxx這個字串值

另外express參數處理也提供了路由參數配對處理也可以透過正規表示法作為參數設定

var app = require(rsquoexpressrsquo)createServer()

port = 1337

applisten(port)

appget(^ip((d23)((d23))((d23))((d23))) function(req res)

ressend(reqparams)

)

上面程式碼可以發現後面路由設定的型態是正規表示法裡面設定

格式為ip之後必須要加上 ip型態才會符合資料格式同時取得 ip資料已經由正規表示法將資料做分群因此可以取得 ip的四個數字

此程式執行之後可以透過瀏覽器測試輸入網址為 localhost3000ip25525510010可以從頁面獲得資料

63 Express路由處理 53

Nodejs中文電子書

此章節全部範例程式碼如下

overview

author Caesar Chi

blog clonnblogspotcom

version 20120226

create server

var app = require(rsquoexpressrsquo)createServer()

port = 1337

applisten(port)

normal style

appget(rsquorsquo function(req res)

ressend(rsquohello worldrsquo)

)

appget(rsquotestrsquo function(req res)

ressend(rsquotest renderrsquo)

)

parameter style

appget(rsquouseridrsquo function(req res)

ressend(rsquouser rsquo + reqparamsid)

)

appget(rsquonumberrsquo function(req res)

ressend(rsquonumber rsquo + reqparamsnumber)

)

REGX style

appget(^ip((d23)((d23))((d23))) function(req res)

ressend(reqparams)

)

appget(rsquorsquo function(req res)

ressend(rsquoPage not foundrsquo 404)

54 6 Express介紹

Nodejs中文電子書

)

consolelog(rsquostart express servernrsquo)

64 Express middleware

Express 裡面有一個十分好用的應用概念稱為 middleware可以透過middleware做出複雜的效果同時上面也有介紹 next方法參數傳遞就是靠 middleware的概念來傳遞參數讓開發者可以明確的控制程式邏輯

create http server

appuse(expressbodyParser())

appuse(expressmethodOverride())

appuse(expresssession())

上面都是一種 middleware的使用方式透過 appuse方式裡面載入函式執行方法回應函式會包含三個基本參數responserequestnext其中next表示下一個 middleware執行函式同時會自動將預設三個參數繼續帶往下個函式執行底下有個實驗

上面的片段程式執行後開啟瀏覽器連結上 localhost1337會發現伺服器回應結果順序如下

first middle ware

second middle ware

execute middle ware

end middleware function

從上面的結果可以得知剛才設定的 middleware都生效了在 appuse設定的 middleware是所有 url皆會執行方法如果有指定特定方法就可以使用 appget的 middleware設定在 appget函式的第二個參數就可以帶入函式或者是匿名函式只要函式裡面最後會接受 request response next這三個參數同時也有正確指定 next函式的執行時機最後都會執行到最後一個方法當然開發者也可以評估程式邏輯要執行到哪一個階段讓邏輯

可以更為分明

64 Express middleware 55

Nodejs中文電子書

65 Express路由應用

在實際開發上可能會遇到需要使用參數等方式混和變數一起使用

express裡面提供了一個很棒的處理方法 appall這個方式可以先採用基本路由配對再將設定為每個不同的處理方式開發者可以透過這個方式簡

化自己的程式邏輯

overview

author

version 20120226

create server

var app = require(rsquoexpressrsquo)createServer()

port = 1337

users = [

name rsquoClonnrsquo

name rsquoChirsquo

]

applisten(port)

appall(rsquouseridoprsquo function(req res next)

requser = users[reqparamsid]

if (requser)

next()

else

next(new Error(rsquocannot find user rsquo + reqparamsid))

)

appget(rsquouseridrsquo function(req res)

ressend(rsquoviewing rsquo + requsername)

)

appget(rsquouserideditrsquo function(req res)

ressend(rsquoediting rsquo + requsername)

)

56 6 Express介紹

Nodejs中文電子書

appget(rsquouseriddeletersquo function(req res)

ressend(rsquodeleting rsquo + requsername)

)

appget(rsquorsquo function(req res)

ressend(rsquoPage not foundrsquo 404)

)

consolelog(rsquostart express servernrsquo)

內部宣告一組預設的使用者分別給予名稱設定藉由 appall這個方法可以先將路由雛形建立再接下來設定 appget的路徑格式只要符合格式就會分配進入對應的方法中像上面的程式當中如果使用者輸入路徑為user0除了執行 appall程式之後執行 next方法就會對應到路徑設定為userid的這個方法當中如果使用者輸入路徑為user0edit就會執行到useridedit的對應方法

66 Express GET應用範例

我們準備一個使用 GET方法傳送資料的表單

ltDOCTYPE htmlgt

lthtmlgt

ltheadgt

ltmeta http-equiv=rdquoContent-Typerdquo content=rdquotexthtml charset=utf8rdquogt

lttitlegtNodejs 菜鳥筆記 (1)lttitlegt

ltlink rel=rdquostylesheetrdquo href=rdquocssstylecssrdquo type=rdquotextcssrdquo media=rdquoallrdquo gt

ltheadgt

ltbodygt

lth1gtNodejs 菜鳥筆記-註冊lth1gt

ltform id=rdquosignuprdquo method=rdquoGETrdquo action=rdquohttplocalhost3000Signuprdquogt

ltlabelgt 使用者名稱ltlabelgtltinput type=rdquotextrdquo id=rdquousernamerdquo name=rdquousernamerdquo gtltbrgt

ltlabelgt 電子郵件ltlabelgtltinput type=rdquotextrdquo id=rdquoemailrdquo name=rdquoemailrdquo gtltbrgt

ltinput type=rdquosubmitrdquo value=rdquo 註冊我的帳號rdquo gtltbrgt

ltformgt

ltbodygt

lthtmlgt

66 Express GET應用範例 57

Nodejs中文電子書

這個表單沒有什麼特別的地方我們只需要看第 9 行form 使用的method是 GET然後 action是rdquohttplocalhost3000Signupldquo等一下我們要來撰寫Signup這個 URL Path的處理程式

處理 Signup行為

我們知道所謂的 GET方法會透過 URL來把表單的值給帶過去以上面的表單來說到時候 URL會以這樣的形式傳遞

httplocalhost3000Signupusername=xxxampemail=xxx

所以要能處理這樣的資料必須有以下功能

bull 解析 URL

bull 辨別動作是 Signup

bull 解析出 username和 email

一旦能取得 username和 email的值程式就能加以應用了

處理 Signup的程式碼雛形

load module

var url = require(rsquourlrsquo)

urlData = urlparse(requrltrue)

action = urlDatapathname

reswriteHead(200 rdquoContent-Typerdquordquotexthtml charset=utf-8rdquo)

if (action === rdquoSignuprdquo)

user = urlDataquery

resend(rdquolth1gtrdquo + userusername + rdquo 歡迎您的加入 lth1gtltpgt 我們已經將會員啟用信寄至rdquo + useremail + rdquoltpgtrdquo)

首先需要加載 url module它是用來協助我們解析 URL的模組接著使用 urlparse方法第一個傳入 url字串變數也就是 requrl另外第二個參數的用意是設為 ture則引進 querystring模組來協助處理預設是 false它影響到的是 urlDataquery設為 true會傳回物件不然就只是一般的字串urlparse會將字串內容整理成一個物件我們把它指定給 urlData

action變數作為記錄 pathname這是我們稍後要來判斷目前網頁的動作是什麼接著先將 html表頭資訊 (Header)準備好再來判斷路徑邏輯如

58 6 Express介紹

Nodejs中文電子書

果是 Signup這個動作就把 urlDataquery裡的資料指定給 user然後輸出userusername和 useremail把使用者從表單註冊的資料顯示於頁面中

最後進行程式測試啟動 nodejs主程式之後開啟瀏覽器就會看到表單填寫完畢按下送出就可以看到結果了

完整 nodejs程式碼如下

var http = require(rsquohttprsquo)

url = require(rsquourlrsquo)

fs = require(rdquofsrdquo)

server

server = httpcreateServer(function (reqres)

var urlData

encode = rdquoutf8rdquo

filePath = rdquoviewexpress_get_example_formhtmlrdquo

action

urlData = urlparse(requrltrue)

action = urlDatapathname

reswriteHead(200 rdquoContent-Typerdquordquotexthtml charset=utf-8rdquo)

if (action === rdquoSignuprdquo)

user = urlDataquery

resend(rdquolth1gtrdquo + userusername + rdquo 歡迎您的加入 lth1gtltpgt 我們已經將會員啟用信寄至rdquo + useremail + rdquoltpgtrdquo)

else

fsreadFile(filePath encode function(err file)

reswrite(file)

resend()

)

)

serverlisten(3000)

consolelog(rsquoServer 跑起來了現在時間是rsquo + new Date())

66 Express GET應用範例 59

Nodejs中文電子書

67 Express POST應用範例

一開始準備基本的 html表單傳送內容以 POST方式form的 action屬性設定為 POST其餘 html內容與前一個範例應用相同

ltDOCTYPE htmlgt

lthtmlgt

ltheadgt

ltmeta http-equiv=rdquoContent-Typerdquo content=rdquotexthtml charset=utf8rdquogt

lttitlegtNodejs 菜鳥筆記 (1)lttitlegt

ltlink rel=rdquostylesheetrdquo href=rdquocssstylecssrdquo type=rdquotextcssrdquo media=rdquoallrdquo gt

ltheadgt

ltbodygt

lth1gtNodejs 菜鳥筆記-註冊lth1gt

ltform id=rdquosignuprdquo method=rdquoPOSTrdquo action=rdquohttplocalhost3000Signuprdquogt

ltlabelgt 使用者名稱ltlabelgtltinput type=rdquotextrdquo id=rdquousernamerdquo name=rdquousernamerdquo gtltbrgt

ltlabelgt 電子郵件ltlabelgtltinput type=rdquotextrdquo id=rdquoemailrdquo name=rdquoemailrdquo gtltbrgt

ltinput type=rdquosubmitrdquo value=rdquo 註冊我的帳號rdquo gtltbrgt

ltformgt

ltbodygt

lthtmlgt

nodejs的程式處理邏輯與前面 GET範例類似部分程式碼如下

qs = require(rsquoquerystringrsquo)

if (action === rdquoSignuprdquo)

formData = rsquorsquo

reqon(rdquodatardquo function (data)

formData += data

)

reqon(rdquoendrdquo function ()

user = qsparse(formData)

resend(rdquolth1gtrdquo + userusername + rdquo 歡迎您的加入 lth1gtltpgt 我們已經將會員啟用信寄至rdquo + useremail + rdquoltpgtrdquo)

)

60 6 Express介紹

Nodejs中文電子書

主要加入了rsquoquerystringrsquo這個moduel方便我們等一下解析由表單 POST回來的資料另外加入一個 formData的變數用來搜集待等一下表單回傳的資料前面的 GET範例我們只從 req拿出 url的資料這次要在利用req身上的事件處理

JavaScript 在訂閱事件時使用 addEventListener而 nodejs 使用的則是on這邊加上了監聽 data的事件會在瀏覽器傳送資料到Web Server時被執行參數是它所接收到的資料型態是字串

接著再增加 end的事件當瀏覽器的請求事件結束時它就會動作

由於瀏覽器使用 POST在上傳資料時會將資料一塊塊地上傳因為我們在監聽 data事件時透過 formData變數將它累加起來lt不過由於我們

上傳的資料很少一次就結束不過如果日後需要傳的是資料比較大的檔

案這個累加動作就很重要

當資料傳完就進到 end 事件中會用到 qsparse 來解析 formDataformData的內容是字串內容是

username=wordsmithampemail=wordsmith40somewhere

而 qsparse可以幫我們把這個 querystring轉成物件的格式也就是

username=wordsmithampemail=wordsmith40somewhere

一旦轉成物件並指定給 user之後其他的事情就和 GET方法時操作的一樣寫 response的表頭將內容回傳並將 userusername和 useremail代入到內容中

修改完成後接著執行 nodejs程式啟動 web server開啟瀏覽器進入表單測試看看POST的方式能否順利運作

完整程式碼如下

var http = require(rsquohttprsquo)

url = require(rsquourlrsquo)

fs = require(rdquofsrdquo)

qs = require(rsquoquerystringrsquo)

server

server = httpcreateServer(function (reqres)

var urlData

67 Express POST應用範例 61

Nodejs中文電子書

encode = rdquoutf8rdquo

filePath = rdquoviewexpress_post_example_formhtmlrdquo

formData

action

urlData = urlparse(requrltrue)

action = urlDatapathname

reswriteHead(200 rdquoContent-Typerdquordquotexthtml charset=utf-8rdquo)

if (action === rdquoSignuprdquo)

formData = rsquorsquo

reqon(rdquodatardquo function (data)

formData += data

)

reqon(rdquoendrdquo function ()

user = qsparse(formData)

resend(rdquolth1gtrdquo + userusername + rdquo 歡迎您的加入 lth1gtltpgt 我們已經將會員啟用信寄至rdquo + useremail + rdquoltpgtrdquo)

)

else

fsreadFile(filePath encode function(err file)

reswrite(file)

resend()

)

)

serverlisten(3000)

consolelog(rsquoServer 跑起來了現在時間是rsquo + new Date())

62 6 Express介紹

Nodejs中文電子書

68 Express AJAX應用範例

在 Nodejs要使用 Ajax傳送資料並且與之互動在接受資料的部份沒有太大的差別client端不是用 GET就是用 POST來傳資料重點在處理完後用 JSON格式回傳當然 Ajax不見得只傳 JSON格式有時是回傳一段 HTML碼不過後者對伺服器來說基本上就和前兩篇沒有差別了所以我們還是以回傳 JSON做為這一回的主題

這一回其實大多數的工作都會落在前端 Ajax上面前端要負責發送與接收資料並在接收資料後撤掉原先發送資料的表單並將取得的資料

改成 HTML格式之後放上頁面

首先先準備 HTML靜態頁面資料

ltDOCTYPE htmlgt

lthtmlgt

ltheadgt

lttitlegtNodejs 菜鳥筆記 (3)lttitlegt

ltlink rel=rdquostylesheetrdquo href=rdquocssstylecssrdquo type=rdquotextcssrdquo media=rdquoallrdquo gt

ltscript src=rdquohttpsajaxgoogleapiscomajaxlibsjquery171jqueryminjsrdquogtltscriptgt

ltheadgt

ltbodygt

lth1gtNodejs 菜鳥筆記-註冊 (用 Ajax)lth1gt

ltform id=rdquosignuprdquo action=rdquoSignuprdquo method=rdquoPOSTrdquogt

ltlabelgt 使用者名稱ltlabelgtltinput type=rdquotextrdquo id=rdquousernamerdquo name=rdquousernamerdquo gtltbrgt

ltlabelgt 電子郵件ltlabelgtltinput type=rdquotextrdquo id=rdquoemailrdquo name=rdquoemailrdquo gtltbrgt

ltinput type=rdquosubmitrdquo value=rdquo 註冊我的帳號rdquo gtltbrgt

ltformgt

ltdiv id=rdquoresultrdquogt

ltdivgt

ltscript type=rdquotextjavascriptrdquogt

$(function()$(rsquosignup input[type=rdquosubmitrdquo]rsquo)click(function(e)

var user =

userusername = $(rdquousernamerdquo)val()useremail = $(rdquoemailrdquo)val()

$post(rdquoSignuprdquouserfunction(data)greet(data)

)

68 Express AJAX應用範例 63

Nodejs中文電子書

epreventDefault()

)

var greet = function(msg)

var resultNode = $(rsquoresultrsquo)greeting = $(rsquolth2gtHirsquo+msgusername+rsquo 你的會員 id 是rsquo+ msgid +rsquo 我們會將會員啟動信寄至rsquo+msgemail+rsquolth2gtrsquo)

$(rsquosignuprsquo)hide()resultNodehtml(rdquordquo)

resultNodeappend(greeting)

)

ltscriptgt

ltbodygt

lthtmlgt

HTML頁面上準備了一個表單用來傳送註冊資料接著直接引用了Google CDN來載入 jQuery用來幫我們處理 Ajax的工作這次要傳送和接收的工作很大的變動都在 HTML頁面上的 JavaScript當中我們要做的事有 (相關 jQuery處理這邊不多做贅述指提起主要功能解說)

bull 用 jQUery取得 submit按鈕綁定它的 click動作

bull 取得表單 username和 email的值存放在 user這個物件中

bull 用 jQuery的 $post方法將 user的資料傳到 Server

bull 一旦成功取得資料後透過 greet這個 function組成回報給 user的訊息

bull 清空原本給使用者填資料的表單

bull 將 Server回傳的 usernameemail和 id這 3個資料組成回應的訊息

bull 將訊息放到原本表單的位置

經過以上的處理後一個 Ajax的表單的基本功能已經完成

接著進行 nodejs主要程式的編輯部分程式碼如下

var fs = require(rdquofsrdquo)

64 6 Express介紹

Nodejs中文電子書

qs = require(rsquoquerystringrsquo)

if (action === rdquoSignuprdquo)

formData = rsquorsquo

reqon(rdquodatardquo function (data)

formData += data

)

reqon(rdquoendrdquo function ()

var msg

user = qsparse(formData)

userid = rdquo123456rdquo

msg = JSONstringify(user)

reswriteHead(200 rdquoContent-Typerdquordquoapplicationjson charset=utf-8rdquordquoContent-Lengthrdquomsglength)

resend(msg)

)

這裡的程式和前面 POST範例基本上大同小異差別在

bull 幫 user的資料加上 id隨意存放一些文字進去讓 Server回傳的資料多於 Client端傳上來的不然會覺得 Server都沒做事

bull 增加了 msg 這個變數存放將 user 物件 JSON 文字化的結果JSONstringify這個轉換函式是 V8引擎所提供的如果你好奇的話

bull 大重點來了我們要告訴 Client端這次回傳的資料格式是 JSON所在 Content-type和 Content-Length要提供給 Client

Server很輕鬆就完成任務了最後進行程式測試啟動 nodejs主程式之後開啟瀏覽器就會看到表單填寫完畢按下送出就可以看到結果了

最後 nodejs本篇範例程式碼如下

var http = require(rsquohttprsquo)

url = require(rsquourlrsquo)

fs = require(rdquofsrdquo)

qs = require(rsquoquerystringrsquo)

server

68 Express AJAX應用範例 65

Nodejs中文電子書

server = httpcreateServer(function (reqres)

var urlData

encode = rdquoutf8rdquo

filePath = rdquoviewexpress_ajax_example_formhtmlrdquo

formData

action

urlData = urlparse(requrltrue)

action = urlDatapathname

if (action === rdquoSignuprdquo)

formData = rsquorsquo

reqon(rdquodatardquo function (data)

formData += data

)

reqon(rdquoendrdquo function ()

var msg

user = qsparse(formData)

userid = rdquo123456rdquo

msg = JSONstringify(user)

reswriteHead(200 rdquoContent-TyperdquordquoapplicationjsonrdquordquoContent-Lengthrdquomsglength)

resend(msg)

)

else

fsreadFile(filePath encode function(err file)

reswriteHead(200 rdquoContent-Typerdquordquotexthtml charset=utf-8rdquo)

reswrite(file)

resend()

)

)

serverlisten(3000)

66 6 Express介紹

Nodejs中文電子書

consolelog(rsquoServer 跑起來了現在時間是rsquo + new Date())

69 原始資料提供

bull [NodeJS初學者筆記 (1)-用 GET傳送資料] (httpithelpithomecomtwquestion10087402)

bull [NodeJS初學者筆記 (2)-用 POST傳送資料] (httpithelpithomecomtwquestion10087489)

bull [NodeJS 初學者筆記 (3)-用 Ajax 傳送資料] (httpithelpithomecomtwquestion10087627)

69 原始資料提供 67

Nodejs中文電子書

68 6 Express介紹

7

CoffeeScript

bull Spine

bull Hem

bull Spineapp

Letrsquos Have a Cup of CoffeeScript

bull es5-shim ECMAScript 5 compatibility shims for legacy JavaScript engines

ESnext

node-iform - A middleware for node-validator httpsgithubcomguileennode-iform

69

Nodejs中文電子書

70 7 CoffeeScript

8

製作一個 Hubot的 Plurk Adapter

81 應用事項提醒

此應用範例以CoffeeScript編寫主要程式架構採用Github釋放的Hubot專案以下有幾個主要注意事項請讀者注意

bull Hubot 一開始的架構除了內建的兩個 Adapter 之外其餘都要以Nodejs的Module方式才能運作

bull Hubot的 binhubot裡面寫著 npm install所以不管你怎麼改原始碼也不能改變第一點的狀況

其實上述都在討論同一件事情NodeJS的模組而且模組不能設定相對路徑之類的來安裝一定要包裝成 npm相符和格式才有辦法正確執行

82 建立 Adapter

首先需要準備兩個檔案

bull packagejson

bull plurkcoffee

71

Nodejs中文電子書

有這兩個就足以變成 NodeJS的模組了在開始 Coding之前先來設定一下 packagejson相依性

rdquonamerdquo rdquohubot-plurkrdquo

rdquoversionrdquo rdquo011rdquo

rdquomainrdquo rdquoplurkrdquo

rdquodependenciesrdquo

rdquohubotrdquo rdquogt=205rdquo

rdquooauthrdquo rdquordquo

rdquocronrdquordquordquo

這邊會使用到NodeJS的 cron模組(Module)而登入 Plurk需要OAuth標準授權才行所以也需要加載 OAuth模組

注意Hubot在讀取非內建模組時會自動在前面加上 hubot-的前置

83 建立 Robot跟 API

本篇應用基本上程式碼大部分參考 Hubot - Twitter Adapter來製作主要差異只有在使用 OAuth這個模組Twitter有 Streaming可用而 Plurk則得用 Comet方式來達到即時讀取

plurkcoffee程式碼

Robot = require(rdquohubotrdquo)robot()

Adapter = require(rdquohubtordquo)adapter()

EventEmitter = require(rdquoeventsrdquo)EventEmitter

oauth = require(rdquooauthrdquo)

cronJob = require(rdquocronrdquo)CronJob

class Plurk exntends Adapter

class PlurkStreaming exnteds EventEmitter

72 8 製作一個 Hubot的 Plurk Adapter

Nodejs中文電子書

先弄個基本架構主要 Class皆參考 Twitter Adapter命名方式接著再將 Plurk這個 Class增加幾個Method基本上只要有 run send reply就夠了而 run用來做初始化的部分

class Plurk entends Adapter

send (plurk id strings⋯) -gt

reply (plurk id strings⋯) -gt

run -gt

看起來有點東西接著來處理主要的 Plurk API結合部分

class PlurkStreaming extends EventEmitter

consuctor (options) -gt

plurk (callback) -gt

觀察河道

getChannel -gt

取得 Comet 網址

reply (plurk id message) -gt

回噗

acceptFriends -gt

接受好友

get (path callback) -gt

GET 請求

post (path body callback)-gt

POST 請求(其實是裝飾)

request (method path body callback)-gt

主要的 OAuth 請求

comet (server callback)-gt

噗浪的 Comet 傳回是 JavaScript Callback 要另外處理後才會變成 JSON

然後把注意力集中到 constructor上先把建構子弄好

constructor (options) -gt

super()

if optionskey and optionssecret and optionstoken and optionstoken secret

key = optionskey

secret = optionssecret

token = optionstoken

token secret = optionstoken secret

83 建立 Robot跟 API 73

Nodejs中文電子書

建立 OAuth 連接

consumer = new oauthOAuth(

rdquohttpwwwplurkcomOAuthrequest tokenrdquo

rdquohttpwwwplurkcomOAuthaccess tokenrdquo

key

secret

rdquo10rdquo

rdquohttpwwwplurkcomOAuthauthorizerdquo

rdquoHMAC-SHA1rdquo

)

domain = rdquowwwplurkcomrdquo

初始化取得 Comet 網址

do getChannel

else

throw new Error(rdquo 參數不足需要 Key Secret Token Token Secretrdquo)

接著來處理 request這個 method

request (method path body callback) -gt

記錄一下這次的 Request

consolelog(rdquohttpdomainpathrdquo)

Callback 這邊先不丟進去要用另一種方式處理

request = consumerget(rdquohttpdomainpathrdquo token token secret null)

requeston rdquoresponserdquo (res) -gt

reson rdquodatardquo (chunk) -gt

parseResponse(chunk+rsquorsquo callback)

reson rdquoendrdquo (data) -gt

consolelog rdquoEnd Request pathrdquo

reson rdquoerrorrdquo (data) -gt

consolelog rdquoError rdquo + data

requestend()

處理資料

parseResponse = (data callback) -gt

if datalength gt 0

用 TryCatch 避免處理 JSON 出錯導致整個中斷

try

callback null JSONparse(data)

catch err

74 8 製作一個 Hubot的 Plurk Adapter

Nodejs中文電子書

consolelog(rdquoError Parse JSONrdquo + data err)

繼續執行

callback null data ||

大致上就是這樣上面程式的架構已經將整個 Hubot Plurk Adapter完成因為在測試時竟然因為噗浪 Lag而沒讀到完整的 Comet資料然後造成程式異常為了避免這個問題發生因此需要加上為了完美呈現需要再

加上 Comet的處理所以要使用到 EventEmitter的功能

comet (server callback) -gt

在 Callback 裡面會找不到自身所以設定區域變數

self =

記錄一下這次的 Request

consolelog(rdquo[Comet] serverrdquo)

Callback 這邊先不丟進去要用另一種方式處理

request = consumerget(rdquohttpdomainpathrdquo token token secret null)

requeston rdquoresponserdquo (res) -gt

reson rdquodatardquo (chunk) -gt

parseResponse(chunk+rsquorsquo callback)

reson rdquoendrdquo (data) -gt

consolelog rdquoEnd Request pathrdquo

請求結束發出事件通知可以進行下一次請求

selfemit rdquonextPlurkrdquo

reson rdquoerrorrdquo (data) -gt

consolelog rdquoError rdquo + data

requestend()

處理資料

parseResponse = (data callback) -gt

if datalength gt 0

用 trycatch 避免失敗中斷

try

去掉 JavaScript 的 Callback

data = datamatch(CometChannelscriptCallback((+))s)

jsonData = rdquordquo

if data

83 建立 Robot跟 API 75

Nodejs中文電子書

jsonData = JSONparse(data[1])

else

如果沒有任何 Match 嘗試直接 parse

jsonData = JSONparse(data)

catch err

consolelog(rdquo[Comet] Errorrdquo data err)

用 TryCatch 避免處理 JSON 出錯導致整個中斷

try

只傳入 json 的 data 部分

callback null jsonDatadata

catch err

consolelog(rdquo[Comet]Error Parse JSONrdquo + data err)

繼續執行

callback null data ||

後面的 get跟 post就簡單多了

get (path callback) -gt

request(rdquoGETrdquo path null callback)

post (path body callback) -gt

request(rdquoPOSTrdquo path body callback)

接著處理取的 Comet網址的 getChannel

getChannel -gt

self =

get rdquoAPPRealtimegetUserChannelrdquo (error data) -gt

if error

檢查是否有 comet server

if datacomet server

selfchannel = datacomet server

如果沒有 Channel Ready 就嘗試連接會失敗

selfemit(rsquochannel readyrsquo)

那麼先來處理 Plurk Adaper好處理的部份

send (plruk id strings⋯)-gt

跟 Reply 一樣直接交給 reply 做

reply plurk id strings⋯

76 8 製作一個 Hubot的 Plurk Adapter

Nodejs中文電子書

reply (plurk id strings⋯) -gt

stringsforEach (message) =gt

botreply(plruk id message)

接著把 run處理好就可以上線運作摟

run -gt

self =

options =

key processenvHUBOT PLURK KEY

secret processenvHUBOT PLURK SECRET

token processenvHUBOT PLURK TOKEN

token secret processenvHUBOT PLURK TOKEN SECRET

創建剛剛的 API

bot = new PlurkStreaming(options)

依照 Twitter 的 new RobotTextMessage 會沒有反應所以參考 hubot-minecraft 的方式

r = robotconstructor

處理噗浪河道訊息

doPlurk = (data)-gt

檢查是否為回噗

if dataresponse

datacontent raw = dataresponsecontent raw

datauser id = dataresponseuser id

確定有噗浪 ID 跟訊息

if dataplurk id and datacontent raw

selfreceive new rTextMessage(dataplurk id datacontent raw)

取得 Comet Server 完成開始第一次 Comet 連接

boton rdquochannel readyrdquo () -gt

botplurk selfdoPlurk

上一次 Comet 完成繼續 Polling

boton rdquonextPlurkrdquo ()-gt

botplurk selfdoPlurk

定時接受好友邀請

do botacceptFriends

bot = bot

83 建立 Robot跟 API 77

Nodejs中文電子書

終於完成 AdapterHubot專案裡面的 scripts資料夾內是互動部分不需要像 Adapter如此大費周章處理只新增檔案並且設計好對白之後就會回噗了我開發用的機器人在此大家可以去跟他玩玩[httpplurkcomelct9620 bot](httpplurkcomelct9620 bot)

84 原始資料提供

bull [製 作 一 個 Hubot 的 噗 浪 Adapter](http revo-skillfrosttw blog20120318create-a-hubot-plurk-adapter)

78 8 製作一個 Hubot的 Plurk Adapter

9

Nodejs好用工具介紹

91 logio

Real-time log monitoring in your browser

httplogioorg

nodejs (gt=0412 lt=0611)

npm config set unsafe-perm true

npm install -g --prefix=usrlocal logio

logio server start

httplocalhost8998

79

Nodejs中文電子書

80 9 Nodejs好用工具介紹

10

精選文章

101 Express

bull 使用 NodeJS + Express從 GETPOST Request取值 [邀稿中]

bull link2 [邀稿中]

bull link3 [邀稿中]

81

Nodejs中文電子書

82 10 精選文章

11

參考資源

111 Nodejs書籍

bull Programming Nodejs April 2012 (est) Travis Swicegood Pragmatic Bookshelf

bull e Little Book on CoffeeScript January 2012 Alex MacCaw OrsquoReilly Media

bull Node for Front-End Developers January 2012 Garann Means OrsquoReilly Media

bull Building Hypermedia APIs with HTML5 and Node November 2011 MikeAmundsen OrsquoReilly Media

bull Node Web Development August 2011 David Herron PacktPub

bull CoffeeScript Accelerated JavaScript Development July 2011 Trevor BurnhamPragmatic Bookshelf

bull Getting Started with GEO CouchDB and Nodejs July 2011 Mick omp-son OrsquoReilly Media

bull What Is Node July 2011 Brett McLaughlin OrsquoReilly Media

bull Node Up and Running May 2011 Tom Hughes-Croucher Mike WilsonOrsquoReilly Media

83

Nodejs中文電子書

bull Hands-on Nodejs e Nodejs introduction and API reference May 2011 Pe-dro Teixeira LeanPub

bull e Node Beginner Book Manuel Kiessling

bull Mastering Nodejs visionmedia

112 Nodejs影音教學

bull e Node Sessions e Best of OSCON 2011 August 2011 OrsquoReilly Media(Video)

bull Tom Hughes-Croucher on Node March 2011 OrsquoReilly Media (Video)

113 Nodejs教學網站

bull Node Tuts

114 Nodejs課程

bull Introduction To Nodejs Van Nguyen CodeLesson

84 11 參考資源

Page 10: Nodejs Wiki

Nodejs中文電子書

4 關於本書

前言

Nodejs是 JavaScript程式語言的開發框架由 04x至目前 v060版本核心上已經有許多變更當然目的只有一個就是讓開發變得更簡單速

度能夠更快這是所有人奮鬥的目標而 nodeJS 華文維基平台主力由nodeJStw一群喜好 javascript開發者共同主筆內文以中文為主希望能夠降低開發者學習門檻藉由我們拋磚引玉讓更多華人開發者共同學習

討論

CoffeeScript好好玩

5

Nodejs中文電子書

6 前言

1

Nodejs簡介

Nodejs是一個高效能易擴充的網站應用程式開發框架 (Web Applica-tion Framework)它誕生的原因是為了讓開發者能夠更容易開發高延展性的網路服務不需要經過太多複雜的調校效能調整及程式修改就能

滿足網路服務在不同發展階段對效能的要求

Ryan Dahl是NodeJS的催生者目前任職於 Joyent (主機託管服務公司)他開發 NodeJS的目的就是希望能解決 Apache在連線數量過高時緩衝區 (buffer)和系統資源會很快被耗盡的問題希望能建立一個新的開發框架以解決這個問題因此嘗試使用效能十分優秀的 V8 JavaScript Engine讓網站開發人員熟悉的 JavaScript程式語言也能應用於後端服務程式的開發並且具有出色的執行效能

JavaScript是功能強大的物件導向程式語言但是在 JavaScript的官方規格中主要是定義網頁 (以瀏覽器為基礎) 應用程式需要的應用程式介面(API)對 JavaScript程式的應用範圍有所侷限為使 JavaScript能夠在更多用途發展CommonJS規範一組標準函式庫 (standard library)使 JavaScript的應用範圍能夠和 RubyPython及 Java等語言同樣豐富撰寫 NodeJS的JavaScript程式碼符合 CommonJS規範可以使用 CommonJS API為基礎開發程式並且在不同的 CommonJS兼容 (compliant) JavaScript執行環境中程式碼具有可攜性

7

Nodejs中文電子書

瀏覽器的 JavaScript與實現 CommonJS規範的 NodeJS有何不同呢瀏覽器的 JavaScript 提供 XMLHttpRequest 讓程式可以和網頁伺服器建立資料傳輸連線但這通常只能適用於網站開發的需求因為我們只能用

XMLHttpRequest與網頁伺服器通訊卻無法利用它建立其他類型如 Telnet FTP NTP的伺服器通訊如果我們想開發網路服務程式例如 SMTP電子郵件伺服器就必須使用 Sockets建立 TCP (某些服務則用 UDP)監聽及連線其他程式語言如 PHPJavaPythonPerl及 Ruby等在標準開發環境中皆有提供 Sockets API而瀏覽器的 JavaScript基於安全及貼近網站設計需求的考量下並未將 Sockets列入標準函式庫之中CommonJS的規範填補這種基礎函式庫功能的空缺遵循 CommonJS規範的 NodeJS可以直接使用 Sockets API建立各種網路服務程式

JavaScript語言本身支援 Lambda的特性因此一個匿名函式 (anonymousfunction)可以被儲存成一個變數並當作參數傳遞給另一個函式

var proc1 = function(op x) return op(x)

var op1 = function(x) return x+1 var op2 = function(x) return xx

proc1(op1 3) result is 4 proc1(op2 5) result is 25

另一個 JavaScript開發者必須掌握的語言特性稱為 Closure

NodeJS符合 CommonJS的規範使得 Callback方式易於實現也能夠讓更多同好基於 JavaScript開發符合 NodeJS的外掛模組 (Module)

回想以前要寫一個能夠同時容納上百人的上線的網路服務需要花費

多大的苦工可能 10人多就需要經過一次程式調整而 NodeJS就是為了解決這個困境NodeJS因此誕生它是一種利用 V8 Javascript編譯器所開發的產品利用 V8編譯器的高效能與 Javascript的程式開發特性所產生的網路程式

開發人員所編寫出來的 Javascript腳本程式怎麼可能會比其他語言寫出來的網路程式還要快上許多呢以前的網路程式原理是將使用者每次的

連線 (connection)都開啟一個執行緒 (thread)當連線爆增的時候將會快速耗盡系統效能並且容易產生阻塞 (block)的發生

8 1 Nodejs簡介

Nodejs中文電子書

NodeJS對於資源的調校有所不同當程式接收到一筆連線 (connection)會通知作業系統透過 epoll kqueue devpoll或 select將連線保留並且放入heap中配置先讓連線進入休眠 (Sleep)狀態當系統通知時才會觸發連線的 callback這種處理連線方式只會佔用掉記憶體並不會使用到 CPU資源另外因為採用 Javascript語言的特性每個 request都會有一個 callback如此可以避免發生 Block的狀況發生

基於 Callback特性目前NodeJS大多應用於 Comet(long pulling) RequestServer或者是高連線數量的網路服務上目前也有許多公司將 NodeJS設為內部核心網路服務之一在 NodeJS也提供了外掛管理 (Node packagemanagement)讓愛好 NodeJS輕易開發更多有趣的服務外掛並且提供到 npm讓全世界使用者快速安裝使用

本書最後執行測試版本為 nodejs v068相關 API文件可查詢 lsquohttpnodejsorg lthttpnodejsorggtlsquo本書所有範例均可於 linux windows上執行如遇到任何問題歡迎至 lsquohttpnodejstw lthttpnodejstwgtlsquo詢問對於nodejs相關問題

9

Nodejs中文電子書

10 1 Nodejs簡介

2

JavaScript與 NodeJS

其實使用 JavaScript 在網頁端與伺服器端的差距並不大但是為了使NodeJS可以發揮他最強大的能力有一些知識還是必要的所以還是針對這些主題介紹一下其中 Event LoopScope以及 Callback其實是比較需要了解的基本知識cpscurrying ow control是更進階的技巧與應用

21 Event Loop

可能很多人在寫 Javascript 時並不知道他是怎麼被執行的這個時候可以參考一下 jQuery作者 John Resig一篇好文章介紹事件及 timer怎麼在瀏覽器中執行How JavaScript Timers Work通常在網頁中所有的Javascript執行完畢後(這部份全部都在 global scope跑除非執行函數)接下來就是如 John Resig解釋的這樣所有的事件處理函數以及 timer執行的函數會排在一個 queue結構中利用一個無窮迴圈不斷從 queue中取出函數來執行這個就是 event loop

(除了 John Resig的那篇文章Nicholas C Zakas的 ldquoProfessional Javascriptfor Web Developer 2nd editionrdquo 有一個試閱本httpyuiblogcomassetspdfzakas-projs-2ed-ch18pdf598頁剛好也有簡短的說明)

所以在 Javascript中雖然有非同步但是他並不是使用執行緒所有

11

Nodejs中文電子書

的事件或是非同步執行的函數都是在同一個執行緒中利用 event loop的方式在執行至於一些比較慢的動作例如 IO網頁 render re ow等實際動作會在其他執行緒跑等到有結果時才利用事件來觸發處理函數來處理

這樣的模型有幾個好處沒有執行緒的額外成本所以反應速度很快不會

有任何程式同時用到同一個變數不必考慮 lock也不會產生 dead lock所以程式撰寫很簡單但是也有一些潛在問題任一個函數執行時間較長都

會讓其他函數更慢執行(因為一個跑完才會跑另一個)在多核心硬體普遍

的現在無法用單一的應用程式 instance發揮所有的硬體能力用 NodeJS撰寫伺服器程式碰到的也是一樣的狀況要讓系統發揮 event loop的效能就要盡量利用事件的方式來組織程式架構另外對於一些有可能較為耗

時的操作可以考慮使用 processnextTick函數來讓他以非同步的方式執行避免在同一個函數中執行太久擋住所有函數的執行

如果想要測試 event loop怎樣在「瀏覽器」中運行可以在函數中呼叫alert()這樣會讓所有 Javascript的執行停下來尤其會干擾所有使用 timer的函數執行有一個簡單的例子這是一個會依照設定的時間間隔嚴格執

行動作的動畫如果時間過了就會跳過要執行的動作點按圖片以後人

物會快速旋轉但是在旋轉執行完畢前按下「delay」按鈕讓 alert訊息等久一點接下來的動畫就完全不會出現了

22 Scope與 Closure

要快速理解 JavaScript的 Scope(變數作用範圍)原理只要記住他是Lexical Scope就差不多了簡單地說變數作用範圍是依照程式定義時(或者叫做程式文本)的上下文決定而不是執行時的上下文決定

為了維護程式執行時所依賴的變數即使執行時程式運行在原本的

scope之外他的變數作用範圍仍然維持不變這時程式依賴的自由變數(定義時不是 local的而是在上一層 scope定義的變數)一樣可以使用就好像被關閉起來所以叫做 Closure用程式看比較好懂

function outter(arg1)

arg1 及 free_variable1 對 inner 函數來說都是自由變數

var free_variable1 = 3

return function inner(arg2)

12 2 JavaScript與 NodeJS

Nodejs中文電子書

var local_variable1 =2arg2 及 local_variable1 對 inner 函數來說都是本地變數

return arg1 + arg2 + free_variable + local_variable1

var a = outter(1)變數 a就是 outter函數執行後返回的 inner函數 var b =a(4)執行 inner函數執行時上下文已經在 outter函數之外但是仍然能正常執行而且可以使用定義在 outter函數裡面的 arg1及 free variable1變數 consolelog(b)結果 10

在 Javascript中scope最主要的單位是函數(另外有 global及 eval)所以有可能製造出 closure的狀況通常在形式上都是有巢狀的函數定義而且內側的函數使用到定義在外側函數裡面的變數

Closure有可能會造成記憶體洩漏主要是因為被參考的變數無法被垃圾收集機制處理造成佔用的資源無法釋放所以使用上必須考慮清楚

不要造成意外的記憶體洩漏(在上面的例子中如果 a一直未執行使用到的記憶體就不會被釋放)

跟透過函數的參數把變數傳給函數比較起來Javascript Engine會比較難對 Closure進行最佳化如果有效能上的考量這一點也需要注意

23 Callback

要介紹 Callback之前要先提到 JavaScript的特色

JavaScript是一種函數式語言(functional language)所有 Javascript語言內的函數都是高階函數 (higher order function這是數學名詞計算機用語好像是 rst class function意指函數使用沒有任何限制與其他物件一樣)也就是說函數可以作為函數的參數傳給函數也可以當作函數的返回值這個特性讓 Javascript的函數使用上非常有彈性而且功能強大

callback在形式上其實就是把函數傳給函數然後在適當的時機呼叫傳入的函數Javascript使用的事件系統通常就是使用這種形式NodeJS中有一個物件叫做 EventEmitter這是 NodeJS事件處理的核心物件所有會使用事件處理的函數都會「繼承」這個物件(這裡說的繼承實

作上應該像是 mixin)他的使用很簡單可以使用物件on(事件名稱callback

23 Callback 13

Nodejs中文電子書

函數) 或是物件addListener(事件名稱callback 函數) 把你想要處理事件的函數傳入在物件中可以使用物件emit(事件名稱 參數) 呼叫傳入的callback函數這是 Observer Pattern的簡單實作而且跟在網頁中使用 DOM的 addEventListener使用上很類似也很容易上手不過 NodeJS是大量使用非同步方式執行的應用所以程式邏輯幾乎都是寫在 callback函數中當邏輯比較複雜時大量的 callback會讓程式看起來很複雜也比較難單元測試舉例來說

var p client = new Db(lsquointegration tests 20rsquo new Server(ldquo127001rdquo 27017) lsquopkrsquoCustomPKFactory) p clientopen(function(err p client)

p clientdropDatabase(function(err done)

p clientcreateCollection(lsquotest custom keyrsquo function(err collection)

collectioninsert(lsquoarsquo1 function(err docs)

collection nd(lsquo idrsquonew ObjectID(ldquoaaaaaaaaaaaardquo) function(err cursor)

cursortoArray(function(err items) testassertEquals(1 itemslength) p clientclose()

)

)

)

)

)

)

這是在網路上看到的一段操作 mongodb的程式碼為了循序操作所以必須在一個 callback裡面呼叫下一個動作要使用的函數這個函數裡面還是會使用 callback最後就形成一個非常深的巢狀

這樣的程式碼會比較難進行單元測試有一個簡單的解決方式是

盡量不要使用匿名函數來當作 callback或是 event handler透過這樣的方式

14 2 JavaScript與 NodeJS

Nodejs中文電子書

就可以對各個 handler做單元測試了例如

var http = require(lsquohttprsquo) var tools =

cookieParser function(request response) if(requestheaders[rsquoCookiersquo]) do parsing

var server = httpcreateServer(function(request response)

thisemit(lsquoinitrsquo request response)

) serveron(lsquoinitrsquo toolscookieParser) serverlisten(8080 lsquo127001rsquo)

更進一步可以把 tools改成外部 module例如叫做 toolsjs

moduleexports = cookieParser function(request response) if(requestheaders[rsquoCookiersquo]) do parsing

然後把程式改成

var http = require(lsquohttprsquo)

var server = httpcreateServer(function(request response) thisemit(lsquoinitrsquo re-quest response)

) serveron(lsquoinitrsquo require(lsquo toolsrsquo)cookieParser) serverlisten(8080lsquo127001rsquo)

這樣就可以單元測試 cookieParser了例如使用 nodeunit時可以這樣寫

var testCase = require(lsquonodeunitrsquo)testCase moduleexports = testCase(

ldquosetUprdquo function(cb) thisrequest = headers Cookielsquoname1val1 name2val2rsquo thisresponse = thisresult= name1rsquoval1rsquoname2rsquoval2rsquo

cb()

ldquotearDownrdquo function(cb)

cb()

23 Callback 15

Nodejs中文電子書

ldquonormal caserdquo function(test)

testexpect(1) var obj = require(lsquotoolsrsquo)cookieParser(thisrequest thisresponse)testdeepEqual(obj thisresult) testdone()

)

善於利用模組可以讓程式更好維護與測試

24 CPS(Continuation-Passing Style)

cps 是 callback 使用上的特例形式上就是在函數最後呼叫 callback這樣就好像把函數執行後把結果交給 callback 繼續運行所以稱作continuation-passing style利用 cps可以在非同步執行的情況下透過傳給 callback的這個 cps callback來獲知 callback執行完畢或是取得執行結果例如

lthtmlgt ltbodygt ltdiv id=rdquopanelrdquo style=rdquovisibilityhiddenrdquogtlt divgtlt bodygt lt htmlgt ltscriptgt var request = new XMLHttpRequest() re-questopen(lsquoGETrsquo lsquotest749txt timestamp=rsquo+new Date()getTime() true) re-questaddEventListener(lsquoreadystatechangersquo function(next)

return function() if(thisreadyState===4ampampthisstatus===200) next(thisresponseText)lt== 傳入的 cps callback 在動作完成時執行並取得結果進一步處理

(function(str)lt==這個匿名函數就是 cps callback

documentgetElementById(lsquopanelrsquo)innerHTML=str docu-mentgetElementById(lsquopanelrsquo)stylevisibility = lsquovisiblersquo

) false) requestsend() ltscriptgt

進一步的應用也可以參考 2-6流程控制

16 2 JavaScript與 NodeJS

Nodejs中文電子書

25 函數返回函數與 Currying

前面的 cps範例裡面使用了函數返回函數這是為了把 cps callback傳遞給 onreadystatechange事件處理函數的方法(因為這個事件處理函數並沒有設計好會傳送接收這樣的參數)實際會執行的事件處理函數其實是內層返回的那個函數之外包覆的這個函數主要是為了利用 Closure把 next傳給內層的事件處理函數這個方法更常使用的地方是為了解決一些

scope問題例如

ltscriptgt var accu=0count=10 for(var i=0 iltcount i++)

setTimeout(

function() countndash accu+=i if(countlt=0)

consolelog(accu)

50)

ltscriptgt

最後得出的結果會是 100而不是想像中的 45這是因為等到 setTime-out指定的函數執行時變數 i已經變成 10而離開迴圈了要解決這個問題就需要透過 Closure來保存變數 i

ltscriptgt var accu=0count=10 for(var i=0 iltcount i++)

setTimeout(

function(i) return function() countndash

accu+=i if(countlt=0)

consolelog(accu)

(i)

50)

25 函數返回函數與 Currying 17

Nodejs中文電子書

淺藍色底色的部份是跟上面例子不一樣的地方 ltscriptgt

函數返回函數的另外一個用途是可以暫緩函數執行例如

function add(m n) return m+n

var a = add(20 10) consolelog(a)

add這個函數必須同時輸入兩個參數才有辦法執行如果我希望這個函數可以先給它一個參數等一些處理過後再給一個參數然後得到結

果就必須用函數返回函數的方式做修改

function add(m)

return function(n) return m+n

var wait another arg = add(20)先給一個參數 var a = function(arr)

var ret=0 for(var i=0iltarrlengthi++) ret+=arr[i] return ret

([1234])計算一下另一個參數 var b = wait another arg(a)然後再繼續執行 consolelog(b)

像這樣利用函數返回函數使得原本接受多個參數的函數可以一次

接受一個參數直到參數接收完成才執行得到結果的方式有一個學名就

叫做Currying

綜合以上許多奇技淫巧就可以透過用函數來處理函數的方式調整

程式流程接下來看看

26 流程控制

(以 sync方式使用 async函數避開巢狀 callback循序呼叫 async callback等奇技淫巧)

建議參考

bull httphowtonodeorgcontrol- ow

bull httphowtonodeorgcontrol- ow-part-ii

18 2 JavaScript與 NodeJS

Nodejs中文電子書

bull httphowtonodeorgcontrol- ow-part-iii

bull httpblogmixunet20110202essential-node-js-patterns-and-snippets

這幾篇都是非常經典的 NodeJSJavascript流程控制好文章(阿mixu是在介紹一些 pattern時提到這方面的主題)不過我還是用幾個簡單的程式介紹一下做法跟概念

並發與等待

下面的程式參考了 mixu文章中的做法

var wait = function(callbacks done) consolelog(lsquowait startrsquo) var counter = call-backslength var results = [] var next = function(result) 接收函數執行結果並判斷是否結束執行 resultspush(result) if(ndashcounter == 0) done(results)如果結束執行就把所有執行結果傳給指定的 callback處理 for(var i = 0 i lt callbackslength i++) 依次呼叫所有要執行的函數 callbacks[i](next) consolelog(lsquowait endrsquo)

wait( [ function(next) setTimeout(function() consolelog(lsquodone arsquo) var result =500 next(result) 500) function(next) setTimeout(function() con-solelog(lsquodone brsquo) var result = 1000 next(result) 1000) function(next)setTimeout(function() consolelog(lsquodone crsquo) var result = 1500 next(1500) 1500) ] function(results) var ret = 0 i=0 for( iltresultslength i++) ret+= results[i] consolelog(lsquodone all result lsquo+ret)

)

執行結果wait start wait end done a done b done c done all result 3000

可以看出來其實 wait並不是真的等到所有函數執行完才結束執行而是在所有傳給他的函數執行完畢後(不論同步非同步)才執行處理結

果的函數(也就是 done())

不過這樣的寫法還不夠實用因為沒辦法實際讓函數可以等待執行

完畢又能當作事件處理函數來實際使用上面參考到的 Tim Caswell的文

26 流程控制 19

Nodejs中文電子書

章裡面有一種解法不過還需要額外包裝(在他的例子中)NodeJS核心的 fs物件把一些函數(例如 readFile)用 Currying處理類似像這樣

var fs = require(lsquofsrsquo) var readFile = function(path)

return function(callback errback)

fsreadFile(path function(err data)

if(err) errback()

else callback(data)

)

其他部份可以參考 Tim Caswell的文章他的 Doparallel跟上面的 wait差不多意思這裡只提示一下他沒說到的地方

另外一種做法是去修飾一下 callback當他作為事件處理函數執行後再用 cps的方式取得結果

ltscriptgt function Wait(fns done)

var count = 0 var results = [] thisgetCallback = function(index)

count++ return (function(waitback)

return function() var i=0args=[]for(iltargumentslengthi++)

argspush(arguments[i])

argspush(waitback) fns[index]apply(thisargs)

)(function(result) resultspush(result) if(ndashcount == 0)

20 2 JavaScript與 NodeJS

Nodejs中文電子書

done(results)

)

var a = new Wait(

[ function(waitback) consolelog(lsquodone arsquo) var result = 500 wait-back(result) function(waitback) consolelog(lsquodone brsquo) var result =1000 waitback(result) function(waitback) consolelog(lsquodone crsquo) varresult = 1500 waitback(result) ] function(results) var ret = 0 i=0for( iltresultslength i++) ret += results[i] consolelog(lsquodone allresult lsquo+ret)

) var callbacks = [agetCallback(0)agetCallback(1)agetCallback(0)agetCallback(2)]一次取出要使用的 callbacks避免結果提早送出 setTimeout(callbacks[0]500) setTimeout(callbacks[1] 1000) setTimeout(callbacks[2] 1500) setTime-out(callbacks[3] 2000) 當所有取出的 callbacks執行完畢就呼叫 done()來處理結果 ltscriptgt

執行結果

done a done b done a done c done all result 3500

上面只是一些小實驗更成熟的作品是 Tim Caswell 的 stephttpsgithubcomcreationixstep

如果希望真正使用同步的方式寫非同步則需要使用 Promisejs這一類的 library來轉換非同步函數不過他結構比較複雜 XD(見仁見智不過有些人認為 Promise有點過頭了)httpblogsmsdncombrbucktonarchive20110815promise-js-2-0-promise-framework-for-javascriptaspx

如果想不透過其他 Library做轉換又能直接用同步方式執行非同步函數大概就要使用一些需要額外 compile原始程式碼的方法了例如 BrunoJouhier的 streamlinejshttpsgithubcomSagestreamlinejs

26 流程控制 21

Nodejs中文電子書

循序執行

循序執行可以協助把非常深的巢狀 callback結構攤平例如用這樣的簡單模組來做(serialjs)

moduleexports = function(funs) var c = 0 if(isArrayOfFunctions(funs))

throw(lsquoArgument type was not matched Should be array of func-tionsrsquo)

return function()

var args = Arrayprototypeslicecall(arguments 0) if((cgt=funslength))

c++ return funs[c-1]apply(this args)

function isArrayOfFunctions(f ) if(typeof f == lsquoobjectrsquo) return false if(flength)return false if(fconcat) return false if(fsplice) return false var i = 0 for(iltflength i++)

if(typeof f[i] == lsquofunctionrsquo) return false

return true

簡單的測試範例(testSerialjs)使用 fs 模組確定某個 path 是檔案然後讀取印出檔案內容這樣會用到兩層的 callback所以測試中有使用serial的版本與 nested callbacks的版本做對照

var serial = require(lsquoserialrsquo) fs = require(lsquofsrsquo) path = lsquodclientjsrsquo cb = serial([ func-tion(err data)

if(err)

if(dataisFile) fsreadFile(path cb)

22 2 JavaScript與 NodeJS

Nodejs中文電子書

else consolelog(err)

function(err data)

if(err) consolelog(lsquo[ attened by searial]rsquo) con-solelog(datatoString(lsquoutf8rsquo))

else consolelog(err)

]) fsstat(path cb)

fsstat(path function(err data) 第一層 callback if(err)

if(dataisFile)

fsreadFile(path function(err data) 第二層 callback if(err)

consolelog(lsquo[nested callbacks]rsquo) con-solelog(datatoString(lsquoutf8rsquo))

else consolelog(err)

)

else consolelog(err)

)

關鍵在於這些 callback的執行是有順序性的所以利用 serial返回的一個函數 cb來取代這些 callback然後在 cb中控制每次會循序呼叫的函數

26 流程控制 23

Nodejs中文電子書

就可以把巢狀的 callback攤平成循序的 function陣列(就是傳給 serial函數的參數)

測試中的dclientjs 是一個簡單的 dnode 測試程式放在跟 testSerialjs同一個目錄

var dnode = require(lsquodnodersquo)

dnodeconnect(8000 lsquolocalhostrsquo function(remote)

remoterestart(function(str) consolelog(str) processexit()

)

)

執行測試程式後出現結果

[ attened by searial] var dnode = require(lsquodnodersquo)

dnodeconnect(8000 lsquolocalhostrsquo function(remote)

remoterestart(function(str) consolelog(str) processexit()

)

)

[nested callbacks] var dnode = require(lsquodnodersquo)

dnodeconnect(8000 lsquolocalhostrsquo function(remote)

remoterestart(function(str) consolelog(str) processexit()

)

)

對照起來看兩種寫法的結果其實是一樣的但是利用 serialjs巢狀的 callback結構就會消失

不過這樣也只限於順序單純的狀況如果函數執行的順序比較複雜

(不只是一直線)還是需要用功能更完整的流程控制模組比較好例如

httpsgithubcomcaolanasync

24 2 JavaScript與 NodeJS

3

Nodejs安裝與設定

本篇將講解如何在各個不同 OS建立 NodeJS環境目前 NodeJS 048版本環境架設方式需依賴 Linux指令才可編譯完成當然在不同作業系統中也已經有 NodeJS package可以直接使用指令快速架設以下各不同作業系統解說如何安裝 NodeJS

31 Ubuntu Linux

更新推薦使用 nvm

git clone gitgithubcomcreationixnvmgit ~nvm

echo rdquo ~nvmnvmshrdquo gtgt ~bashrc

nvm install v0614

nvm alias default v0614

以 上 可 參 考 http dreamerslabcom blog tw how-to-setup-a-node-js-development-environment-on-ubuntu-11-04

使用 APT套件管理工具是常見的方法以下是使用社群提供的 PPA安裝方式

sudo apt-get install python-software-properties

sudo add-apt-repository ppachris-leanodejs-devel

25

Nodejs中文電子書

sudo apt-get update

sudo apt-get install nodejs

32 Other Linux

Linux很適合作為 NodeJS的伺服器作業系統及開發環境安裝前請先確認以下套件已正確安裝

bull curl (wget)用來下載檔案的工具

bull git先進的版本控制工具

bull g++ GNU C++軟體編譯工具

bull make GNU軟體專案建置工具

安裝指令如下如設有權限問題請在指令前面加上 sudo

git clone httpsgithubcomjoyentnodegit

cd node

git checkout v067

configure

make

sudo make install

接著測試 nodeJS是否正常執行

node --version

出現版本訊息即表示安裝成功

33 Windows

nodeJS 在 v060 版本之後開始正式支援 windows native直接使用nodeexe 就可以執行程式支援性完全與 linux 相同更棒的部份就是不需經過編譯經過下載之後簡單設定完成立即開發 node程式

26 3 Nodejs安裝與設定

Nodejs中文電子書

下載 nodejs安裝檔案 lthttpnodejsorgdownloadgt

如此完成 windows native nodeexe安裝接著可以進入 command line執行測試在 command line輸入rdquonoderdquo會出現 nodejs版本訊息畫面表示安裝完成

33 Windows 27

Nodejs中文電子書

28 3 Nodejs安裝與設定

4

Nodejs基礎

前篇文章已經由介紹安裝至設定都有完整介紹nodeJS 內部除了javascript常用的函式 (function)物件 (object)之外也有許多不同的自訂物件nodeJS預設建立這些物件為核心物件是為了要讓開發流程更為這些資料在官方文件已經具有許多具體說明接下來將會介紹在開發 nodeJS程式時常見的物件特性與使用方法

41 nodejs http伺服器建立

在 lsquonodejs官方網站 lthttpnodejsorggtlsquo裡面有舉一個最簡單的 HTTP伺服器建立一開始初步就是建立一個伺服器平台讓 nodejs可以與瀏覽器互相行為每種語言一開始的程式建立都是以 Hello world開始最初也從 Hello world帶各位進入 nodejs的世界

輸入以下程式碼儲存檔案為 node basic http hello worldjs

var server

ip = rdquo127001rdquo

port = 1337

http = require(rsquohttprsquo)

server = httpcreateServer(function (req res)

29

Nodejs中文電子書

reswriteHead(200 rsquoContent-Typersquo rsquotextplainrsquo)

resend(rsquoHello Worldnrsquo)

)

serverlisten(port ip)

consolelog(rdquoServer running at httprdquo + ip + rdquordquo + port)

程式碼解講一開始需要有幾個基本的變數

bull ip 機器本身的 ip位置因為使用本地端因此設定為 127001

bull port 需要開通的阜號通常設定為 http port 80因範例不希望與基本 port相衝隨意設定為 1337

在 nodejs 的程式中有許多預設的模組可以使用因此需要使用require方法將模組引入在這邊我們需要使用 http這個模組因此將 http載入Http模組裡面內建有許多方法可以使用這邊採用 createServer創建一個基本的 http伺服器再將 http伺服器給予一個 server變數裡面的回呼函式 (call back function)可以載入 http伺服器的資料與回應方法 (requestresponse)在程式裡面就可以看到我們直接回應給瀏覽器端所需的 Header回應內容

reswriteHead(200 rsquoContent-Typersquo rsquotextplainrsquo)

resend(rsquoHello Worldnrsquo)

Http伺服器需要設定 port ip在最後需要設定 Http監聽需要使用到listen事件監聽所有 Http伺服器行為

httplisten(port ip)

所有事情都完成之後需要確認伺服器正確執行因此使用 console在javascript裡就有這個原生物件console所印出的資料都會顯示於 nodejs伺服器頁面這邊印出的資料並不會傳送到使用者頁面上之後許多除壞

(debug)都會用到 console物件

consolelog(rdquoServer running at httprdquo + ip + rdquordquo + port)

30 4 Nodejs基礎

Nodejs中文電子書

42 nodejs http路徑建立

前面已經介紹如何建立一個簡單的 http伺服器接下來本章節將會介紹如何處理伺服器路徑 (rout)問題在 http的協定下所有從瀏覽器發出的要求 (request)都需要經過處理路徑上的建立也是如此

路徑就是指伺服器 ip位置或者是網域名稱之後對於伺服器給予的要求修改剛才的 hello world檔案修改如下

server = httpcreateServer(function (req res)

consolelog(requrl)

reswriteHead(200 rsquoContent-Typersquo rsquotextplainrsquo)

resend(rsquohello worldnrsquo)

)

重新啟動 nodejs程式後在瀏覽器端測試一下路徑行為結果如下圖

當在瀏覽器輸入 http1270011337test 在伺服器端會收到兩個要求一個是我們輸入的test要求另外一個則是faviconicotest的路徑要求http伺服器本身需要經過程式設定才有辦法回應給瀏覽器端所需要的回應在伺服器中所有的路徑要求都是需要被解析才有辦法取得資料從

42 nodejs http路徑建立 31

Nodejs中文電子書

上面解說可以了解到在 nodejs當中所有的路徑都需要經過設定未經過設定的路由會讓瀏覽器無法取得任何資料導致錯誤頁面的發生底下將會解

說如何設定路由同時避免發生錯誤情形先前 nodejs程式需要增加一些修改才能讓使用者透過瀏覽器在不同路徑時有不同的結果根據剛才

的程式做如下的修改

var server

ip = rdquo127001rdquo

port = 1337

http = require(rsquohttprsquo)

url = require(rsquourlrsquo)

path

server = httpcreateServer(function (req res)

path = urlparse(requrl)

reswriteHead(200 rsquoContent-Typersquo rsquotextplainrsquo)

switch (pathpathname)

case rdquoindexrdquo

resend(rsquoI am indexnrsquo)

break

case rdquotestrdquo

resend(rsquothis is test pagenrsquo)

break

default

resend(rsquodefault pagenrsquo)

break

)

serverlisten(port ip)

consolelog(rdquoServer running at httprdquo + ip + rdquordquo + port)

程式做了片段的修改首先載入 url模組另外增加一個 path變數url模組就跟如同他的命名一般專門處理 url字串處理裡面提供了許多方法來解決路徑上的問題因為從瀏覽器發出的要求路徑可能會帶有多種需求

或者 GET參數組合等因此我們需要將路徑單純化取用路徑部分的資料即可例如使用者可能會送出 http1270011337testsend=1如果直接

32 4 Nodejs基礎

Nodejs中文電子書

信任 requrl就會收到結果為testsend=1所以需要透過 url模組的方法將路徑資料過濾

在這邊使用 urlparse的方法裡面帶入網址格式資料會回傳路徑資料為了後需方便使用將回傳的資料設定到 path變數當中在回傳的路徑資料裡面包含資訊如下圖

這邊只需要使用單純的路徑要求直接取用 pathpathname就可以達到我們的目的

最後要做路徑的判別在不同的路徑可以指定不同的輸出在範例中

有三個可能結果第一個從瀏覽器輸入index就會顯示 index結果test 就會呈現出 test頁面最後如果都不符合預期的輸入會直接顯示 default的頁面最後的預防可以讓瀏覽器不會出現非預期結果讓程式的可靠性提昇

底下為測試結果

42 nodejs http路徑建立 33

Nodejs中文電子書

43 nodejs檔案讀取

前面已經介紹如何使用路由(rount)做出不同的回應實際應用只有在瀏覽器只有輸出幾個文字資料總是不夠的在本章節中將介紹如何使

用檔案讀取輸出檔案資料讓使用者在前端瀏覽器也可以讀取到完整的

html css javascript檔案輸出

檔案管理最重要的部分就是 lsquoFile system lthttpnodejsorgdocslatestapifshtmlgtlsquo這個模組此模組可以針對檔案做管理監控讀取等行為裡面有許多預設的方法底下是檔案輸出的基本範例底下會有兩個檔案

第一個是靜態 html檔案

ltDOCTYPE htmlgt

lthtml xmlns=rdquohttpwwww3org1999xhtmlrdquo xmllang=rdquozh-CNrdquo lang=rdquozh-CNrdquogt

ltheadgt

ltmeta http-equiv=rdquoContent-Typerdquo content=rdquotexthtml charset=UTF-7rdquo gt

lttitlegtnodejs index html filelttitlegt

ltheadgt

ltbodygt

34 4 Nodejs基礎

Nodejs中文電子書

lth1gtnodejs index html filelth1gt

ltbodygt

lthtmlgt

另一個為 nodejs程式

var fs = require(rdquofsrdquo)

filename = rdquostaticindexhtmlrdquo

encode = rdquoutf8rdquo

fsreadFile(filename encode function(err file)

consolelog(file)

)

一開始直接載入 le system模組 載入名稱為 fs讀取檔案主要使

用的方法為 readFile裡面以三個參數路徑 ( le path) 編碼方式 (encoding)

回應函式 (callback)路徑必須要設定為靜態 html所在位置才能指定到正確的檔案靜態檔案的編碼方式也必須正確這邊使用靜態檔案的編

碼為 utf8如果編碼設定錯誤nodejs讀取出來檔案結果會使用 byte raw格式輸出如果錯誤編碼格式會導致輸出資料為 byte raw

回應函式中裡面會使用兩個變數error為錯誤資訊如果讀取的檔案不存在或者發生錯誤error數值會是 true如果成功讀取資料 error將會是 falsecontent則是檔案內容資料讀取後將會把資料全數丟到 content這個變數當中

最後程式的輸出結果畫面如下

43 nodejs檔案讀取 35

Nodejs中文電子書

44 nodejs http靜態檔案輸出

前面已經了解如何讀取本地端檔案接下來將配合 http伺服器路由讓每個路由都能夠輸出相對應的靜態 html檔案首先新增加幾個靜態 html檔案

ltDOCTYPE htmlgt

lthtml xmlns=rdquohttpwwww3org1999xhtmlrdquo xmllang=rdquozh-CNrdquo lang=rdquozh-CNrdquogt

ltheadgt

ltmeta http-equiv=rdquoContent-Typerdquo content=rdquotexthtml charset=UTF-7rdquo gt

lttitlegtnodejs index html filelttitlegt

ltheadgt

ltbodygt

lth1gtnodejs index html filelth1gt

ltbodygt

lthtmlgt

ltDOCTYPE htmlgt

lthtml xmlns=rdquohttpwwww3org1999xhtmlrdquo xmllang=rdquozh-CNrdquo lang=rdquozh-CNrdquogt

ltheadgt

ltmeta http-equiv=rdquoContent-Typerdquo content=rdquotexthtml charset=UTF-7rdquo gt

lttitlegtnodejs test html filelttitlegt

ltheadgt

ltbodygt

lth1gtnodejs test html filelth1gt

ltbodygt

lthtmlgt

ltDOCTYPE htmlgt

lthtml xmlns=rdquohttpwwww3org1999xhtmlrdquo xmllang=rdquozh-CNrdquo lang=rdquozh-CNrdquogt

ltheadgt

ltmeta http-equiv=rdquoContent-Typerdquo content=rdquotexthtml charset=UTF-7rdquo gt

lttitlegtnodejs static html filelttitlegt

ltheadgt

ltbodygt

lth1gtnodejs static html filelth1gt

ltbodygt

lthtmlgt

36 4 Nodejs基礎

Nodejs中文電子書

準備一個包含基本路由功能的 http伺服器

var server

ip = rdquo127001rdquo

port = 1337

http = require(rsquohttprsquo)

url = require(rsquourlrsquo)

server = httpcreateServer(function (req res)

var path = urlparse(requrl)

)

serverlisten(port ip)

consolelog(rdquoServer running at httprdquo + ip + rdquordquo + port)

加入 le system 模組使用 readFile 的功能將這一段程式放置於

createServer的回應函式中

fsreadFile(filePath encode function(err file)

)

readFile的回應函式裡面加入頁面輸出讓瀏覽器可以正確讀到檔案在這邊我們設定讀取的檔案為 html 靜態檔案所以 Content-type 設定為texthtml讀取到檔案的內容將會正確輸出成 html靜態檔案

fsreadFile(filePath encode function(err file)

reswriteHead(200 rsquoContent-Typersquo rsquotexthtmlrsquo)

reswrite(file)

resend()

)

到這邊為止基本的程式內容都已經完成剩下一些細節的調整首先

路徑上必須做調整目前的靜態檔案全部都放置於 static資料夾底下設定一個變數來記住資料夾位置

接著將瀏覽器發出要求路徑與資料夾組合讀取正確 html靜態檔案使用者有可能會輸入錯誤路徑所以在讀取檔案的時候要加入錯誤處理

同時回應 404伺服器無法正確回應的 http header格式

加入這些細節的修改一個基本的 http 靜態 html輸出伺服器就完成了完整程式碼如下

44 nodejs http靜態檔案輸出 37

Nodejs中文電子書

var server

ip = rdquo127001rdquo

port = 1337

http = require(rsquohttprsquo)

fs = require(rdquofsrdquo)

folderPath = rdquostaticrdquo

url = require(rsquourlrsquo)

path

filePath

encode = rdquoutf8rdquo

server = httpcreateServer(function (req res)

path = urlparse(requrl)

filePath = folderPath + pathpathname

fsreadFile(filePath encode function(err file)

if (err)

reswriteHead(404 rsquoContent-Typersquo rsquotextplainrsquo)

resend()

return

reswriteHead(200 rsquoContent-Typersquo rsquotextapplicationrsquo)

reswrite(file)

resend()

)

)

serverlisten(port ip)

consolelog(rdquoServer running at httprdquo + ip + rdquordquo + port)

45 nodejs http GET資料擷取

http伺服器中除了路由之外另一個最常使用的方法就是擷取 GET資料本單元將會介紹如何透過基本 http伺服器擷取瀏覽器傳來的要求擷取 GET資料

在 http協定中GET參數都是藉由 URL從瀏覽器發出要求送至伺服器

38 4 Nodejs基礎

Nodejs中文電子書

端基本的傳送網址格式可能如下

http127001testsend=1amptest=2

上面這段網址裡面的 GET參數就是 send而這個變數的數值就為 1如果想要在 http伺服器取得 GET資料需要在瀏覽器給予的要求 (request)做處理

首先需要載入 query string這個模組這個模組主要是用來將字串資料

過濾後轉換成javascript物件

qs = require(rsquoquerystringrsquo)

接著在第一階段利用 url模組過濾瀏覽器發出的 URL資料後將回應的物件裡面的 query這個變數是一個字串值資料過濾後如下

send=1amptest=2

透過 query string使用 parse這個方法將資料轉換成 javascript物件就表示 GET的資料已經被伺服器端正式擷取下來

path = urlparse(requrl)

parameter = qsparse(pathquery)

整個 nodejs http GET參數完整擷取程式碼如下

var server

ip = rdquo127001rdquo

port = 1337

http = require(rsquohttprsquo)

qs = require(rsquoquerystringrsquo)

url = require(rsquourlrsquo)

server = httpcreateServer(function (req res)

var path = urlparse(requrl)

parameter = qsparse(pathquery)

consoledir(parameter)

reswriteHead(200 rsquoContent-Typersquo rsquotextplainrsquo)

reswrite(rsquoBrowser test GET parameternrsquo)

resend()

)

45 nodejs http GET資料擷取 39

Nodejs中文電子書

serverlisten(port ip)

consolelog(rdquoServer running at httprdquo + ip + rdquordquo + port)

程式運作之後由瀏覽器輸入要求網址之後nodejs伺服器端回應資料為

Server running at http1270011337

send rsquo1rsquo test rsquo1rsquo

46 本章結語

前面所解說的部份一大部分主要是處理 http伺服器基本問題雖然在某些部分有牽扯到 http 伺服器基本運作原理主要還是希望可以藉由這些基本範例練習 nodejs練習回應函式與語法串接的特點習慣編寫javascript風格程式當然直接這樣開發 nodejs是非常辛苦的接下來在模組實戰開發的部份將會介紹特定的模組一步一步帶領各位從無到有進行

nodejs應用程式開發

40 4 Nodejs基礎

5

NPM套件管理工具

npm全名為 Node Package Manager是 Nodejs的套件(package)管理工具類似 Perl的 ppm或 PHP的 PEAR等安裝 npm後使用npm install

module name指令即可安裝新套件維護管理套件的工作會更加輕鬆

npm可以讓Nodejs的開發者直接利用擴充線上的套件庫(packagesregistry)加速軟體專案的開發npm提供很友善的搜尋功能可以快速找到安裝需要的套件當這些套件發行新版本時npm也可以協助開發者自動更新這些套件

npm不僅可用於安裝新的套件它也支援搜尋列出已安裝模組及更新的功能

51 安裝 NPM

Nodejs在 063版本開始內建 npm讀者安裝的版本若是此版本或更新的版本就可以略過以下安裝說明

若要檢查 npm是否正確安裝可以使用以下的指令

npm -v

41

Nodejs中文電子書

執行結果說明

若 npm正確安裝執行 npm -v將會看到類似 110-2的版本訊息

若讀者安裝的 Nodejs版本比較舊或是有興趣嘗試自己動手安裝 npm工具則可以參考以下的說明

安裝於Windows系統

Nodejs for Windows 於 062 版開始內建 npm使用 nodejsorg 官方提供的安裝程式不需要進一步的設定就可以立即使用 npm指令對於Windows的開發者來說大幅降低環境設定的問題與門檻

除了使用 Nodejs內建的 npm讀者也可以從 npm官方提供的以下網址

httpnpmjsorgdist

這是由 npm提供的 Fancy Windows Install版本請下載壓縮檔(例如npm-110-3zip)並將壓縮檔內容解壓縮至 Nodejs的安裝路徑(例如CProgram Filesnodejs)

解壓縮後在 Nodejs的安裝路徑下應該有以下的檔案及資料夾

bull npmcmd(檔案)

bull node modules(資料夾)

安裝於 Linux系統

Ubuntu Linux的使用者可以加入 NPM Unoffcial PPA這個 repository即可使用 apt-get完成 npm安裝

42 5 NPM套件管理工具

Nodejs中文電子書

Ubuntu Linux使用 apt-get安裝 npm

sudo apt-get install python-software-properties

sudo add-apt-repository ppagias-kay-leenpm

sudo apt-get update

sudo apt-get install npm

npm官方提供的安裝程式 installsh可以適用於大多數的 Linux系統使用這個安裝程式請先確認

1 系統已安裝 curl工具(請使用 curl --version查看版本訊息)

2 已安裝 Nodejs並且 PATH正確設置

3 Nodejs的版本必須大於 04x

以下為 npm提供的安裝指令

curl httpnpmjsorginstallsh | sh

安裝成功會看到如下訊息

installsh安裝成功的訊息

npm10105 homeUSERNAMElocalnodelibnode_modulesnpm

It worked

安裝於Mac OS X

建議採用與 Nodejs相同的方式進行 npm的安裝例如使用MacPorts安裝 Nodejs就同樣使用MacPorts安裝 npm這樣對日後的維護才會更方便容易

使用 MacPorts安裝 npm是本書比較建議的方式它可以讓 npm的安裝移除及更新工作自動化將會幫助開發者節省寶貴時間

51 安裝 NPM 43

Nodejs中文電子書

安裝MacPorts的提示

在MacPorts網站可以取得 OS X系統版本對應的安裝程式(例如 106或 107)httpwwwmacportsorg安裝過程會詢問系統管理者密碼使用預設的選項完成安裝即可安

裝MacPorts之後在終端機執行 port -v將會看到MacPorts的版本訊息

安裝 npm之前先更新MacPorts的套件清單以確保安裝的 npm是最新版本

sudo port -d selfupdate

接著安裝 npm

sudo port install npm

若讀者的 Nodejs並非使用MacPorts安裝則不建議使用MacPorts安裝npm因為 MacPorts會自動檢查並安裝相依套件而 npm相依 nodejs所以MacPorts也會一併將 nodejs套件安裝造成先前讀者使用其它方式安裝的 nodejs被覆蓋

讀者可以先使用 MacPorts安裝 curl(sudo port install curl)

再參考 Linux的 installsh安裝方式即可使用 npm官方提供的安裝程式

NPM安裝後測試

npm是指令列工具(command-line tool)使用時請先打開系統的文字終端機工具

測試 npm安裝與設定是否正確請輸入指令如下

npm -v

或是

npm --version

如果 npm已經正確安裝設定就會顯示版本訊息

44 5 NPM套件管理工具

Nodejs中文電子書

執行結果(範例)

110-2

52 使用 NPM安裝套件

npm目前擁有超過 6000種套件(packages)可以在 npm registry使用關鍵字搜尋套件

httpsearchnpmjsorg

舉例來說在關鍵字欄位輸入「coffee-script」下方的清單就會自動列出包含 coffee-script關鍵字的套件

接著我們回到終端機模式的操作npm的指令工具本身就可以完成套

件搜尋的任務

例如以下的指令同樣可以找出 coffee-script相關套件

npm search coffee-script

以下是搜尋結果的參考畫面

52 使用 NPM安裝套件 45

Nodejs中文電子書

找到需要的套件後(例如 express)即可使用以下指令安裝

npm install coffee-script

值得注意的一點是使用 npm install會將指定的套件安裝在工

作目錄(Working Directory)的 node modules資料夾下

以Windows為例如果執行 npm install的目錄位於

Cproject1

那麼 npm將會自動建立一個 node modules的子目錄(如果不存在)

Cproject1node modules

並且將下載的套件放置於這個子目錄例如

Cproject1node modulescoffee-script

這個設計讓專案可以個別管理相依的套件並且可以在專案佈署或發

行時將這些套件(位於 node modules)一併打包方便其它專案的使用者不必再重新下載套件

這個 npm install的預設安裝模式為 local(本地)只會變更當前專案的資料夾不會影響系統

另一種安裝模式稱為 global(全域)這種模式會將套件安裝到系統資

料夾也就是 npm安裝路徑的 node modules資料夾例如

CProgram Filesnodejsnode modules

46 5 NPM套件管理工具

Nodejs中文電子書

是否要使用全域安裝可以依照套件是否提供新指令來判斷舉例來說express 套件提供 express 這個指令而 coffee-script 則提供 coffee

指令

在 local 安 裝 模 式 中這 些 指 令 的 程 式 檔 案會 被 安 裝 到node modules的 bin這個隱藏資料夾下除非將bin的路徑加入 PATH環境變數否則要執行這些指令將會相當不便

為了方便指令的執行我們可以在 npm install 加上 -g 或 --

global參數啟用 global安裝模式例如

npm install -g coffee-script

npm install -g express

使用 global安裝模式需要注意執行權限的問題若權限不足可能會出現類似以下的錯誤訊息

npm ERR Error EACCES permission denied rsquorsquo

npm ERR

npm ERR Please try running this command again as rootAdministrator

要獲得足夠得執行權限請參考以下說明

bull Windows 7 或 2008 以上在「命令提示字元」的捷徑按右鍵選擇「以系統管理員身分執行」執行 npm指令時就會具有 Administrator身分

bull Mac OS X或 Linux系統可以使用 sudo指令例如

sudo npm install -g express

bull Linux系統可以使用 root權限登入或是以「sudo su -」切換成 root身分(使用 root權限操作系統相當危險因此並不建議使用這種方式)

若加上 -g參數使用 npm install -g coffee-script完成安裝

後就可以在終端機執行 coffee指令例如

coffee -v

52 使用 NPM安裝套件 47

Nodejs中文電子書

執行結果(範例)

CoffeeScript version 120

53 套件的更新及維護

除了前一節說明的 search 及 install 用法npm 還提供其他許多指令(commands)

使用 npm help可以查詢可用的指令

npm help

執行結果(部分)

where ltcommandgt is one of

adduser apihelp author bin bugs c cache completion

config deprecate docs edit explore faq find get

help help-search home i info init install la link

list ll ln login ls outdated owner pack prefix

prune publish r rb rebuild remove restart rm root

run-script s se search set show star start stop

submodule tag test un uninstall unlink unpublish

unstar up update version view whoami

使用 npm help command可以查詢指令的詳細用法例如

npm help list

接下來本節要介紹開發過程常用的 npm指令

使用 list可以列出已安裝套件

npm list

48 5 NPM套件管理工具

Nodejs中文電子書

執行結果(範例)

-- coffee-script120

- express256

- connect185

| -- formidable108

-- mime124

-- mkdirp007

-- qs041

檢視某個套件的詳細資訊例如

npm show express

升級所有套件(如果該套件已發佈更新版本)

npm update

升級指定的套件

npm update express

移除指定的套件

npm uninstall express

54 使用 packagejson

對於正式的 Nodejs專案可以建立一個命名為 packagejson的設

定檔(純文字格式)檔案內容參考範例如下

54 使用 packagejson 49

Nodejs中文電子書

packagejson(範例)

rdquonamerdquo rdquoapplication-namerdquo

rdquoversionrdquo rdquo001rdquo

rdquoprivaterdquo true

rdquodependenciesrdquo

rdquoexpressrdquo rdquo255rdquo

rdquocoffee-scriptrdquo rdquolatestrdquo

rdquomongooserdquo rdquogt= 253rdquo

其中 name與 version依照專案的需求設置

需要注意的是 dependencies的設定它用於指定專案相依的套件名

稱及版本

bull rdquoexpressrdquo rdquo255rdquo

代表此專案相依版本 255的 express套件

bull rdquocoffee-scriptrdquo rdquolatestrdquo

使用最新版的 coffee-script套件(每次更新都會檢查新版)

bull rdquomongooserdquo rdquogt= 253rdquo

使用版本大於 253的 mongoose套件

假設某個套件的新版可能造成專案無法正常運作就必須指定套件的

版本避免專案的程式碼來不及更新以相容新版套件通常在開發初期的

專案需要盡可能維持新套件的相容性(以取得套件的更新或修正)可以

用「gt=」設定最低相容的版本或是使用「latest」設定永遠保持最新

套件

50 5 NPM套件管理工具

6

Express介紹

在前面的 nodejs基礎當中介紹許多許多開設 http的使用方法及介紹以及許多基本的 nodejs基本應用

接下來要介紹一個套件稱為 express [Express](httpexpressjscom)這個套件主要幫忙解決許多 nodejs http server所需要的基本服務讓開發 httpservice變得更為容易不需要像之前需要透過層層模組(module)才有辦法開始編寫自己的程式

這個套件是由 TJ Holowaychuk 製作而成的套件裡面包含基本的路由處理 (route)http資料處理(GETPOSTPUT)另外還與樣板套件(jshtml template engine)搭配同時也可以處理許多複雜化的問題

61 Express安裝

安裝方式十分簡單只要透過之前介紹的 NPM就可以使用簡單的指令安裝指令如下

這邊建議需要將此套件安裝成為全域模組方便日後使用

51

Nodejs中文電子書

62 Express基本操作

express的使用也十分簡單先來建立一個基本的 hello world

var app = require(rsquoexpressrsquo)createServer()

port = 1337

applisten(port)

appget(rsquorsquo function(req res)

ressend(rsquohello worldrsquo)

)

consolelog(rsquostart express servernrsquo)

可以從上面的程式碼發現基本操作與 nodejs http的建立方式沒有太大差異主要差在當我們設定路由時可以直接透過 appget方式設定回應與接受方式

63 Express路由處理

Express對於 http服務上有許多包裝讓開發者使用及設定上更為方便例如有幾個路由設定那我們就統一藉由 appget來處理

Create http server

appget(rsquorsquo function(req res)

ressend(rsquohello worldrsquo)

)

appget(rsquotestrsquo function(req res)

ressend(rsquotest renderrsquo)

)

appget(rsquouserrsquo function(req res)

ressend(rsquouser pagersquo)

)

52 6 Express介紹

Nodejs中文電子書

如上面的程式碼所表示appget可以帶入兩個參數第一個是路徑名稱設定第二個為回應函式 (call back function)回應函式裡面就如同之前的 createServer方法裡面包含 requestresponse兩個物件可供使用使用者就可以透過瀏覽器輸入不同的 url切換到不同的頁面顯示不同的結果

路由設定上也有基本的配對方式讓使用者從瀏覽器輸入的網址可以

是一個變數只要符合型態就可以有對應的頁面產出例如

Create http server

appget(rsquouseridrsquo function(req res)

ressend(rsquouser rsquo + reqparamsid)

)

appget(rsquonumberrsquo function(req res)

ressend(rsquonumber rsquo + reqparamsnumber)

)

裡 面 使 用 到number 從 網 址 輸 入 之 後 就 可 以 直 接 使 用reqparamsnumber 取得所輸入的資料變成 url 參數使用當然前面也是可以加上路徑的設定userid在瀏覽器上路徑必須符合userxxx透

過 reqparamsid就可以取到 xxx這個字串值

另外express參數處理也提供了路由參數配對處理也可以透過正規表示法作為參數設定

var app = require(rsquoexpressrsquo)createServer()

port = 1337

applisten(port)

appget(^ip((d23)((d23))((d23))((d23))) function(req res)

ressend(reqparams)

)

上面程式碼可以發現後面路由設定的型態是正規表示法裡面設定

格式為ip之後必須要加上 ip型態才會符合資料格式同時取得 ip資料已經由正規表示法將資料做分群因此可以取得 ip的四個數字

此程式執行之後可以透過瀏覽器測試輸入網址為 localhost3000ip25525510010可以從頁面獲得資料

63 Express路由處理 53

Nodejs中文電子書

此章節全部範例程式碼如下

overview

author Caesar Chi

blog clonnblogspotcom

version 20120226

create server

var app = require(rsquoexpressrsquo)createServer()

port = 1337

applisten(port)

normal style

appget(rsquorsquo function(req res)

ressend(rsquohello worldrsquo)

)

appget(rsquotestrsquo function(req res)

ressend(rsquotest renderrsquo)

)

parameter style

appget(rsquouseridrsquo function(req res)

ressend(rsquouser rsquo + reqparamsid)

)

appget(rsquonumberrsquo function(req res)

ressend(rsquonumber rsquo + reqparamsnumber)

)

REGX style

appget(^ip((d23)((d23))((d23))) function(req res)

ressend(reqparams)

)

appget(rsquorsquo function(req res)

ressend(rsquoPage not foundrsquo 404)

54 6 Express介紹

Nodejs中文電子書

)

consolelog(rsquostart express servernrsquo)

64 Express middleware

Express 裡面有一個十分好用的應用概念稱為 middleware可以透過middleware做出複雜的效果同時上面也有介紹 next方法參數傳遞就是靠 middleware的概念來傳遞參數讓開發者可以明確的控制程式邏輯

create http server

appuse(expressbodyParser())

appuse(expressmethodOverride())

appuse(expresssession())

上面都是一種 middleware的使用方式透過 appuse方式裡面載入函式執行方法回應函式會包含三個基本參數responserequestnext其中next表示下一個 middleware執行函式同時會自動將預設三個參數繼續帶往下個函式執行底下有個實驗

上面的片段程式執行後開啟瀏覽器連結上 localhost1337會發現伺服器回應結果順序如下

first middle ware

second middle ware

execute middle ware

end middleware function

從上面的結果可以得知剛才設定的 middleware都生效了在 appuse設定的 middleware是所有 url皆會執行方法如果有指定特定方法就可以使用 appget的 middleware設定在 appget函式的第二個參數就可以帶入函式或者是匿名函式只要函式裡面最後會接受 request response next這三個參數同時也有正確指定 next函式的執行時機最後都會執行到最後一個方法當然開發者也可以評估程式邏輯要執行到哪一個階段讓邏輯

可以更為分明

64 Express middleware 55

Nodejs中文電子書

65 Express路由應用

在實際開發上可能會遇到需要使用參數等方式混和變數一起使用

express裡面提供了一個很棒的處理方法 appall這個方式可以先採用基本路由配對再將設定為每個不同的處理方式開發者可以透過這個方式簡

化自己的程式邏輯

overview

author

version 20120226

create server

var app = require(rsquoexpressrsquo)createServer()

port = 1337

users = [

name rsquoClonnrsquo

name rsquoChirsquo

]

applisten(port)

appall(rsquouseridoprsquo function(req res next)

requser = users[reqparamsid]

if (requser)

next()

else

next(new Error(rsquocannot find user rsquo + reqparamsid))

)

appget(rsquouseridrsquo function(req res)

ressend(rsquoviewing rsquo + requsername)

)

appget(rsquouserideditrsquo function(req res)

ressend(rsquoediting rsquo + requsername)

)

56 6 Express介紹

Nodejs中文電子書

appget(rsquouseriddeletersquo function(req res)

ressend(rsquodeleting rsquo + requsername)

)

appget(rsquorsquo function(req res)

ressend(rsquoPage not foundrsquo 404)

)

consolelog(rsquostart express servernrsquo)

內部宣告一組預設的使用者分別給予名稱設定藉由 appall這個方法可以先將路由雛形建立再接下來設定 appget的路徑格式只要符合格式就會分配進入對應的方法中像上面的程式當中如果使用者輸入路徑為user0除了執行 appall程式之後執行 next方法就會對應到路徑設定為userid的這個方法當中如果使用者輸入路徑為user0edit就會執行到useridedit的對應方法

66 Express GET應用範例

我們準備一個使用 GET方法傳送資料的表單

ltDOCTYPE htmlgt

lthtmlgt

ltheadgt

ltmeta http-equiv=rdquoContent-Typerdquo content=rdquotexthtml charset=utf8rdquogt

lttitlegtNodejs 菜鳥筆記 (1)lttitlegt

ltlink rel=rdquostylesheetrdquo href=rdquocssstylecssrdquo type=rdquotextcssrdquo media=rdquoallrdquo gt

ltheadgt

ltbodygt

lth1gtNodejs 菜鳥筆記-註冊lth1gt

ltform id=rdquosignuprdquo method=rdquoGETrdquo action=rdquohttplocalhost3000Signuprdquogt

ltlabelgt 使用者名稱ltlabelgtltinput type=rdquotextrdquo id=rdquousernamerdquo name=rdquousernamerdquo gtltbrgt

ltlabelgt 電子郵件ltlabelgtltinput type=rdquotextrdquo id=rdquoemailrdquo name=rdquoemailrdquo gtltbrgt

ltinput type=rdquosubmitrdquo value=rdquo 註冊我的帳號rdquo gtltbrgt

ltformgt

ltbodygt

lthtmlgt

66 Express GET應用範例 57

Nodejs中文電子書

這個表單沒有什麼特別的地方我們只需要看第 9 行form 使用的method是 GET然後 action是rdquohttplocalhost3000Signupldquo等一下我們要來撰寫Signup這個 URL Path的處理程式

處理 Signup行為

我們知道所謂的 GET方法會透過 URL來把表單的值給帶過去以上面的表單來說到時候 URL會以這樣的形式傳遞

httplocalhost3000Signupusername=xxxampemail=xxx

所以要能處理這樣的資料必須有以下功能

bull 解析 URL

bull 辨別動作是 Signup

bull 解析出 username和 email

一旦能取得 username和 email的值程式就能加以應用了

處理 Signup的程式碼雛形

load module

var url = require(rsquourlrsquo)

urlData = urlparse(requrltrue)

action = urlDatapathname

reswriteHead(200 rdquoContent-Typerdquordquotexthtml charset=utf-8rdquo)

if (action === rdquoSignuprdquo)

user = urlDataquery

resend(rdquolth1gtrdquo + userusername + rdquo 歡迎您的加入 lth1gtltpgt 我們已經將會員啟用信寄至rdquo + useremail + rdquoltpgtrdquo)

首先需要加載 url module它是用來協助我們解析 URL的模組接著使用 urlparse方法第一個傳入 url字串變數也就是 requrl另外第二個參數的用意是設為 ture則引進 querystring模組來協助處理預設是 false它影響到的是 urlDataquery設為 true會傳回物件不然就只是一般的字串urlparse會將字串內容整理成一個物件我們把它指定給 urlData

action變數作為記錄 pathname這是我們稍後要來判斷目前網頁的動作是什麼接著先將 html表頭資訊 (Header)準備好再來判斷路徑邏輯如

58 6 Express介紹

Nodejs中文電子書

果是 Signup這個動作就把 urlDataquery裡的資料指定給 user然後輸出userusername和 useremail把使用者從表單註冊的資料顯示於頁面中

最後進行程式測試啟動 nodejs主程式之後開啟瀏覽器就會看到表單填寫完畢按下送出就可以看到結果了

完整 nodejs程式碼如下

var http = require(rsquohttprsquo)

url = require(rsquourlrsquo)

fs = require(rdquofsrdquo)

server

server = httpcreateServer(function (reqres)

var urlData

encode = rdquoutf8rdquo

filePath = rdquoviewexpress_get_example_formhtmlrdquo

action

urlData = urlparse(requrltrue)

action = urlDatapathname

reswriteHead(200 rdquoContent-Typerdquordquotexthtml charset=utf-8rdquo)

if (action === rdquoSignuprdquo)

user = urlDataquery

resend(rdquolth1gtrdquo + userusername + rdquo 歡迎您的加入 lth1gtltpgt 我們已經將會員啟用信寄至rdquo + useremail + rdquoltpgtrdquo)

else

fsreadFile(filePath encode function(err file)

reswrite(file)

resend()

)

)

serverlisten(3000)

consolelog(rsquoServer 跑起來了現在時間是rsquo + new Date())

66 Express GET應用範例 59

Nodejs中文電子書

67 Express POST應用範例

一開始準備基本的 html表單傳送內容以 POST方式form的 action屬性設定為 POST其餘 html內容與前一個範例應用相同

ltDOCTYPE htmlgt

lthtmlgt

ltheadgt

ltmeta http-equiv=rdquoContent-Typerdquo content=rdquotexthtml charset=utf8rdquogt

lttitlegtNodejs 菜鳥筆記 (1)lttitlegt

ltlink rel=rdquostylesheetrdquo href=rdquocssstylecssrdquo type=rdquotextcssrdquo media=rdquoallrdquo gt

ltheadgt

ltbodygt

lth1gtNodejs 菜鳥筆記-註冊lth1gt

ltform id=rdquosignuprdquo method=rdquoPOSTrdquo action=rdquohttplocalhost3000Signuprdquogt

ltlabelgt 使用者名稱ltlabelgtltinput type=rdquotextrdquo id=rdquousernamerdquo name=rdquousernamerdquo gtltbrgt

ltlabelgt 電子郵件ltlabelgtltinput type=rdquotextrdquo id=rdquoemailrdquo name=rdquoemailrdquo gtltbrgt

ltinput type=rdquosubmitrdquo value=rdquo 註冊我的帳號rdquo gtltbrgt

ltformgt

ltbodygt

lthtmlgt

nodejs的程式處理邏輯與前面 GET範例類似部分程式碼如下

qs = require(rsquoquerystringrsquo)

if (action === rdquoSignuprdquo)

formData = rsquorsquo

reqon(rdquodatardquo function (data)

formData += data

)

reqon(rdquoendrdquo function ()

user = qsparse(formData)

resend(rdquolth1gtrdquo + userusername + rdquo 歡迎您的加入 lth1gtltpgt 我們已經將會員啟用信寄至rdquo + useremail + rdquoltpgtrdquo)

)

60 6 Express介紹

Nodejs中文電子書

主要加入了rsquoquerystringrsquo這個moduel方便我們等一下解析由表單 POST回來的資料另外加入一個 formData的變數用來搜集待等一下表單回傳的資料前面的 GET範例我們只從 req拿出 url的資料這次要在利用req身上的事件處理

JavaScript 在訂閱事件時使用 addEventListener而 nodejs 使用的則是on這邊加上了監聽 data的事件會在瀏覽器傳送資料到Web Server時被執行參數是它所接收到的資料型態是字串

接著再增加 end的事件當瀏覽器的請求事件結束時它就會動作

由於瀏覽器使用 POST在上傳資料時會將資料一塊塊地上傳因為我們在監聽 data事件時透過 formData變數將它累加起來lt不過由於我們

上傳的資料很少一次就結束不過如果日後需要傳的是資料比較大的檔

案這個累加動作就很重要

當資料傳完就進到 end 事件中會用到 qsparse 來解析 formDataformData的內容是字串內容是

username=wordsmithampemail=wordsmith40somewhere

而 qsparse可以幫我們把這個 querystring轉成物件的格式也就是

username=wordsmithampemail=wordsmith40somewhere

一旦轉成物件並指定給 user之後其他的事情就和 GET方法時操作的一樣寫 response的表頭將內容回傳並將 userusername和 useremail代入到內容中

修改完成後接著執行 nodejs程式啟動 web server開啟瀏覽器進入表單測試看看POST的方式能否順利運作

完整程式碼如下

var http = require(rsquohttprsquo)

url = require(rsquourlrsquo)

fs = require(rdquofsrdquo)

qs = require(rsquoquerystringrsquo)

server

server = httpcreateServer(function (reqres)

var urlData

67 Express POST應用範例 61

Nodejs中文電子書

encode = rdquoutf8rdquo

filePath = rdquoviewexpress_post_example_formhtmlrdquo

formData

action

urlData = urlparse(requrltrue)

action = urlDatapathname

reswriteHead(200 rdquoContent-Typerdquordquotexthtml charset=utf-8rdquo)

if (action === rdquoSignuprdquo)

formData = rsquorsquo

reqon(rdquodatardquo function (data)

formData += data

)

reqon(rdquoendrdquo function ()

user = qsparse(formData)

resend(rdquolth1gtrdquo + userusername + rdquo 歡迎您的加入 lth1gtltpgt 我們已經將會員啟用信寄至rdquo + useremail + rdquoltpgtrdquo)

)

else

fsreadFile(filePath encode function(err file)

reswrite(file)

resend()

)

)

serverlisten(3000)

consolelog(rsquoServer 跑起來了現在時間是rsquo + new Date())

62 6 Express介紹

Nodejs中文電子書

68 Express AJAX應用範例

在 Nodejs要使用 Ajax傳送資料並且與之互動在接受資料的部份沒有太大的差別client端不是用 GET就是用 POST來傳資料重點在處理完後用 JSON格式回傳當然 Ajax不見得只傳 JSON格式有時是回傳一段 HTML碼不過後者對伺服器來說基本上就和前兩篇沒有差別了所以我們還是以回傳 JSON做為這一回的主題

這一回其實大多數的工作都會落在前端 Ajax上面前端要負責發送與接收資料並在接收資料後撤掉原先發送資料的表單並將取得的資料

改成 HTML格式之後放上頁面

首先先準備 HTML靜態頁面資料

ltDOCTYPE htmlgt

lthtmlgt

ltheadgt

lttitlegtNodejs 菜鳥筆記 (3)lttitlegt

ltlink rel=rdquostylesheetrdquo href=rdquocssstylecssrdquo type=rdquotextcssrdquo media=rdquoallrdquo gt

ltscript src=rdquohttpsajaxgoogleapiscomajaxlibsjquery171jqueryminjsrdquogtltscriptgt

ltheadgt

ltbodygt

lth1gtNodejs 菜鳥筆記-註冊 (用 Ajax)lth1gt

ltform id=rdquosignuprdquo action=rdquoSignuprdquo method=rdquoPOSTrdquogt

ltlabelgt 使用者名稱ltlabelgtltinput type=rdquotextrdquo id=rdquousernamerdquo name=rdquousernamerdquo gtltbrgt

ltlabelgt 電子郵件ltlabelgtltinput type=rdquotextrdquo id=rdquoemailrdquo name=rdquoemailrdquo gtltbrgt

ltinput type=rdquosubmitrdquo value=rdquo 註冊我的帳號rdquo gtltbrgt

ltformgt

ltdiv id=rdquoresultrdquogt

ltdivgt

ltscript type=rdquotextjavascriptrdquogt

$(function()$(rsquosignup input[type=rdquosubmitrdquo]rsquo)click(function(e)

var user =

userusername = $(rdquousernamerdquo)val()useremail = $(rdquoemailrdquo)val()

$post(rdquoSignuprdquouserfunction(data)greet(data)

)

68 Express AJAX應用範例 63

Nodejs中文電子書

epreventDefault()

)

var greet = function(msg)

var resultNode = $(rsquoresultrsquo)greeting = $(rsquolth2gtHirsquo+msgusername+rsquo 你的會員 id 是rsquo+ msgid +rsquo 我們會將會員啟動信寄至rsquo+msgemail+rsquolth2gtrsquo)

$(rsquosignuprsquo)hide()resultNodehtml(rdquordquo)

resultNodeappend(greeting)

)

ltscriptgt

ltbodygt

lthtmlgt

HTML頁面上準備了一個表單用來傳送註冊資料接著直接引用了Google CDN來載入 jQuery用來幫我們處理 Ajax的工作這次要傳送和接收的工作很大的變動都在 HTML頁面上的 JavaScript當中我們要做的事有 (相關 jQuery處理這邊不多做贅述指提起主要功能解說)

bull 用 jQUery取得 submit按鈕綁定它的 click動作

bull 取得表單 username和 email的值存放在 user這個物件中

bull 用 jQuery的 $post方法將 user的資料傳到 Server

bull 一旦成功取得資料後透過 greet這個 function組成回報給 user的訊息

bull 清空原本給使用者填資料的表單

bull 將 Server回傳的 usernameemail和 id這 3個資料組成回應的訊息

bull 將訊息放到原本表單的位置

經過以上的處理後一個 Ajax的表單的基本功能已經完成

接著進行 nodejs主要程式的編輯部分程式碼如下

var fs = require(rdquofsrdquo)

64 6 Express介紹

Nodejs中文電子書

qs = require(rsquoquerystringrsquo)

if (action === rdquoSignuprdquo)

formData = rsquorsquo

reqon(rdquodatardquo function (data)

formData += data

)

reqon(rdquoendrdquo function ()

var msg

user = qsparse(formData)

userid = rdquo123456rdquo

msg = JSONstringify(user)

reswriteHead(200 rdquoContent-Typerdquordquoapplicationjson charset=utf-8rdquordquoContent-Lengthrdquomsglength)

resend(msg)

)

這裡的程式和前面 POST範例基本上大同小異差別在

bull 幫 user的資料加上 id隨意存放一些文字進去讓 Server回傳的資料多於 Client端傳上來的不然會覺得 Server都沒做事

bull 增加了 msg 這個變數存放將 user 物件 JSON 文字化的結果JSONstringify這個轉換函式是 V8引擎所提供的如果你好奇的話

bull 大重點來了我們要告訴 Client端這次回傳的資料格式是 JSON所在 Content-type和 Content-Length要提供給 Client

Server很輕鬆就完成任務了最後進行程式測試啟動 nodejs主程式之後開啟瀏覽器就會看到表單填寫完畢按下送出就可以看到結果了

最後 nodejs本篇範例程式碼如下

var http = require(rsquohttprsquo)

url = require(rsquourlrsquo)

fs = require(rdquofsrdquo)

qs = require(rsquoquerystringrsquo)

server

68 Express AJAX應用範例 65

Nodejs中文電子書

server = httpcreateServer(function (reqres)

var urlData

encode = rdquoutf8rdquo

filePath = rdquoviewexpress_ajax_example_formhtmlrdquo

formData

action

urlData = urlparse(requrltrue)

action = urlDatapathname

if (action === rdquoSignuprdquo)

formData = rsquorsquo

reqon(rdquodatardquo function (data)

formData += data

)

reqon(rdquoendrdquo function ()

var msg

user = qsparse(formData)

userid = rdquo123456rdquo

msg = JSONstringify(user)

reswriteHead(200 rdquoContent-TyperdquordquoapplicationjsonrdquordquoContent-Lengthrdquomsglength)

resend(msg)

)

else

fsreadFile(filePath encode function(err file)

reswriteHead(200 rdquoContent-Typerdquordquotexthtml charset=utf-8rdquo)

reswrite(file)

resend()

)

)

serverlisten(3000)

66 6 Express介紹

Nodejs中文電子書

consolelog(rsquoServer 跑起來了現在時間是rsquo + new Date())

69 原始資料提供

bull [NodeJS初學者筆記 (1)-用 GET傳送資料] (httpithelpithomecomtwquestion10087402)

bull [NodeJS初學者筆記 (2)-用 POST傳送資料] (httpithelpithomecomtwquestion10087489)

bull [NodeJS 初學者筆記 (3)-用 Ajax 傳送資料] (httpithelpithomecomtwquestion10087627)

69 原始資料提供 67

Nodejs中文電子書

68 6 Express介紹

7

CoffeeScript

bull Spine

bull Hem

bull Spineapp

Letrsquos Have a Cup of CoffeeScript

bull es5-shim ECMAScript 5 compatibility shims for legacy JavaScript engines

ESnext

node-iform - A middleware for node-validator httpsgithubcomguileennode-iform

69

Nodejs中文電子書

70 7 CoffeeScript

8

製作一個 Hubot的 Plurk Adapter

81 應用事項提醒

此應用範例以CoffeeScript編寫主要程式架構採用Github釋放的Hubot專案以下有幾個主要注意事項請讀者注意

bull Hubot 一開始的架構除了內建的兩個 Adapter 之外其餘都要以Nodejs的Module方式才能運作

bull Hubot的 binhubot裡面寫著 npm install所以不管你怎麼改原始碼也不能改變第一點的狀況

其實上述都在討論同一件事情NodeJS的模組而且模組不能設定相對路徑之類的來安裝一定要包裝成 npm相符和格式才有辦法正確執行

82 建立 Adapter

首先需要準備兩個檔案

bull packagejson

bull plurkcoffee

71

Nodejs中文電子書

有這兩個就足以變成 NodeJS的模組了在開始 Coding之前先來設定一下 packagejson相依性

rdquonamerdquo rdquohubot-plurkrdquo

rdquoversionrdquo rdquo011rdquo

rdquomainrdquo rdquoplurkrdquo

rdquodependenciesrdquo

rdquohubotrdquo rdquogt=205rdquo

rdquooauthrdquo rdquordquo

rdquocronrdquordquordquo

這邊會使用到NodeJS的 cron模組(Module)而登入 Plurk需要OAuth標準授權才行所以也需要加載 OAuth模組

注意Hubot在讀取非內建模組時會自動在前面加上 hubot-的前置

83 建立 Robot跟 API

本篇應用基本上程式碼大部分參考 Hubot - Twitter Adapter來製作主要差異只有在使用 OAuth這個模組Twitter有 Streaming可用而 Plurk則得用 Comet方式來達到即時讀取

plurkcoffee程式碼

Robot = require(rdquohubotrdquo)robot()

Adapter = require(rdquohubtordquo)adapter()

EventEmitter = require(rdquoeventsrdquo)EventEmitter

oauth = require(rdquooauthrdquo)

cronJob = require(rdquocronrdquo)CronJob

class Plurk exntends Adapter

class PlurkStreaming exnteds EventEmitter

72 8 製作一個 Hubot的 Plurk Adapter

Nodejs中文電子書

先弄個基本架構主要 Class皆參考 Twitter Adapter命名方式接著再將 Plurk這個 Class增加幾個Method基本上只要有 run send reply就夠了而 run用來做初始化的部分

class Plurk entends Adapter

send (plurk id strings⋯) -gt

reply (plurk id strings⋯) -gt

run -gt

看起來有點東西接著來處理主要的 Plurk API結合部分

class PlurkStreaming extends EventEmitter

consuctor (options) -gt

plurk (callback) -gt

觀察河道

getChannel -gt

取得 Comet 網址

reply (plurk id message) -gt

回噗

acceptFriends -gt

接受好友

get (path callback) -gt

GET 請求

post (path body callback)-gt

POST 請求(其實是裝飾)

request (method path body callback)-gt

主要的 OAuth 請求

comet (server callback)-gt

噗浪的 Comet 傳回是 JavaScript Callback 要另外處理後才會變成 JSON

然後把注意力集中到 constructor上先把建構子弄好

constructor (options) -gt

super()

if optionskey and optionssecret and optionstoken and optionstoken secret

key = optionskey

secret = optionssecret

token = optionstoken

token secret = optionstoken secret

83 建立 Robot跟 API 73

Nodejs中文電子書

建立 OAuth 連接

consumer = new oauthOAuth(

rdquohttpwwwplurkcomOAuthrequest tokenrdquo

rdquohttpwwwplurkcomOAuthaccess tokenrdquo

key

secret

rdquo10rdquo

rdquohttpwwwplurkcomOAuthauthorizerdquo

rdquoHMAC-SHA1rdquo

)

domain = rdquowwwplurkcomrdquo

初始化取得 Comet 網址

do getChannel

else

throw new Error(rdquo 參數不足需要 Key Secret Token Token Secretrdquo)

接著來處理 request這個 method

request (method path body callback) -gt

記錄一下這次的 Request

consolelog(rdquohttpdomainpathrdquo)

Callback 這邊先不丟進去要用另一種方式處理

request = consumerget(rdquohttpdomainpathrdquo token token secret null)

requeston rdquoresponserdquo (res) -gt

reson rdquodatardquo (chunk) -gt

parseResponse(chunk+rsquorsquo callback)

reson rdquoendrdquo (data) -gt

consolelog rdquoEnd Request pathrdquo

reson rdquoerrorrdquo (data) -gt

consolelog rdquoError rdquo + data

requestend()

處理資料

parseResponse = (data callback) -gt

if datalength gt 0

用 TryCatch 避免處理 JSON 出錯導致整個中斷

try

callback null JSONparse(data)

catch err

74 8 製作一個 Hubot的 Plurk Adapter

Nodejs中文電子書

consolelog(rdquoError Parse JSONrdquo + data err)

繼續執行

callback null data ||

大致上就是這樣上面程式的架構已經將整個 Hubot Plurk Adapter完成因為在測試時竟然因為噗浪 Lag而沒讀到完整的 Comet資料然後造成程式異常為了避免這個問題發生因此需要加上為了完美呈現需要再

加上 Comet的處理所以要使用到 EventEmitter的功能

comet (server callback) -gt

在 Callback 裡面會找不到自身所以設定區域變數

self =

記錄一下這次的 Request

consolelog(rdquo[Comet] serverrdquo)

Callback 這邊先不丟進去要用另一種方式處理

request = consumerget(rdquohttpdomainpathrdquo token token secret null)

requeston rdquoresponserdquo (res) -gt

reson rdquodatardquo (chunk) -gt

parseResponse(chunk+rsquorsquo callback)

reson rdquoendrdquo (data) -gt

consolelog rdquoEnd Request pathrdquo

請求結束發出事件通知可以進行下一次請求

selfemit rdquonextPlurkrdquo

reson rdquoerrorrdquo (data) -gt

consolelog rdquoError rdquo + data

requestend()

處理資料

parseResponse = (data callback) -gt

if datalength gt 0

用 trycatch 避免失敗中斷

try

去掉 JavaScript 的 Callback

data = datamatch(CometChannelscriptCallback((+))s)

jsonData = rdquordquo

if data

83 建立 Robot跟 API 75

Nodejs中文電子書

jsonData = JSONparse(data[1])

else

如果沒有任何 Match 嘗試直接 parse

jsonData = JSONparse(data)

catch err

consolelog(rdquo[Comet] Errorrdquo data err)

用 TryCatch 避免處理 JSON 出錯導致整個中斷

try

只傳入 json 的 data 部分

callback null jsonDatadata

catch err

consolelog(rdquo[Comet]Error Parse JSONrdquo + data err)

繼續執行

callback null data ||

後面的 get跟 post就簡單多了

get (path callback) -gt

request(rdquoGETrdquo path null callback)

post (path body callback) -gt

request(rdquoPOSTrdquo path body callback)

接著處理取的 Comet網址的 getChannel

getChannel -gt

self =

get rdquoAPPRealtimegetUserChannelrdquo (error data) -gt

if error

檢查是否有 comet server

if datacomet server

selfchannel = datacomet server

如果沒有 Channel Ready 就嘗試連接會失敗

selfemit(rsquochannel readyrsquo)

那麼先來處理 Plurk Adaper好處理的部份

send (plruk id strings⋯)-gt

跟 Reply 一樣直接交給 reply 做

reply plurk id strings⋯

76 8 製作一個 Hubot的 Plurk Adapter

Nodejs中文電子書

reply (plurk id strings⋯) -gt

stringsforEach (message) =gt

botreply(plruk id message)

接著把 run處理好就可以上線運作摟

run -gt

self =

options =

key processenvHUBOT PLURK KEY

secret processenvHUBOT PLURK SECRET

token processenvHUBOT PLURK TOKEN

token secret processenvHUBOT PLURK TOKEN SECRET

創建剛剛的 API

bot = new PlurkStreaming(options)

依照 Twitter 的 new RobotTextMessage 會沒有反應所以參考 hubot-minecraft 的方式

r = robotconstructor

處理噗浪河道訊息

doPlurk = (data)-gt

檢查是否為回噗

if dataresponse

datacontent raw = dataresponsecontent raw

datauser id = dataresponseuser id

確定有噗浪 ID 跟訊息

if dataplurk id and datacontent raw

selfreceive new rTextMessage(dataplurk id datacontent raw)

取得 Comet Server 完成開始第一次 Comet 連接

boton rdquochannel readyrdquo () -gt

botplurk selfdoPlurk

上一次 Comet 完成繼續 Polling

boton rdquonextPlurkrdquo ()-gt

botplurk selfdoPlurk

定時接受好友邀請

do botacceptFriends

bot = bot

83 建立 Robot跟 API 77

Nodejs中文電子書

終於完成 AdapterHubot專案裡面的 scripts資料夾內是互動部分不需要像 Adapter如此大費周章處理只新增檔案並且設計好對白之後就會回噗了我開發用的機器人在此大家可以去跟他玩玩[httpplurkcomelct9620 bot](httpplurkcomelct9620 bot)

84 原始資料提供

bull [製 作 一 個 Hubot 的 噗 浪 Adapter](http revo-skillfrosttw blog20120318create-a-hubot-plurk-adapter)

78 8 製作一個 Hubot的 Plurk Adapter

9

Nodejs好用工具介紹

91 logio

Real-time log monitoring in your browser

httplogioorg

nodejs (gt=0412 lt=0611)

npm config set unsafe-perm true

npm install -g --prefix=usrlocal logio

logio server start

httplocalhost8998

79

Nodejs中文電子書

80 9 Nodejs好用工具介紹

10

精選文章

101 Express

bull 使用 NodeJS + Express從 GETPOST Request取值 [邀稿中]

bull link2 [邀稿中]

bull link3 [邀稿中]

81

Nodejs中文電子書

82 10 精選文章

11

參考資源

111 Nodejs書籍

bull Programming Nodejs April 2012 (est) Travis Swicegood Pragmatic Bookshelf

bull e Little Book on CoffeeScript January 2012 Alex MacCaw OrsquoReilly Media

bull Node for Front-End Developers January 2012 Garann Means OrsquoReilly Media

bull Building Hypermedia APIs with HTML5 and Node November 2011 MikeAmundsen OrsquoReilly Media

bull Node Web Development August 2011 David Herron PacktPub

bull CoffeeScript Accelerated JavaScript Development July 2011 Trevor BurnhamPragmatic Bookshelf

bull Getting Started with GEO CouchDB and Nodejs July 2011 Mick omp-son OrsquoReilly Media

bull What Is Node July 2011 Brett McLaughlin OrsquoReilly Media

bull Node Up and Running May 2011 Tom Hughes-Croucher Mike WilsonOrsquoReilly Media

83

Nodejs中文電子書

bull Hands-on Nodejs e Nodejs introduction and API reference May 2011 Pe-dro Teixeira LeanPub

bull e Node Beginner Book Manuel Kiessling

bull Mastering Nodejs visionmedia

112 Nodejs影音教學

bull e Node Sessions e Best of OSCON 2011 August 2011 OrsquoReilly Media(Video)

bull Tom Hughes-Croucher on Node March 2011 OrsquoReilly Media (Video)

113 Nodejs教學網站

bull Node Tuts

114 Nodejs課程

bull Introduction To Nodejs Van Nguyen CodeLesson

84 11 參考資源

Page 11: Nodejs Wiki

前言

Nodejs是 JavaScript程式語言的開發框架由 04x至目前 v060版本核心上已經有許多變更當然目的只有一個就是讓開發變得更簡單速

度能夠更快這是所有人奮鬥的目標而 nodeJS 華文維基平台主力由nodeJStw一群喜好 javascript開發者共同主筆內文以中文為主希望能夠降低開發者學習門檻藉由我們拋磚引玉讓更多華人開發者共同學習

討論

CoffeeScript好好玩

5

Nodejs中文電子書

6 前言

1

Nodejs簡介

Nodejs是一個高效能易擴充的網站應用程式開發框架 (Web Applica-tion Framework)它誕生的原因是為了讓開發者能夠更容易開發高延展性的網路服務不需要經過太多複雜的調校效能調整及程式修改就能

滿足網路服務在不同發展階段對效能的要求

Ryan Dahl是NodeJS的催生者目前任職於 Joyent (主機託管服務公司)他開發 NodeJS的目的就是希望能解決 Apache在連線數量過高時緩衝區 (buffer)和系統資源會很快被耗盡的問題希望能建立一個新的開發框架以解決這個問題因此嘗試使用效能十分優秀的 V8 JavaScript Engine讓網站開發人員熟悉的 JavaScript程式語言也能應用於後端服務程式的開發並且具有出色的執行效能

JavaScript是功能強大的物件導向程式語言但是在 JavaScript的官方規格中主要是定義網頁 (以瀏覽器為基礎) 應用程式需要的應用程式介面(API)對 JavaScript程式的應用範圍有所侷限為使 JavaScript能夠在更多用途發展CommonJS規範一組標準函式庫 (standard library)使 JavaScript的應用範圍能夠和 RubyPython及 Java等語言同樣豐富撰寫 NodeJS的JavaScript程式碼符合 CommonJS規範可以使用 CommonJS API為基礎開發程式並且在不同的 CommonJS兼容 (compliant) JavaScript執行環境中程式碼具有可攜性

7

Nodejs中文電子書

瀏覽器的 JavaScript與實現 CommonJS規範的 NodeJS有何不同呢瀏覽器的 JavaScript 提供 XMLHttpRequest 讓程式可以和網頁伺服器建立資料傳輸連線但這通常只能適用於網站開發的需求因為我們只能用

XMLHttpRequest與網頁伺服器通訊卻無法利用它建立其他類型如 Telnet FTP NTP的伺服器通訊如果我們想開發網路服務程式例如 SMTP電子郵件伺服器就必須使用 Sockets建立 TCP (某些服務則用 UDP)監聽及連線其他程式語言如 PHPJavaPythonPerl及 Ruby等在標準開發環境中皆有提供 Sockets API而瀏覽器的 JavaScript基於安全及貼近網站設計需求的考量下並未將 Sockets列入標準函式庫之中CommonJS的規範填補這種基礎函式庫功能的空缺遵循 CommonJS規範的 NodeJS可以直接使用 Sockets API建立各種網路服務程式

JavaScript語言本身支援 Lambda的特性因此一個匿名函式 (anonymousfunction)可以被儲存成一個變數並當作參數傳遞給另一個函式

var proc1 = function(op x) return op(x)

var op1 = function(x) return x+1 var op2 = function(x) return xx

proc1(op1 3) result is 4 proc1(op2 5) result is 25

另一個 JavaScript開發者必須掌握的語言特性稱為 Closure

NodeJS符合 CommonJS的規範使得 Callback方式易於實現也能夠讓更多同好基於 JavaScript開發符合 NodeJS的外掛模組 (Module)

回想以前要寫一個能夠同時容納上百人的上線的網路服務需要花費

多大的苦工可能 10人多就需要經過一次程式調整而 NodeJS就是為了解決這個困境NodeJS因此誕生它是一種利用 V8 Javascript編譯器所開發的產品利用 V8編譯器的高效能與 Javascript的程式開發特性所產生的網路程式

開發人員所編寫出來的 Javascript腳本程式怎麼可能會比其他語言寫出來的網路程式還要快上許多呢以前的網路程式原理是將使用者每次的

連線 (connection)都開啟一個執行緒 (thread)當連線爆增的時候將會快速耗盡系統效能並且容易產生阻塞 (block)的發生

8 1 Nodejs簡介

Nodejs中文電子書

NodeJS對於資源的調校有所不同當程式接收到一筆連線 (connection)會通知作業系統透過 epoll kqueue devpoll或 select將連線保留並且放入heap中配置先讓連線進入休眠 (Sleep)狀態當系統通知時才會觸發連線的 callback這種處理連線方式只會佔用掉記憶體並不會使用到 CPU資源另外因為採用 Javascript語言的特性每個 request都會有一個 callback如此可以避免發生 Block的狀況發生

基於 Callback特性目前NodeJS大多應用於 Comet(long pulling) RequestServer或者是高連線數量的網路服務上目前也有許多公司將 NodeJS設為內部核心網路服務之一在 NodeJS也提供了外掛管理 (Node packagemanagement)讓愛好 NodeJS輕易開發更多有趣的服務外掛並且提供到 npm讓全世界使用者快速安裝使用

本書最後執行測試版本為 nodejs v068相關 API文件可查詢 lsquohttpnodejsorg lthttpnodejsorggtlsquo本書所有範例均可於 linux windows上執行如遇到任何問題歡迎至 lsquohttpnodejstw lthttpnodejstwgtlsquo詢問對於nodejs相關問題

9

Nodejs中文電子書

10 1 Nodejs簡介

2

JavaScript與 NodeJS

其實使用 JavaScript 在網頁端與伺服器端的差距並不大但是為了使NodeJS可以發揮他最強大的能力有一些知識還是必要的所以還是針對這些主題介紹一下其中 Event LoopScope以及 Callback其實是比較需要了解的基本知識cpscurrying ow control是更進階的技巧與應用

21 Event Loop

可能很多人在寫 Javascript 時並不知道他是怎麼被執行的這個時候可以參考一下 jQuery作者 John Resig一篇好文章介紹事件及 timer怎麼在瀏覽器中執行How JavaScript Timers Work通常在網頁中所有的Javascript執行完畢後(這部份全部都在 global scope跑除非執行函數)接下來就是如 John Resig解釋的這樣所有的事件處理函數以及 timer執行的函數會排在一個 queue結構中利用一個無窮迴圈不斷從 queue中取出函數來執行這個就是 event loop

(除了 John Resig的那篇文章Nicholas C Zakas的 ldquoProfessional Javascriptfor Web Developer 2nd editionrdquo 有一個試閱本httpyuiblogcomassetspdfzakas-projs-2ed-ch18pdf598頁剛好也有簡短的說明)

所以在 Javascript中雖然有非同步但是他並不是使用執行緒所有

11

Nodejs中文電子書

的事件或是非同步執行的函數都是在同一個執行緒中利用 event loop的方式在執行至於一些比較慢的動作例如 IO網頁 render re ow等實際動作會在其他執行緒跑等到有結果時才利用事件來觸發處理函數來處理

這樣的模型有幾個好處沒有執行緒的額外成本所以反應速度很快不會

有任何程式同時用到同一個變數不必考慮 lock也不會產生 dead lock所以程式撰寫很簡單但是也有一些潛在問題任一個函數執行時間較長都

會讓其他函數更慢執行(因為一個跑完才會跑另一個)在多核心硬體普遍

的現在無法用單一的應用程式 instance發揮所有的硬體能力用 NodeJS撰寫伺服器程式碰到的也是一樣的狀況要讓系統發揮 event loop的效能就要盡量利用事件的方式來組織程式架構另外對於一些有可能較為耗

時的操作可以考慮使用 processnextTick函數來讓他以非同步的方式執行避免在同一個函數中執行太久擋住所有函數的執行

如果想要測試 event loop怎樣在「瀏覽器」中運行可以在函數中呼叫alert()這樣會讓所有 Javascript的執行停下來尤其會干擾所有使用 timer的函數執行有一個簡單的例子這是一個會依照設定的時間間隔嚴格執

行動作的動畫如果時間過了就會跳過要執行的動作點按圖片以後人

物會快速旋轉但是在旋轉執行完畢前按下「delay」按鈕讓 alert訊息等久一點接下來的動畫就完全不會出現了

22 Scope與 Closure

要快速理解 JavaScript的 Scope(變數作用範圍)原理只要記住他是Lexical Scope就差不多了簡單地說變數作用範圍是依照程式定義時(或者叫做程式文本)的上下文決定而不是執行時的上下文決定

為了維護程式執行時所依賴的變數即使執行時程式運行在原本的

scope之外他的變數作用範圍仍然維持不變這時程式依賴的自由變數(定義時不是 local的而是在上一層 scope定義的變數)一樣可以使用就好像被關閉起來所以叫做 Closure用程式看比較好懂

function outter(arg1)

arg1 及 free_variable1 對 inner 函數來說都是自由變數

var free_variable1 = 3

return function inner(arg2)

12 2 JavaScript與 NodeJS

Nodejs中文電子書

var local_variable1 =2arg2 及 local_variable1 對 inner 函數來說都是本地變數

return arg1 + arg2 + free_variable + local_variable1

var a = outter(1)變數 a就是 outter函數執行後返回的 inner函數 var b =a(4)執行 inner函數執行時上下文已經在 outter函數之外但是仍然能正常執行而且可以使用定義在 outter函數裡面的 arg1及 free variable1變數 consolelog(b)結果 10

在 Javascript中scope最主要的單位是函數(另外有 global及 eval)所以有可能製造出 closure的狀況通常在形式上都是有巢狀的函數定義而且內側的函數使用到定義在外側函數裡面的變數

Closure有可能會造成記憶體洩漏主要是因為被參考的變數無法被垃圾收集機制處理造成佔用的資源無法釋放所以使用上必須考慮清楚

不要造成意外的記憶體洩漏(在上面的例子中如果 a一直未執行使用到的記憶體就不會被釋放)

跟透過函數的參數把變數傳給函數比較起來Javascript Engine會比較難對 Closure進行最佳化如果有效能上的考量這一點也需要注意

23 Callback

要介紹 Callback之前要先提到 JavaScript的特色

JavaScript是一種函數式語言(functional language)所有 Javascript語言內的函數都是高階函數 (higher order function這是數學名詞計算機用語好像是 rst class function意指函數使用沒有任何限制與其他物件一樣)也就是說函數可以作為函數的參數傳給函數也可以當作函數的返回值這個特性讓 Javascript的函數使用上非常有彈性而且功能強大

callback在形式上其實就是把函數傳給函數然後在適當的時機呼叫傳入的函數Javascript使用的事件系統通常就是使用這種形式NodeJS中有一個物件叫做 EventEmitter這是 NodeJS事件處理的核心物件所有會使用事件處理的函數都會「繼承」這個物件(這裡說的繼承實

作上應該像是 mixin)他的使用很簡單可以使用物件on(事件名稱callback

23 Callback 13

Nodejs中文電子書

函數) 或是物件addListener(事件名稱callback 函數) 把你想要處理事件的函數傳入在物件中可以使用物件emit(事件名稱 參數) 呼叫傳入的callback函數這是 Observer Pattern的簡單實作而且跟在網頁中使用 DOM的 addEventListener使用上很類似也很容易上手不過 NodeJS是大量使用非同步方式執行的應用所以程式邏輯幾乎都是寫在 callback函數中當邏輯比較複雜時大量的 callback會讓程式看起來很複雜也比較難單元測試舉例來說

var p client = new Db(lsquointegration tests 20rsquo new Server(ldquo127001rdquo 27017) lsquopkrsquoCustomPKFactory) p clientopen(function(err p client)

p clientdropDatabase(function(err done)

p clientcreateCollection(lsquotest custom keyrsquo function(err collection)

collectioninsert(lsquoarsquo1 function(err docs)

collection nd(lsquo idrsquonew ObjectID(ldquoaaaaaaaaaaaardquo) function(err cursor)

cursortoArray(function(err items) testassertEquals(1 itemslength) p clientclose()

)

)

)

)

)

)

這是在網路上看到的一段操作 mongodb的程式碼為了循序操作所以必須在一個 callback裡面呼叫下一個動作要使用的函數這個函數裡面還是會使用 callback最後就形成一個非常深的巢狀

這樣的程式碼會比較難進行單元測試有一個簡單的解決方式是

盡量不要使用匿名函數來當作 callback或是 event handler透過這樣的方式

14 2 JavaScript與 NodeJS

Nodejs中文電子書

就可以對各個 handler做單元測試了例如

var http = require(lsquohttprsquo) var tools =

cookieParser function(request response) if(requestheaders[rsquoCookiersquo]) do parsing

var server = httpcreateServer(function(request response)

thisemit(lsquoinitrsquo request response)

) serveron(lsquoinitrsquo toolscookieParser) serverlisten(8080 lsquo127001rsquo)

更進一步可以把 tools改成外部 module例如叫做 toolsjs

moduleexports = cookieParser function(request response) if(requestheaders[rsquoCookiersquo]) do parsing

然後把程式改成

var http = require(lsquohttprsquo)

var server = httpcreateServer(function(request response) thisemit(lsquoinitrsquo re-quest response)

) serveron(lsquoinitrsquo require(lsquo toolsrsquo)cookieParser) serverlisten(8080lsquo127001rsquo)

這樣就可以單元測試 cookieParser了例如使用 nodeunit時可以這樣寫

var testCase = require(lsquonodeunitrsquo)testCase moduleexports = testCase(

ldquosetUprdquo function(cb) thisrequest = headers Cookielsquoname1val1 name2val2rsquo thisresponse = thisresult= name1rsquoval1rsquoname2rsquoval2rsquo

cb()

ldquotearDownrdquo function(cb)

cb()

23 Callback 15

Nodejs中文電子書

ldquonormal caserdquo function(test)

testexpect(1) var obj = require(lsquotoolsrsquo)cookieParser(thisrequest thisresponse)testdeepEqual(obj thisresult) testdone()

)

善於利用模組可以讓程式更好維護與測試

24 CPS(Continuation-Passing Style)

cps 是 callback 使用上的特例形式上就是在函數最後呼叫 callback這樣就好像把函數執行後把結果交給 callback 繼續運行所以稱作continuation-passing style利用 cps可以在非同步執行的情況下透過傳給 callback的這個 cps callback來獲知 callback執行完畢或是取得執行結果例如

lthtmlgt ltbodygt ltdiv id=rdquopanelrdquo style=rdquovisibilityhiddenrdquogtlt divgtlt bodygt lt htmlgt ltscriptgt var request = new XMLHttpRequest() re-questopen(lsquoGETrsquo lsquotest749txt timestamp=rsquo+new Date()getTime() true) re-questaddEventListener(lsquoreadystatechangersquo function(next)

return function() if(thisreadyState===4ampampthisstatus===200) next(thisresponseText)lt== 傳入的 cps callback 在動作完成時執行並取得結果進一步處理

(function(str)lt==這個匿名函數就是 cps callback

documentgetElementById(lsquopanelrsquo)innerHTML=str docu-mentgetElementById(lsquopanelrsquo)stylevisibility = lsquovisiblersquo

) false) requestsend() ltscriptgt

進一步的應用也可以參考 2-6流程控制

16 2 JavaScript與 NodeJS

Nodejs中文電子書

25 函數返回函數與 Currying

前面的 cps範例裡面使用了函數返回函數這是為了把 cps callback傳遞給 onreadystatechange事件處理函數的方法(因為這個事件處理函數並沒有設計好會傳送接收這樣的參數)實際會執行的事件處理函數其實是內層返回的那個函數之外包覆的這個函數主要是為了利用 Closure把 next傳給內層的事件處理函數這個方法更常使用的地方是為了解決一些

scope問題例如

ltscriptgt var accu=0count=10 for(var i=0 iltcount i++)

setTimeout(

function() countndash accu+=i if(countlt=0)

consolelog(accu)

50)

ltscriptgt

最後得出的結果會是 100而不是想像中的 45這是因為等到 setTime-out指定的函數執行時變數 i已經變成 10而離開迴圈了要解決這個問題就需要透過 Closure來保存變數 i

ltscriptgt var accu=0count=10 for(var i=0 iltcount i++)

setTimeout(

function(i) return function() countndash

accu+=i if(countlt=0)

consolelog(accu)

(i)

50)

25 函數返回函數與 Currying 17

Nodejs中文電子書

淺藍色底色的部份是跟上面例子不一樣的地方 ltscriptgt

函數返回函數的另外一個用途是可以暫緩函數執行例如

function add(m n) return m+n

var a = add(20 10) consolelog(a)

add這個函數必須同時輸入兩個參數才有辦法執行如果我希望這個函數可以先給它一個參數等一些處理過後再給一個參數然後得到結

果就必須用函數返回函數的方式做修改

function add(m)

return function(n) return m+n

var wait another arg = add(20)先給一個參數 var a = function(arr)

var ret=0 for(var i=0iltarrlengthi++) ret+=arr[i] return ret

([1234])計算一下另一個參數 var b = wait another arg(a)然後再繼續執行 consolelog(b)

像這樣利用函數返回函數使得原本接受多個參數的函數可以一次

接受一個參數直到參數接收完成才執行得到結果的方式有一個學名就

叫做Currying

綜合以上許多奇技淫巧就可以透過用函數來處理函數的方式調整

程式流程接下來看看

26 流程控制

(以 sync方式使用 async函數避開巢狀 callback循序呼叫 async callback等奇技淫巧)

建議參考

bull httphowtonodeorgcontrol- ow

bull httphowtonodeorgcontrol- ow-part-ii

18 2 JavaScript與 NodeJS

Nodejs中文電子書

bull httphowtonodeorgcontrol- ow-part-iii

bull httpblogmixunet20110202essential-node-js-patterns-and-snippets

這幾篇都是非常經典的 NodeJSJavascript流程控制好文章(阿mixu是在介紹一些 pattern時提到這方面的主題)不過我還是用幾個簡單的程式介紹一下做法跟概念

並發與等待

下面的程式參考了 mixu文章中的做法

var wait = function(callbacks done) consolelog(lsquowait startrsquo) var counter = call-backslength var results = [] var next = function(result) 接收函數執行結果並判斷是否結束執行 resultspush(result) if(ndashcounter == 0) done(results)如果結束執行就把所有執行結果傳給指定的 callback處理 for(var i = 0 i lt callbackslength i++) 依次呼叫所有要執行的函數 callbacks[i](next) consolelog(lsquowait endrsquo)

wait( [ function(next) setTimeout(function() consolelog(lsquodone arsquo) var result =500 next(result) 500) function(next) setTimeout(function() con-solelog(lsquodone brsquo) var result = 1000 next(result) 1000) function(next)setTimeout(function() consolelog(lsquodone crsquo) var result = 1500 next(1500) 1500) ] function(results) var ret = 0 i=0 for( iltresultslength i++) ret+= results[i] consolelog(lsquodone all result lsquo+ret)

)

執行結果wait start wait end done a done b done c done all result 3000

可以看出來其實 wait並不是真的等到所有函數執行完才結束執行而是在所有傳給他的函數執行完畢後(不論同步非同步)才執行處理結

果的函數(也就是 done())

不過這樣的寫法還不夠實用因為沒辦法實際讓函數可以等待執行

完畢又能當作事件處理函數來實際使用上面參考到的 Tim Caswell的文

26 流程控制 19

Nodejs中文電子書

章裡面有一種解法不過還需要額外包裝(在他的例子中)NodeJS核心的 fs物件把一些函數(例如 readFile)用 Currying處理類似像這樣

var fs = require(lsquofsrsquo) var readFile = function(path)

return function(callback errback)

fsreadFile(path function(err data)

if(err) errback()

else callback(data)

)

其他部份可以參考 Tim Caswell的文章他的 Doparallel跟上面的 wait差不多意思這裡只提示一下他沒說到的地方

另外一種做法是去修飾一下 callback當他作為事件處理函數執行後再用 cps的方式取得結果

ltscriptgt function Wait(fns done)

var count = 0 var results = [] thisgetCallback = function(index)

count++ return (function(waitback)

return function() var i=0args=[]for(iltargumentslengthi++)

argspush(arguments[i])

argspush(waitback) fns[index]apply(thisargs)

)(function(result) resultspush(result) if(ndashcount == 0)

20 2 JavaScript與 NodeJS

Nodejs中文電子書

done(results)

)

var a = new Wait(

[ function(waitback) consolelog(lsquodone arsquo) var result = 500 wait-back(result) function(waitback) consolelog(lsquodone brsquo) var result =1000 waitback(result) function(waitback) consolelog(lsquodone crsquo) varresult = 1500 waitback(result) ] function(results) var ret = 0 i=0for( iltresultslength i++) ret += results[i] consolelog(lsquodone allresult lsquo+ret)

) var callbacks = [agetCallback(0)agetCallback(1)agetCallback(0)agetCallback(2)]一次取出要使用的 callbacks避免結果提早送出 setTimeout(callbacks[0]500) setTimeout(callbacks[1] 1000) setTimeout(callbacks[2] 1500) setTime-out(callbacks[3] 2000) 當所有取出的 callbacks執行完畢就呼叫 done()來處理結果 ltscriptgt

執行結果

done a done b done a done c done all result 3500

上面只是一些小實驗更成熟的作品是 Tim Caswell 的 stephttpsgithubcomcreationixstep

如果希望真正使用同步的方式寫非同步則需要使用 Promisejs這一類的 library來轉換非同步函數不過他結構比較複雜 XD(見仁見智不過有些人認為 Promise有點過頭了)httpblogsmsdncombrbucktonarchive20110815promise-js-2-0-promise-framework-for-javascriptaspx

如果想不透過其他 Library做轉換又能直接用同步方式執行非同步函數大概就要使用一些需要額外 compile原始程式碼的方法了例如 BrunoJouhier的 streamlinejshttpsgithubcomSagestreamlinejs

26 流程控制 21

Nodejs中文電子書

循序執行

循序執行可以協助把非常深的巢狀 callback結構攤平例如用這樣的簡單模組來做(serialjs)

moduleexports = function(funs) var c = 0 if(isArrayOfFunctions(funs))

throw(lsquoArgument type was not matched Should be array of func-tionsrsquo)

return function()

var args = Arrayprototypeslicecall(arguments 0) if((cgt=funslength))

c++ return funs[c-1]apply(this args)

function isArrayOfFunctions(f ) if(typeof f == lsquoobjectrsquo) return false if(flength)return false if(fconcat) return false if(fsplice) return false var i = 0 for(iltflength i++)

if(typeof f[i] == lsquofunctionrsquo) return false

return true

簡單的測試範例(testSerialjs)使用 fs 模組確定某個 path 是檔案然後讀取印出檔案內容這樣會用到兩層的 callback所以測試中有使用serial的版本與 nested callbacks的版本做對照

var serial = require(lsquoserialrsquo) fs = require(lsquofsrsquo) path = lsquodclientjsrsquo cb = serial([ func-tion(err data)

if(err)

if(dataisFile) fsreadFile(path cb)

22 2 JavaScript與 NodeJS

Nodejs中文電子書

else consolelog(err)

function(err data)

if(err) consolelog(lsquo[ attened by searial]rsquo) con-solelog(datatoString(lsquoutf8rsquo))

else consolelog(err)

]) fsstat(path cb)

fsstat(path function(err data) 第一層 callback if(err)

if(dataisFile)

fsreadFile(path function(err data) 第二層 callback if(err)

consolelog(lsquo[nested callbacks]rsquo) con-solelog(datatoString(lsquoutf8rsquo))

else consolelog(err)

)

else consolelog(err)

)

關鍵在於這些 callback的執行是有順序性的所以利用 serial返回的一個函數 cb來取代這些 callback然後在 cb中控制每次會循序呼叫的函數

26 流程控制 23

Nodejs中文電子書

就可以把巢狀的 callback攤平成循序的 function陣列(就是傳給 serial函數的參數)

測試中的dclientjs 是一個簡單的 dnode 測試程式放在跟 testSerialjs同一個目錄

var dnode = require(lsquodnodersquo)

dnodeconnect(8000 lsquolocalhostrsquo function(remote)

remoterestart(function(str) consolelog(str) processexit()

)

)

執行測試程式後出現結果

[ attened by searial] var dnode = require(lsquodnodersquo)

dnodeconnect(8000 lsquolocalhostrsquo function(remote)

remoterestart(function(str) consolelog(str) processexit()

)

)

[nested callbacks] var dnode = require(lsquodnodersquo)

dnodeconnect(8000 lsquolocalhostrsquo function(remote)

remoterestart(function(str) consolelog(str) processexit()

)

)

對照起來看兩種寫法的結果其實是一樣的但是利用 serialjs巢狀的 callback結構就會消失

不過這樣也只限於順序單純的狀況如果函數執行的順序比較複雜

(不只是一直線)還是需要用功能更完整的流程控制模組比較好例如

httpsgithubcomcaolanasync

24 2 JavaScript與 NodeJS

3

Nodejs安裝與設定

本篇將講解如何在各個不同 OS建立 NodeJS環境目前 NodeJS 048版本環境架設方式需依賴 Linux指令才可編譯完成當然在不同作業系統中也已經有 NodeJS package可以直接使用指令快速架設以下各不同作業系統解說如何安裝 NodeJS

31 Ubuntu Linux

更新推薦使用 nvm

git clone gitgithubcomcreationixnvmgit ~nvm

echo rdquo ~nvmnvmshrdquo gtgt ~bashrc

nvm install v0614

nvm alias default v0614

以 上 可 參 考 http dreamerslabcom blog tw how-to-setup-a-node-js-development-environment-on-ubuntu-11-04

使用 APT套件管理工具是常見的方法以下是使用社群提供的 PPA安裝方式

sudo apt-get install python-software-properties

sudo add-apt-repository ppachris-leanodejs-devel

25

Nodejs中文電子書

sudo apt-get update

sudo apt-get install nodejs

32 Other Linux

Linux很適合作為 NodeJS的伺服器作業系統及開發環境安裝前請先確認以下套件已正確安裝

bull curl (wget)用來下載檔案的工具

bull git先進的版本控制工具

bull g++ GNU C++軟體編譯工具

bull make GNU軟體專案建置工具

安裝指令如下如設有權限問題請在指令前面加上 sudo

git clone httpsgithubcomjoyentnodegit

cd node

git checkout v067

configure

make

sudo make install

接著測試 nodeJS是否正常執行

node --version

出現版本訊息即表示安裝成功

33 Windows

nodeJS 在 v060 版本之後開始正式支援 windows native直接使用nodeexe 就可以執行程式支援性完全與 linux 相同更棒的部份就是不需經過編譯經過下載之後簡單設定完成立即開發 node程式

26 3 Nodejs安裝與設定

Nodejs中文電子書

下載 nodejs安裝檔案 lthttpnodejsorgdownloadgt

如此完成 windows native nodeexe安裝接著可以進入 command line執行測試在 command line輸入rdquonoderdquo會出現 nodejs版本訊息畫面表示安裝完成

33 Windows 27

Nodejs中文電子書

28 3 Nodejs安裝與設定

4

Nodejs基礎

前篇文章已經由介紹安裝至設定都有完整介紹nodeJS 內部除了javascript常用的函式 (function)物件 (object)之外也有許多不同的自訂物件nodeJS預設建立這些物件為核心物件是為了要讓開發流程更為這些資料在官方文件已經具有許多具體說明接下來將會介紹在開發 nodeJS程式時常見的物件特性與使用方法

41 nodejs http伺服器建立

在 lsquonodejs官方網站 lthttpnodejsorggtlsquo裡面有舉一個最簡單的 HTTP伺服器建立一開始初步就是建立一個伺服器平台讓 nodejs可以與瀏覽器互相行為每種語言一開始的程式建立都是以 Hello world開始最初也從 Hello world帶各位進入 nodejs的世界

輸入以下程式碼儲存檔案為 node basic http hello worldjs

var server

ip = rdquo127001rdquo

port = 1337

http = require(rsquohttprsquo)

server = httpcreateServer(function (req res)

29

Nodejs中文電子書

reswriteHead(200 rsquoContent-Typersquo rsquotextplainrsquo)

resend(rsquoHello Worldnrsquo)

)

serverlisten(port ip)

consolelog(rdquoServer running at httprdquo + ip + rdquordquo + port)

程式碼解講一開始需要有幾個基本的變數

bull ip 機器本身的 ip位置因為使用本地端因此設定為 127001

bull port 需要開通的阜號通常設定為 http port 80因範例不希望與基本 port相衝隨意設定為 1337

在 nodejs 的程式中有許多預設的模組可以使用因此需要使用require方法將模組引入在這邊我們需要使用 http這個模組因此將 http載入Http模組裡面內建有許多方法可以使用這邊採用 createServer創建一個基本的 http伺服器再將 http伺服器給予一個 server變數裡面的回呼函式 (call back function)可以載入 http伺服器的資料與回應方法 (requestresponse)在程式裡面就可以看到我們直接回應給瀏覽器端所需的 Header回應內容

reswriteHead(200 rsquoContent-Typersquo rsquotextplainrsquo)

resend(rsquoHello Worldnrsquo)

Http伺服器需要設定 port ip在最後需要設定 Http監聽需要使用到listen事件監聽所有 Http伺服器行為

httplisten(port ip)

所有事情都完成之後需要確認伺服器正確執行因此使用 console在javascript裡就有這個原生物件console所印出的資料都會顯示於 nodejs伺服器頁面這邊印出的資料並不會傳送到使用者頁面上之後許多除壞

(debug)都會用到 console物件

consolelog(rdquoServer running at httprdquo + ip + rdquordquo + port)

30 4 Nodejs基礎

Nodejs中文電子書

42 nodejs http路徑建立

前面已經介紹如何建立一個簡單的 http伺服器接下來本章節將會介紹如何處理伺服器路徑 (rout)問題在 http的協定下所有從瀏覽器發出的要求 (request)都需要經過處理路徑上的建立也是如此

路徑就是指伺服器 ip位置或者是網域名稱之後對於伺服器給予的要求修改剛才的 hello world檔案修改如下

server = httpcreateServer(function (req res)

consolelog(requrl)

reswriteHead(200 rsquoContent-Typersquo rsquotextplainrsquo)

resend(rsquohello worldnrsquo)

)

重新啟動 nodejs程式後在瀏覽器端測試一下路徑行為結果如下圖

當在瀏覽器輸入 http1270011337test 在伺服器端會收到兩個要求一個是我們輸入的test要求另外一個則是faviconicotest的路徑要求http伺服器本身需要經過程式設定才有辦法回應給瀏覽器端所需要的回應在伺服器中所有的路徑要求都是需要被解析才有辦法取得資料從

42 nodejs http路徑建立 31

Nodejs中文電子書

上面解說可以了解到在 nodejs當中所有的路徑都需要經過設定未經過設定的路由會讓瀏覽器無法取得任何資料導致錯誤頁面的發生底下將會解

說如何設定路由同時避免發生錯誤情形先前 nodejs程式需要增加一些修改才能讓使用者透過瀏覽器在不同路徑時有不同的結果根據剛才

的程式做如下的修改

var server

ip = rdquo127001rdquo

port = 1337

http = require(rsquohttprsquo)

url = require(rsquourlrsquo)

path

server = httpcreateServer(function (req res)

path = urlparse(requrl)

reswriteHead(200 rsquoContent-Typersquo rsquotextplainrsquo)

switch (pathpathname)

case rdquoindexrdquo

resend(rsquoI am indexnrsquo)

break

case rdquotestrdquo

resend(rsquothis is test pagenrsquo)

break

default

resend(rsquodefault pagenrsquo)

break

)

serverlisten(port ip)

consolelog(rdquoServer running at httprdquo + ip + rdquordquo + port)

程式做了片段的修改首先載入 url模組另外增加一個 path變數url模組就跟如同他的命名一般專門處理 url字串處理裡面提供了許多方法來解決路徑上的問題因為從瀏覽器發出的要求路徑可能會帶有多種需求

或者 GET參數組合等因此我們需要將路徑單純化取用路徑部分的資料即可例如使用者可能會送出 http1270011337testsend=1如果直接

32 4 Nodejs基礎

Nodejs中文電子書

信任 requrl就會收到結果為testsend=1所以需要透過 url模組的方法將路徑資料過濾

在這邊使用 urlparse的方法裡面帶入網址格式資料會回傳路徑資料為了後需方便使用將回傳的資料設定到 path變數當中在回傳的路徑資料裡面包含資訊如下圖

這邊只需要使用單純的路徑要求直接取用 pathpathname就可以達到我們的目的

最後要做路徑的判別在不同的路徑可以指定不同的輸出在範例中

有三個可能結果第一個從瀏覽器輸入index就會顯示 index結果test 就會呈現出 test頁面最後如果都不符合預期的輸入會直接顯示 default的頁面最後的預防可以讓瀏覽器不會出現非預期結果讓程式的可靠性提昇

底下為測試結果

42 nodejs http路徑建立 33

Nodejs中文電子書

43 nodejs檔案讀取

前面已經介紹如何使用路由(rount)做出不同的回應實際應用只有在瀏覽器只有輸出幾個文字資料總是不夠的在本章節中將介紹如何使

用檔案讀取輸出檔案資料讓使用者在前端瀏覽器也可以讀取到完整的

html css javascript檔案輸出

檔案管理最重要的部分就是 lsquoFile system lthttpnodejsorgdocslatestapifshtmlgtlsquo這個模組此模組可以針對檔案做管理監控讀取等行為裡面有許多預設的方法底下是檔案輸出的基本範例底下會有兩個檔案

第一個是靜態 html檔案

ltDOCTYPE htmlgt

lthtml xmlns=rdquohttpwwww3org1999xhtmlrdquo xmllang=rdquozh-CNrdquo lang=rdquozh-CNrdquogt

ltheadgt

ltmeta http-equiv=rdquoContent-Typerdquo content=rdquotexthtml charset=UTF-7rdquo gt

lttitlegtnodejs index html filelttitlegt

ltheadgt

ltbodygt

34 4 Nodejs基礎

Nodejs中文電子書

lth1gtnodejs index html filelth1gt

ltbodygt

lthtmlgt

另一個為 nodejs程式

var fs = require(rdquofsrdquo)

filename = rdquostaticindexhtmlrdquo

encode = rdquoutf8rdquo

fsreadFile(filename encode function(err file)

consolelog(file)

)

一開始直接載入 le system模組 載入名稱為 fs讀取檔案主要使

用的方法為 readFile裡面以三個參數路徑 ( le path) 編碼方式 (encoding)

回應函式 (callback)路徑必須要設定為靜態 html所在位置才能指定到正確的檔案靜態檔案的編碼方式也必須正確這邊使用靜態檔案的編

碼為 utf8如果編碼設定錯誤nodejs讀取出來檔案結果會使用 byte raw格式輸出如果錯誤編碼格式會導致輸出資料為 byte raw

回應函式中裡面會使用兩個變數error為錯誤資訊如果讀取的檔案不存在或者發生錯誤error數值會是 true如果成功讀取資料 error將會是 falsecontent則是檔案內容資料讀取後將會把資料全數丟到 content這個變數當中

最後程式的輸出結果畫面如下

43 nodejs檔案讀取 35

Nodejs中文電子書

44 nodejs http靜態檔案輸出

前面已經了解如何讀取本地端檔案接下來將配合 http伺服器路由讓每個路由都能夠輸出相對應的靜態 html檔案首先新增加幾個靜態 html檔案

ltDOCTYPE htmlgt

lthtml xmlns=rdquohttpwwww3org1999xhtmlrdquo xmllang=rdquozh-CNrdquo lang=rdquozh-CNrdquogt

ltheadgt

ltmeta http-equiv=rdquoContent-Typerdquo content=rdquotexthtml charset=UTF-7rdquo gt

lttitlegtnodejs index html filelttitlegt

ltheadgt

ltbodygt

lth1gtnodejs index html filelth1gt

ltbodygt

lthtmlgt

ltDOCTYPE htmlgt

lthtml xmlns=rdquohttpwwww3org1999xhtmlrdquo xmllang=rdquozh-CNrdquo lang=rdquozh-CNrdquogt

ltheadgt

ltmeta http-equiv=rdquoContent-Typerdquo content=rdquotexthtml charset=UTF-7rdquo gt

lttitlegtnodejs test html filelttitlegt

ltheadgt

ltbodygt

lth1gtnodejs test html filelth1gt

ltbodygt

lthtmlgt

ltDOCTYPE htmlgt

lthtml xmlns=rdquohttpwwww3org1999xhtmlrdquo xmllang=rdquozh-CNrdquo lang=rdquozh-CNrdquogt

ltheadgt

ltmeta http-equiv=rdquoContent-Typerdquo content=rdquotexthtml charset=UTF-7rdquo gt

lttitlegtnodejs static html filelttitlegt

ltheadgt

ltbodygt

lth1gtnodejs static html filelth1gt

ltbodygt

lthtmlgt

36 4 Nodejs基礎

Nodejs中文電子書

準備一個包含基本路由功能的 http伺服器

var server

ip = rdquo127001rdquo

port = 1337

http = require(rsquohttprsquo)

url = require(rsquourlrsquo)

server = httpcreateServer(function (req res)

var path = urlparse(requrl)

)

serverlisten(port ip)

consolelog(rdquoServer running at httprdquo + ip + rdquordquo + port)

加入 le system 模組使用 readFile 的功能將這一段程式放置於

createServer的回應函式中

fsreadFile(filePath encode function(err file)

)

readFile的回應函式裡面加入頁面輸出讓瀏覽器可以正確讀到檔案在這邊我們設定讀取的檔案為 html 靜態檔案所以 Content-type 設定為texthtml讀取到檔案的內容將會正確輸出成 html靜態檔案

fsreadFile(filePath encode function(err file)

reswriteHead(200 rsquoContent-Typersquo rsquotexthtmlrsquo)

reswrite(file)

resend()

)

到這邊為止基本的程式內容都已經完成剩下一些細節的調整首先

路徑上必須做調整目前的靜態檔案全部都放置於 static資料夾底下設定一個變數來記住資料夾位置

接著將瀏覽器發出要求路徑與資料夾組合讀取正確 html靜態檔案使用者有可能會輸入錯誤路徑所以在讀取檔案的時候要加入錯誤處理

同時回應 404伺服器無法正確回應的 http header格式

加入這些細節的修改一個基本的 http 靜態 html輸出伺服器就完成了完整程式碼如下

44 nodejs http靜態檔案輸出 37

Nodejs中文電子書

var server

ip = rdquo127001rdquo

port = 1337

http = require(rsquohttprsquo)

fs = require(rdquofsrdquo)

folderPath = rdquostaticrdquo

url = require(rsquourlrsquo)

path

filePath

encode = rdquoutf8rdquo

server = httpcreateServer(function (req res)

path = urlparse(requrl)

filePath = folderPath + pathpathname

fsreadFile(filePath encode function(err file)

if (err)

reswriteHead(404 rsquoContent-Typersquo rsquotextplainrsquo)

resend()

return

reswriteHead(200 rsquoContent-Typersquo rsquotextapplicationrsquo)

reswrite(file)

resend()

)

)

serverlisten(port ip)

consolelog(rdquoServer running at httprdquo + ip + rdquordquo + port)

45 nodejs http GET資料擷取

http伺服器中除了路由之外另一個最常使用的方法就是擷取 GET資料本單元將會介紹如何透過基本 http伺服器擷取瀏覽器傳來的要求擷取 GET資料

在 http協定中GET參數都是藉由 URL從瀏覽器發出要求送至伺服器

38 4 Nodejs基礎

Nodejs中文電子書

端基本的傳送網址格式可能如下

http127001testsend=1amptest=2

上面這段網址裡面的 GET參數就是 send而這個變數的數值就為 1如果想要在 http伺服器取得 GET資料需要在瀏覽器給予的要求 (request)做處理

首先需要載入 query string這個模組這個模組主要是用來將字串資料

過濾後轉換成javascript物件

qs = require(rsquoquerystringrsquo)

接著在第一階段利用 url模組過濾瀏覽器發出的 URL資料後將回應的物件裡面的 query這個變數是一個字串值資料過濾後如下

send=1amptest=2

透過 query string使用 parse這個方法將資料轉換成 javascript物件就表示 GET的資料已經被伺服器端正式擷取下來

path = urlparse(requrl)

parameter = qsparse(pathquery)

整個 nodejs http GET參數完整擷取程式碼如下

var server

ip = rdquo127001rdquo

port = 1337

http = require(rsquohttprsquo)

qs = require(rsquoquerystringrsquo)

url = require(rsquourlrsquo)

server = httpcreateServer(function (req res)

var path = urlparse(requrl)

parameter = qsparse(pathquery)

consoledir(parameter)

reswriteHead(200 rsquoContent-Typersquo rsquotextplainrsquo)

reswrite(rsquoBrowser test GET parameternrsquo)

resend()

)

45 nodejs http GET資料擷取 39

Nodejs中文電子書

serverlisten(port ip)

consolelog(rdquoServer running at httprdquo + ip + rdquordquo + port)

程式運作之後由瀏覽器輸入要求網址之後nodejs伺服器端回應資料為

Server running at http1270011337

send rsquo1rsquo test rsquo1rsquo

46 本章結語

前面所解說的部份一大部分主要是處理 http伺服器基本問題雖然在某些部分有牽扯到 http 伺服器基本運作原理主要還是希望可以藉由這些基本範例練習 nodejs練習回應函式與語法串接的特點習慣編寫javascript風格程式當然直接這樣開發 nodejs是非常辛苦的接下來在模組實戰開發的部份將會介紹特定的模組一步一步帶領各位從無到有進行

nodejs應用程式開發

40 4 Nodejs基礎

5

NPM套件管理工具

npm全名為 Node Package Manager是 Nodejs的套件(package)管理工具類似 Perl的 ppm或 PHP的 PEAR等安裝 npm後使用npm install

module name指令即可安裝新套件維護管理套件的工作會更加輕鬆

npm可以讓Nodejs的開發者直接利用擴充線上的套件庫(packagesregistry)加速軟體專案的開發npm提供很友善的搜尋功能可以快速找到安裝需要的套件當這些套件發行新版本時npm也可以協助開發者自動更新這些套件

npm不僅可用於安裝新的套件它也支援搜尋列出已安裝模組及更新的功能

51 安裝 NPM

Nodejs在 063版本開始內建 npm讀者安裝的版本若是此版本或更新的版本就可以略過以下安裝說明

若要檢查 npm是否正確安裝可以使用以下的指令

npm -v

41

Nodejs中文電子書

執行結果說明

若 npm正確安裝執行 npm -v將會看到類似 110-2的版本訊息

若讀者安裝的 Nodejs版本比較舊或是有興趣嘗試自己動手安裝 npm工具則可以參考以下的說明

安裝於Windows系統

Nodejs for Windows 於 062 版開始內建 npm使用 nodejsorg 官方提供的安裝程式不需要進一步的設定就可以立即使用 npm指令對於Windows的開發者來說大幅降低環境設定的問題與門檻

除了使用 Nodejs內建的 npm讀者也可以從 npm官方提供的以下網址

httpnpmjsorgdist

這是由 npm提供的 Fancy Windows Install版本請下載壓縮檔(例如npm-110-3zip)並將壓縮檔內容解壓縮至 Nodejs的安裝路徑(例如CProgram Filesnodejs)

解壓縮後在 Nodejs的安裝路徑下應該有以下的檔案及資料夾

bull npmcmd(檔案)

bull node modules(資料夾)

安裝於 Linux系統

Ubuntu Linux的使用者可以加入 NPM Unoffcial PPA這個 repository即可使用 apt-get完成 npm安裝

42 5 NPM套件管理工具

Nodejs中文電子書

Ubuntu Linux使用 apt-get安裝 npm

sudo apt-get install python-software-properties

sudo add-apt-repository ppagias-kay-leenpm

sudo apt-get update

sudo apt-get install npm

npm官方提供的安裝程式 installsh可以適用於大多數的 Linux系統使用這個安裝程式請先確認

1 系統已安裝 curl工具(請使用 curl --version查看版本訊息)

2 已安裝 Nodejs並且 PATH正確設置

3 Nodejs的版本必須大於 04x

以下為 npm提供的安裝指令

curl httpnpmjsorginstallsh | sh

安裝成功會看到如下訊息

installsh安裝成功的訊息

npm10105 homeUSERNAMElocalnodelibnode_modulesnpm

It worked

安裝於Mac OS X

建議採用與 Nodejs相同的方式進行 npm的安裝例如使用MacPorts安裝 Nodejs就同樣使用MacPorts安裝 npm這樣對日後的維護才會更方便容易

使用 MacPorts安裝 npm是本書比較建議的方式它可以讓 npm的安裝移除及更新工作自動化將會幫助開發者節省寶貴時間

51 安裝 NPM 43

Nodejs中文電子書

安裝MacPorts的提示

在MacPorts網站可以取得 OS X系統版本對應的安裝程式(例如 106或 107)httpwwwmacportsorg安裝過程會詢問系統管理者密碼使用預設的選項完成安裝即可安

裝MacPorts之後在終端機執行 port -v將會看到MacPorts的版本訊息

安裝 npm之前先更新MacPorts的套件清單以確保安裝的 npm是最新版本

sudo port -d selfupdate

接著安裝 npm

sudo port install npm

若讀者的 Nodejs並非使用MacPorts安裝則不建議使用MacPorts安裝npm因為 MacPorts會自動檢查並安裝相依套件而 npm相依 nodejs所以MacPorts也會一併將 nodejs套件安裝造成先前讀者使用其它方式安裝的 nodejs被覆蓋

讀者可以先使用 MacPorts安裝 curl(sudo port install curl)

再參考 Linux的 installsh安裝方式即可使用 npm官方提供的安裝程式

NPM安裝後測試

npm是指令列工具(command-line tool)使用時請先打開系統的文字終端機工具

測試 npm安裝與設定是否正確請輸入指令如下

npm -v

或是

npm --version

如果 npm已經正確安裝設定就會顯示版本訊息

44 5 NPM套件管理工具

Nodejs中文電子書

執行結果(範例)

110-2

52 使用 NPM安裝套件

npm目前擁有超過 6000種套件(packages)可以在 npm registry使用關鍵字搜尋套件

httpsearchnpmjsorg

舉例來說在關鍵字欄位輸入「coffee-script」下方的清單就會自動列出包含 coffee-script關鍵字的套件

接著我們回到終端機模式的操作npm的指令工具本身就可以完成套

件搜尋的任務

例如以下的指令同樣可以找出 coffee-script相關套件

npm search coffee-script

以下是搜尋結果的參考畫面

52 使用 NPM安裝套件 45

Nodejs中文電子書

找到需要的套件後(例如 express)即可使用以下指令安裝

npm install coffee-script

值得注意的一點是使用 npm install會將指定的套件安裝在工

作目錄(Working Directory)的 node modules資料夾下

以Windows為例如果執行 npm install的目錄位於

Cproject1

那麼 npm將會自動建立一個 node modules的子目錄(如果不存在)

Cproject1node modules

並且將下載的套件放置於這個子目錄例如

Cproject1node modulescoffee-script

這個設計讓專案可以個別管理相依的套件並且可以在專案佈署或發

行時將這些套件(位於 node modules)一併打包方便其它專案的使用者不必再重新下載套件

這個 npm install的預設安裝模式為 local(本地)只會變更當前專案的資料夾不會影響系統

另一種安裝模式稱為 global(全域)這種模式會將套件安裝到系統資

料夾也就是 npm安裝路徑的 node modules資料夾例如

CProgram Filesnodejsnode modules

46 5 NPM套件管理工具

Nodejs中文電子書

是否要使用全域安裝可以依照套件是否提供新指令來判斷舉例來說express 套件提供 express 這個指令而 coffee-script 則提供 coffee

指令

在 local 安 裝 模 式 中這 些 指 令 的 程 式 檔 案會 被 安 裝 到node modules的 bin這個隱藏資料夾下除非將bin的路徑加入 PATH環境變數否則要執行這些指令將會相當不便

為了方便指令的執行我們可以在 npm install 加上 -g 或 --

global參數啟用 global安裝模式例如

npm install -g coffee-script

npm install -g express

使用 global安裝模式需要注意執行權限的問題若權限不足可能會出現類似以下的錯誤訊息

npm ERR Error EACCES permission denied rsquorsquo

npm ERR

npm ERR Please try running this command again as rootAdministrator

要獲得足夠得執行權限請參考以下說明

bull Windows 7 或 2008 以上在「命令提示字元」的捷徑按右鍵選擇「以系統管理員身分執行」執行 npm指令時就會具有 Administrator身分

bull Mac OS X或 Linux系統可以使用 sudo指令例如

sudo npm install -g express

bull Linux系統可以使用 root權限登入或是以「sudo su -」切換成 root身分(使用 root權限操作系統相當危險因此並不建議使用這種方式)

若加上 -g參數使用 npm install -g coffee-script完成安裝

後就可以在終端機執行 coffee指令例如

coffee -v

52 使用 NPM安裝套件 47

Nodejs中文電子書

執行結果(範例)

CoffeeScript version 120

53 套件的更新及維護

除了前一節說明的 search 及 install 用法npm 還提供其他許多指令(commands)

使用 npm help可以查詢可用的指令

npm help

執行結果(部分)

where ltcommandgt is one of

adduser apihelp author bin bugs c cache completion

config deprecate docs edit explore faq find get

help help-search home i info init install la link

list ll ln login ls outdated owner pack prefix

prune publish r rb rebuild remove restart rm root

run-script s se search set show star start stop

submodule tag test un uninstall unlink unpublish

unstar up update version view whoami

使用 npm help command可以查詢指令的詳細用法例如

npm help list

接下來本節要介紹開發過程常用的 npm指令

使用 list可以列出已安裝套件

npm list

48 5 NPM套件管理工具

Nodejs中文電子書

執行結果(範例)

-- coffee-script120

- express256

- connect185

| -- formidable108

-- mime124

-- mkdirp007

-- qs041

檢視某個套件的詳細資訊例如

npm show express

升級所有套件(如果該套件已發佈更新版本)

npm update

升級指定的套件

npm update express

移除指定的套件

npm uninstall express

54 使用 packagejson

對於正式的 Nodejs專案可以建立一個命名為 packagejson的設

定檔(純文字格式)檔案內容參考範例如下

54 使用 packagejson 49

Nodejs中文電子書

packagejson(範例)

rdquonamerdquo rdquoapplication-namerdquo

rdquoversionrdquo rdquo001rdquo

rdquoprivaterdquo true

rdquodependenciesrdquo

rdquoexpressrdquo rdquo255rdquo

rdquocoffee-scriptrdquo rdquolatestrdquo

rdquomongooserdquo rdquogt= 253rdquo

其中 name與 version依照專案的需求設置

需要注意的是 dependencies的設定它用於指定專案相依的套件名

稱及版本

bull rdquoexpressrdquo rdquo255rdquo

代表此專案相依版本 255的 express套件

bull rdquocoffee-scriptrdquo rdquolatestrdquo

使用最新版的 coffee-script套件(每次更新都會檢查新版)

bull rdquomongooserdquo rdquogt= 253rdquo

使用版本大於 253的 mongoose套件

假設某個套件的新版可能造成專案無法正常運作就必須指定套件的

版本避免專案的程式碼來不及更新以相容新版套件通常在開發初期的

專案需要盡可能維持新套件的相容性(以取得套件的更新或修正)可以

用「gt=」設定最低相容的版本或是使用「latest」設定永遠保持最新

套件

50 5 NPM套件管理工具

6

Express介紹

在前面的 nodejs基礎當中介紹許多許多開設 http的使用方法及介紹以及許多基本的 nodejs基本應用

接下來要介紹一個套件稱為 express [Express](httpexpressjscom)這個套件主要幫忙解決許多 nodejs http server所需要的基本服務讓開發 httpservice變得更為容易不需要像之前需要透過層層模組(module)才有辦法開始編寫自己的程式

這個套件是由 TJ Holowaychuk 製作而成的套件裡面包含基本的路由處理 (route)http資料處理(GETPOSTPUT)另外還與樣板套件(jshtml template engine)搭配同時也可以處理許多複雜化的問題

61 Express安裝

安裝方式十分簡單只要透過之前介紹的 NPM就可以使用簡單的指令安裝指令如下

這邊建議需要將此套件安裝成為全域模組方便日後使用

51

Nodejs中文電子書

62 Express基本操作

express的使用也十分簡單先來建立一個基本的 hello world

var app = require(rsquoexpressrsquo)createServer()

port = 1337

applisten(port)

appget(rsquorsquo function(req res)

ressend(rsquohello worldrsquo)

)

consolelog(rsquostart express servernrsquo)

可以從上面的程式碼發現基本操作與 nodejs http的建立方式沒有太大差異主要差在當我們設定路由時可以直接透過 appget方式設定回應與接受方式

63 Express路由處理

Express對於 http服務上有許多包裝讓開發者使用及設定上更為方便例如有幾個路由設定那我們就統一藉由 appget來處理

Create http server

appget(rsquorsquo function(req res)

ressend(rsquohello worldrsquo)

)

appget(rsquotestrsquo function(req res)

ressend(rsquotest renderrsquo)

)

appget(rsquouserrsquo function(req res)

ressend(rsquouser pagersquo)

)

52 6 Express介紹

Nodejs中文電子書

如上面的程式碼所表示appget可以帶入兩個參數第一個是路徑名稱設定第二個為回應函式 (call back function)回應函式裡面就如同之前的 createServer方法裡面包含 requestresponse兩個物件可供使用使用者就可以透過瀏覽器輸入不同的 url切換到不同的頁面顯示不同的結果

路由設定上也有基本的配對方式讓使用者從瀏覽器輸入的網址可以

是一個變數只要符合型態就可以有對應的頁面產出例如

Create http server

appget(rsquouseridrsquo function(req res)

ressend(rsquouser rsquo + reqparamsid)

)

appget(rsquonumberrsquo function(req res)

ressend(rsquonumber rsquo + reqparamsnumber)

)

裡 面 使 用 到number 從 網 址 輸 入 之 後 就 可 以 直 接 使 用reqparamsnumber 取得所輸入的資料變成 url 參數使用當然前面也是可以加上路徑的設定userid在瀏覽器上路徑必須符合userxxx透

過 reqparamsid就可以取到 xxx這個字串值

另外express參數處理也提供了路由參數配對處理也可以透過正規表示法作為參數設定

var app = require(rsquoexpressrsquo)createServer()

port = 1337

applisten(port)

appget(^ip((d23)((d23))((d23))((d23))) function(req res)

ressend(reqparams)

)

上面程式碼可以發現後面路由設定的型態是正規表示法裡面設定

格式為ip之後必須要加上 ip型態才會符合資料格式同時取得 ip資料已經由正規表示法將資料做分群因此可以取得 ip的四個數字

此程式執行之後可以透過瀏覽器測試輸入網址為 localhost3000ip25525510010可以從頁面獲得資料

63 Express路由處理 53

Nodejs中文電子書

此章節全部範例程式碼如下

overview

author Caesar Chi

blog clonnblogspotcom

version 20120226

create server

var app = require(rsquoexpressrsquo)createServer()

port = 1337

applisten(port)

normal style

appget(rsquorsquo function(req res)

ressend(rsquohello worldrsquo)

)

appget(rsquotestrsquo function(req res)

ressend(rsquotest renderrsquo)

)

parameter style

appget(rsquouseridrsquo function(req res)

ressend(rsquouser rsquo + reqparamsid)

)

appget(rsquonumberrsquo function(req res)

ressend(rsquonumber rsquo + reqparamsnumber)

)

REGX style

appget(^ip((d23)((d23))((d23))) function(req res)

ressend(reqparams)

)

appget(rsquorsquo function(req res)

ressend(rsquoPage not foundrsquo 404)

54 6 Express介紹

Nodejs中文電子書

)

consolelog(rsquostart express servernrsquo)

64 Express middleware

Express 裡面有一個十分好用的應用概念稱為 middleware可以透過middleware做出複雜的效果同時上面也有介紹 next方法參數傳遞就是靠 middleware的概念來傳遞參數讓開發者可以明確的控制程式邏輯

create http server

appuse(expressbodyParser())

appuse(expressmethodOverride())

appuse(expresssession())

上面都是一種 middleware的使用方式透過 appuse方式裡面載入函式執行方法回應函式會包含三個基本參數responserequestnext其中next表示下一個 middleware執行函式同時會自動將預設三個參數繼續帶往下個函式執行底下有個實驗

上面的片段程式執行後開啟瀏覽器連結上 localhost1337會發現伺服器回應結果順序如下

first middle ware

second middle ware

execute middle ware

end middleware function

從上面的結果可以得知剛才設定的 middleware都生效了在 appuse設定的 middleware是所有 url皆會執行方法如果有指定特定方法就可以使用 appget的 middleware設定在 appget函式的第二個參數就可以帶入函式或者是匿名函式只要函式裡面最後會接受 request response next這三個參數同時也有正確指定 next函式的執行時機最後都會執行到最後一個方法當然開發者也可以評估程式邏輯要執行到哪一個階段讓邏輯

可以更為分明

64 Express middleware 55

Nodejs中文電子書

65 Express路由應用

在實際開發上可能會遇到需要使用參數等方式混和變數一起使用

express裡面提供了一個很棒的處理方法 appall這個方式可以先採用基本路由配對再將設定為每個不同的處理方式開發者可以透過這個方式簡

化自己的程式邏輯

overview

author

version 20120226

create server

var app = require(rsquoexpressrsquo)createServer()

port = 1337

users = [

name rsquoClonnrsquo

name rsquoChirsquo

]

applisten(port)

appall(rsquouseridoprsquo function(req res next)

requser = users[reqparamsid]

if (requser)

next()

else

next(new Error(rsquocannot find user rsquo + reqparamsid))

)

appget(rsquouseridrsquo function(req res)

ressend(rsquoviewing rsquo + requsername)

)

appget(rsquouserideditrsquo function(req res)

ressend(rsquoediting rsquo + requsername)

)

56 6 Express介紹

Nodejs中文電子書

appget(rsquouseriddeletersquo function(req res)

ressend(rsquodeleting rsquo + requsername)

)

appget(rsquorsquo function(req res)

ressend(rsquoPage not foundrsquo 404)

)

consolelog(rsquostart express servernrsquo)

內部宣告一組預設的使用者分別給予名稱設定藉由 appall這個方法可以先將路由雛形建立再接下來設定 appget的路徑格式只要符合格式就會分配進入對應的方法中像上面的程式當中如果使用者輸入路徑為user0除了執行 appall程式之後執行 next方法就會對應到路徑設定為userid的這個方法當中如果使用者輸入路徑為user0edit就會執行到useridedit的對應方法

66 Express GET應用範例

我們準備一個使用 GET方法傳送資料的表單

ltDOCTYPE htmlgt

lthtmlgt

ltheadgt

ltmeta http-equiv=rdquoContent-Typerdquo content=rdquotexthtml charset=utf8rdquogt

lttitlegtNodejs 菜鳥筆記 (1)lttitlegt

ltlink rel=rdquostylesheetrdquo href=rdquocssstylecssrdquo type=rdquotextcssrdquo media=rdquoallrdquo gt

ltheadgt

ltbodygt

lth1gtNodejs 菜鳥筆記-註冊lth1gt

ltform id=rdquosignuprdquo method=rdquoGETrdquo action=rdquohttplocalhost3000Signuprdquogt

ltlabelgt 使用者名稱ltlabelgtltinput type=rdquotextrdquo id=rdquousernamerdquo name=rdquousernamerdquo gtltbrgt

ltlabelgt 電子郵件ltlabelgtltinput type=rdquotextrdquo id=rdquoemailrdquo name=rdquoemailrdquo gtltbrgt

ltinput type=rdquosubmitrdquo value=rdquo 註冊我的帳號rdquo gtltbrgt

ltformgt

ltbodygt

lthtmlgt

66 Express GET應用範例 57

Nodejs中文電子書

這個表單沒有什麼特別的地方我們只需要看第 9 行form 使用的method是 GET然後 action是rdquohttplocalhost3000Signupldquo等一下我們要來撰寫Signup這個 URL Path的處理程式

處理 Signup行為

我們知道所謂的 GET方法會透過 URL來把表單的值給帶過去以上面的表單來說到時候 URL會以這樣的形式傳遞

httplocalhost3000Signupusername=xxxampemail=xxx

所以要能處理這樣的資料必須有以下功能

bull 解析 URL

bull 辨別動作是 Signup

bull 解析出 username和 email

一旦能取得 username和 email的值程式就能加以應用了

處理 Signup的程式碼雛形

load module

var url = require(rsquourlrsquo)

urlData = urlparse(requrltrue)

action = urlDatapathname

reswriteHead(200 rdquoContent-Typerdquordquotexthtml charset=utf-8rdquo)

if (action === rdquoSignuprdquo)

user = urlDataquery

resend(rdquolth1gtrdquo + userusername + rdquo 歡迎您的加入 lth1gtltpgt 我們已經將會員啟用信寄至rdquo + useremail + rdquoltpgtrdquo)

首先需要加載 url module它是用來協助我們解析 URL的模組接著使用 urlparse方法第一個傳入 url字串變數也就是 requrl另外第二個參數的用意是設為 ture則引進 querystring模組來協助處理預設是 false它影響到的是 urlDataquery設為 true會傳回物件不然就只是一般的字串urlparse會將字串內容整理成一個物件我們把它指定給 urlData

action變數作為記錄 pathname這是我們稍後要來判斷目前網頁的動作是什麼接著先將 html表頭資訊 (Header)準備好再來判斷路徑邏輯如

58 6 Express介紹

Nodejs中文電子書

果是 Signup這個動作就把 urlDataquery裡的資料指定給 user然後輸出userusername和 useremail把使用者從表單註冊的資料顯示於頁面中

最後進行程式測試啟動 nodejs主程式之後開啟瀏覽器就會看到表單填寫完畢按下送出就可以看到結果了

完整 nodejs程式碼如下

var http = require(rsquohttprsquo)

url = require(rsquourlrsquo)

fs = require(rdquofsrdquo)

server

server = httpcreateServer(function (reqres)

var urlData

encode = rdquoutf8rdquo

filePath = rdquoviewexpress_get_example_formhtmlrdquo

action

urlData = urlparse(requrltrue)

action = urlDatapathname

reswriteHead(200 rdquoContent-Typerdquordquotexthtml charset=utf-8rdquo)

if (action === rdquoSignuprdquo)

user = urlDataquery

resend(rdquolth1gtrdquo + userusername + rdquo 歡迎您的加入 lth1gtltpgt 我們已經將會員啟用信寄至rdquo + useremail + rdquoltpgtrdquo)

else

fsreadFile(filePath encode function(err file)

reswrite(file)

resend()

)

)

serverlisten(3000)

consolelog(rsquoServer 跑起來了現在時間是rsquo + new Date())

66 Express GET應用範例 59

Nodejs中文電子書

67 Express POST應用範例

一開始準備基本的 html表單傳送內容以 POST方式form的 action屬性設定為 POST其餘 html內容與前一個範例應用相同

ltDOCTYPE htmlgt

lthtmlgt

ltheadgt

ltmeta http-equiv=rdquoContent-Typerdquo content=rdquotexthtml charset=utf8rdquogt

lttitlegtNodejs 菜鳥筆記 (1)lttitlegt

ltlink rel=rdquostylesheetrdquo href=rdquocssstylecssrdquo type=rdquotextcssrdquo media=rdquoallrdquo gt

ltheadgt

ltbodygt

lth1gtNodejs 菜鳥筆記-註冊lth1gt

ltform id=rdquosignuprdquo method=rdquoPOSTrdquo action=rdquohttplocalhost3000Signuprdquogt

ltlabelgt 使用者名稱ltlabelgtltinput type=rdquotextrdquo id=rdquousernamerdquo name=rdquousernamerdquo gtltbrgt

ltlabelgt 電子郵件ltlabelgtltinput type=rdquotextrdquo id=rdquoemailrdquo name=rdquoemailrdquo gtltbrgt

ltinput type=rdquosubmitrdquo value=rdquo 註冊我的帳號rdquo gtltbrgt

ltformgt

ltbodygt

lthtmlgt

nodejs的程式處理邏輯與前面 GET範例類似部分程式碼如下

qs = require(rsquoquerystringrsquo)

if (action === rdquoSignuprdquo)

formData = rsquorsquo

reqon(rdquodatardquo function (data)

formData += data

)

reqon(rdquoendrdquo function ()

user = qsparse(formData)

resend(rdquolth1gtrdquo + userusername + rdquo 歡迎您的加入 lth1gtltpgt 我們已經將會員啟用信寄至rdquo + useremail + rdquoltpgtrdquo)

)

60 6 Express介紹

Nodejs中文電子書

主要加入了rsquoquerystringrsquo這個moduel方便我們等一下解析由表單 POST回來的資料另外加入一個 formData的變數用來搜集待等一下表單回傳的資料前面的 GET範例我們只從 req拿出 url的資料這次要在利用req身上的事件處理

JavaScript 在訂閱事件時使用 addEventListener而 nodejs 使用的則是on這邊加上了監聽 data的事件會在瀏覽器傳送資料到Web Server時被執行參數是它所接收到的資料型態是字串

接著再增加 end的事件當瀏覽器的請求事件結束時它就會動作

由於瀏覽器使用 POST在上傳資料時會將資料一塊塊地上傳因為我們在監聽 data事件時透過 formData變數將它累加起來lt不過由於我們

上傳的資料很少一次就結束不過如果日後需要傳的是資料比較大的檔

案這個累加動作就很重要

當資料傳完就進到 end 事件中會用到 qsparse 來解析 formDataformData的內容是字串內容是

username=wordsmithampemail=wordsmith40somewhere

而 qsparse可以幫我們把這個 querystring轉成物件的格式也就是

username=wordsmithampemail=wordsmith40somewhere

一旦轉成物件並指定給 user之後其他的事情就和 GET方法時操作的一樣寫 response的表頭將內容回傳並將 userusername和 useremail代入到內容中

修改完成後接著執行 nodejs程式啟動 web server開啟瀏覽器進入表單測試看看POST的方式能否順利運作

完整程式碼如下

var http = require(rsquohttprsquo)

url = require(rsquourlrsquo)

fs = require(rdquofsrdquo)

qs = require(rsquoquerystringrsquo)

server

server = httpcreateServer(function (reqres)

var urlData

67 Express POST應用範例 61

Nodejs中文電子書

encode = rdquoutf8rdquo

filePath = rdquoviewexpress_post_example_formhtmlrdquo

formData

action

urlData = urlparse(requrltrue)

action = urlDatapathname

reswriteHead(200 rdquoContent-Typerdquordquotexthtml charset=utf-8rdquo)

if (action === rdquoSignuprdquo)

formData = rsquorsquo

reqon(rdquodatardquo function (data)

formData += data

)

reqon(rdquoendrdquo function ()

user = qsparse(formData)

resend(rdquolth1gtrdquo + userusername + rdquo 歡迎您的加入 lth1gtltpgt 我們已經將會員啟用信寄至rdquo + useremail + rdquoltpgtrdquo)

)

else

fsreadFile(filePath encode function(err file)

reswrite(file)

resend()

)

)

serverlisten(3000)

consolelog(rsquoServer 跑起來了現在時間是rsquo + new Date())

62 6 Express介紹

Nodejs中文電子書

68 Express AJAX應用範例

在 Nodejs要使用 Ajax傳送資料並且與之互動在接受資料的部份沒有太大的差別client端不是用 GET就是用 POST來傳資料重點在處理完後用 JSON格式回傳當然 Ajax不見得只傳 JSON格式有時是回傳一段 HTML碼不過後者對伺服器來說基本上就和前兩篇沒有差別了所以我們還是以回傳 JSON做為這一回的主題

這一回其實大多數的工作都會落在前端 Ajax上面前端要負責發送與接收資料並在接收資料後撤掉原先發送資料的表單並將取得的資料

改成 HTML格式之後放上頁面

首先先準備 HTML靜態頁面資料

ltDOCTYPE htmlgt

lthtmlgt

ltheadgt

lttitlegtNodejs 菜鳥筆記 (3)lttitlegt

ltlink rel=rdquostylesheetrdquo href=rdquocssstylecssrdquo type=rdquotextcssrdquo media=rdquoallrdquo gt

ltscript src=rdquohttpsajaxgoogleapiscomajaxlibsjquery171jqueryminjsrdquogtltscriptgt

ltheadgt

ltbodygt

lth1gtNodejs 菜鳥筆記-註冊 (用 Ajax)lth1gt

ltform id=rdquosignuprdquo action=rdquoSignuprdquo method=rdquoPOSTrdquogt

ltlabelgt 使用者名稱ltlabelgtltinput type=rdquotextrdquo id=rdquousernamerdquo name=rdquousernamerdquo gtltbrgt

ltlabelgt 電子郵件ltlabelgtltinput type=rdquotextrdquo id=rdquoemailrdquo name=rdquoemailrdquo gtltbrgt

ltinput type=rdquosubmitrdquo value=rdquo 註冊我的帳號rdquo gtltbrgt

ltformgt

ltdiv id=rdquoresultrdquogt

ltdivgt

ltscript type=rdquotextjavascriptrdquogt

$(function()$(rsquosignup input[type=rdquosubmitrdquo]rsquo)click(function(e)

var user =

userusername = $(rdquousernamerdquo)val()useremail = $(rdquoemailrdquo)val()

$post(rdquoSignuprdquouserfunction(data)greet(data)

)

68 Express AJAX應用範例 63

Nodejs中文電子書

epreventDefault()

)

var greet = function(msg)

var resultNode = $(rsquoresultrsquo)greeting = $(rsquolth2gtHirsquo+msgusername+rsquo 你的會員 id 是rsquo+ msgid +rsquo 我們會將會員啟動信寄至rsquo+msgemail+rsquolth2gtrsquo)

$(rsquosignuprsquo)hide()resultNodehtml(rdquordquo)

resultNodeappend(greeting)

)

ltscriptgt

ltbodygt

lthtmlgt

HTML頁面上準備了一個表單用來傳送註冊資料接著直接引用了Google CDN來載入 jQuery用來幫我們處理 Ajax的工作這次要傳送和接收的工作很大的變動都在 HTML頁面上的 JavaScript當中我們要做的事有 (相關 jQuery處理這邊不多做贅述指提起主要功能解說)

bull 用 jQUery取得 submit按鈕綁定它的 click動作

bull 取得表單 username和 email的值存放在 user這個物件中

bull 用 jQuery的 $post方法將 user的資料傳到 Server

bull 一旦成功取得資料後透過 greet這個 function組成回報給 user的訊息

bull 清空原本給使用者填資料的表單

bull 將 Server回傳的 usernameemail和 id這 3個資料組成回應的訊息

bull 將訊息放到原本表單的位置

經過以上的處理後一個 Ajax的表單的基本功能已經完成

接著進行 nodejs主要程式的編輯部分程式碼如下

var fs = require(rdquofsrdquo)

64 6 Express介紹

Nodejs中文電子書

qs = require(rsquoquerystringrsquo)

if (action === rdquoSignuprdquo)

formData = rsquorsquo

reqon(rdquodatardquo function (data)

formData += data

)

reqon(rdquoendrdquo function ()

var msg

user = qsparse(formData)

userid = rdquo123456rdquo

msg = JSONstringify(user)

reswriteHead(200 rdquoContent-Typerdquordquoapplicationjson charset=utf-8rdquordquoContent-Lengthrdquomsglength)

resend(msg)

)

這裡的程式和前面 POST範例基本上大同小異差別在

bull 幫 user的資料加上 id隨意存放一些文字進去讓 Server回傳的資料多於 Client端傳上來的不然會覺得 Server都沒做事

bull 增加了 msg 這個變數存放將 user 物件 JSON 文字化的結果JSONstringify這個轉換函式是 V8引擎所提供的如果你好奇的話

bull 大重點來了我們要告訴 Client端這次回傳的資料格式是 JSON所在 Content-type和 Content-Length要提供給 Client

Server很輕鬆就完成任務了最後進行程式測試啟動 nodejs主程式之後開啟瀏覽器就會看到表單填寫完畢按下送出就可以看到結果了

最後 nodejs本篇範例程式碼如下

var http = require(rsquohttprsquo)

url = require(rsquourlrsquo)

fs = require(rdquofsrdquo)

qs = require(rsquoquerystringrsquo)

server

68 Express AJAX應用範例 65

Nodejs中文電子書

server = httpcreateServer(function (reqres)

var urlData

encode = rdquoutf8rdquo

filePath = rdquoviewexpress_ajax_example_formhtmlrdquo

formData

action

urlData = urlparse(requrltrue)

action = urlDatapathname

if (action === rdquoSignuprdquo)

formData = rsquorsquo

reqon(rdquodatardquo function (data)

formData += data

)

reqon(rdquoendrdquo function ()

var msg

user = qsparse(formData)

userid = rdquo123456rdquo

msg = JSONstringify(user)

reswriteHead(200 rdquoContent-TyperdquordquoapplicationjsonrdquordquoContent-Lengthrdquomsglength)

resend(msg)

)

else

fsreadFile(filePath encode function(err file)

reswriteHead(200 rdquoContent-Typerdquordquotexthtml charset=utf-8rdquo)

reswrite(file)

resend()

)

)

serverlisten(3000)

66 6 Express介紹

Nodejs中文電子書

consolelog(rsquoServer 跑起來了現在時間是rsquo + new Date())

69 原始資料提供

bull [NodeJS初學者筆記 (1)-用 GET傳送資料] (httpithelpithomecomtwquestion10087402)

bull [NodeJS初學者筆記 (2)-用 POST傳送資料] (httpithelpithomecomtwquestion10087489)

bull [NodeJS 初學者筆記 (3)-用 Ajax 傳送資料] (httpithelpithomecomtwquestion10087627)

69 原始資料提供 67

Nodejs中文電子書

68 6 Express介紹

7

CoffeeScript

bull Spine

bull Hem

bull Spineapp

Letrsquos Have a Cup of CoffeeScript

bull es5-shim ECMAScript 5 compatibility shims for legacy JavaScript engines

ESnext

node-iform - A middleware for node-validator httpsgithubcomguileennode-iform

69

Nodejs中文電子書

70 7 CoffeeScript

8

製作一個 Hubot的 Plurk Adapter

81 應用事項提醒

此應用範例以CoffeeScript編寫主要程式架構採用Github釋放的Hubot專案以下有幾個主要注意事項請讀者注意

bull Hubot 一開始的架構除了內建的兩個 Adapter 之外其餘都要以Nodejs的Module方式才能運作

bull Hubot的 binhubot裡面寫著 npm install所以不管你怎麼改原始碼也不能改變第一點的狀況

其實上述都在討論同一件事情NodeJS的模組而且模組不能設定相對路徑之類的來安裝一定要包裝成 npm相符和格式才有辦法正確執行

82 建立 Adapter

首先需要準備兩個檔案

bull packagejson

bull plurkcoffee

71

Nodejs中文電子書

有這兩個就足以變成 NodeJS的模組了在開始 Coding之前先來設定一下 packagejson相依性

rdquonamerdquo rdquohubot-plurkrdquo

rdquoversionrdquo rdquo011rdquo

rdquomainrdquo rdquoplurkrdquo

rdquodependenciesrdquo

rdquohubotrdquo rdquogt=205rdquo

rdquooauthrdquo rdquordquo

rdquocronrdquordquordquo

這邊會使用到NodeJS的 cron模組(Module)而登入 Plurk需要OAuth標準授權才行所以也需要加載 OAuth模組

注意Hubot在讀取非內建模組時會自動在前面加上 hubot-的前置

83 建立 Robot跟 API

本篇應用基本上程式碼大部分參考 Hubot - Twitter Adapter來製作主要差異只有在使用 OAuth這個模組Twitter有 Streaming可用而 Plurk則得用 Comet方式來達到即時讀取

plurkcoffee程式碼

Robot = require(rdquohubotrdquo)robot()

Adapter = require(rdquohubtordquo)adapter()

EventEmitter = require(rdquoeventsrdquo)EventEmitter

oauth = require(rdquooauthrdquo)

cronJob = require(rdquocronrdquo)CronJob

class Plurk exntends Adapter

class PlurkStreaming exnteds EventEmitter

72 8 製作一個 Hubot的 Plurk Adapter

Nodejs中文電子書

先弄個基本架構主要 Class皆參考 Twitter Adapter命名方式接著再將 Plurk這個 Class增加幾個Method基本上只要有 run send reply就夠了而 run用來做初始化的部分

class Plurk entends Adapter

send (plurk id strings⋯) -gt

reply (plurk id strings⋯) -gt

run -gt

看起來有點東西接著來處理主要的 Plurk API結合部分

class PlurkStreaming extends EventEmitter

consuctor (options) -gt

plurk (callback) -gt

觀察河道

getChannel -gt

取得 Comet 網址

reply (plurk id message) -gt

回噗

acceptFriends -gt

接受好友

get (path callback) -gt

GET 請求

post (path body callback)-gt

POST 請求(其實是裝飾)

request (method path body callback)-gt

主要的 OAuth 請求

comet (server callback)-gt

噗浪的 Comet 傳回是 JavaScript Callback 要另外處理後才會變成 JSON

然後把注意力集中到 constructor上先把建構子弄好

constructor (options) -gt

super()

if optionskey and optionssecret and optionstoken and optionstoken secret

key = optionskey

secret = optionssecret

token = optionstoken

token secret = optionstoken secret

83 建立 Robot跟 API 73

Nodejs中文電子書

建立 OAuth 連接

consumer = new oauthOAuth(

rdquohttpwwwplurkcomOAuthrequest tokenrdquo

rdquohttpwwwplurkcomOAuthaccess tokenrdquo

key

secret

rdquo10rdquo

rdquohttpwwwplurkcomOAuthauthorizerdquo

rdquoHMAC-SHA1rdquo

)

domain = rdquowwwplurkcomrdquo

初始化取得 Comet 網址

do getChannel

else

throw new Error(rdquo 參數不足需要 Key Secret Token Token Secretrdquo)

接著來處理 request這個 method

request (method path body callback) -gt

記錄一下這次的 Request

consolelog(rdquohttpdomainpathrdquo)

Callback 這邊先不丟進去要用另一種方式處理

request = consumerget(rdquohttpdomainpathrdquo token token secret null)

requeston rdquoresponserdquo (res) -gt

reson rdquodatardquo (chunk) -gt

parseResponse(chunk+rsquorsquo callback)

reson rdquoendrdquo (data) -gt

consolelog rdquoEnd Request pathrdquo

reson rdquoerrorrdquo (data) -gt

consolelog rdquoError rdquo + data

requestend()

處理資料

parseResponse = (data callback) -gt

if datalength gt 0

用 TryCatch 避免處理 JSON 出錯導致整個中斷

try

callback null JSONparse(data)

catch err

74 8 製作一個 Hubot的 Plurk Adapter

Nodejs中文電子書

consolelog(rdquoError Parse JSONrdquo + data err)

繼續執行

callback null data ||

大致上就是這樣上面程式的架構已經將整個 Hubot Plurk Adapter完成因為在測試時竟然因為噗浪 Lag而沒讀到完整的 Comet資料然後造成程式異常為了避免這個問題發生因此需要加上為了完美呈現需要再

加上 Comet的處理所以要使用到 EventEmitter的功能

comet (server callback) -gt

在 Callback 裡面會找不到自身所以設定區域變數

self =

記錄一下這次的 Request

consolelog(rdquo[Comet] serverrdquo)

Callback 這邊先不丟進去要用另一種方式處理

request = consumerget(rdquohttpdomainpathrdquo token token secret null)

requeston rdquoresponserdquo (res) -gt

reson rdquodatardquo (chunk) -gt

parseResponse(chunk+rsquorsquo callback)

reson rdquoendrdquo (data) -gt

consolelog rdquoEnd Request pathrdquo

請求結束發出事件通知可以進行下一次請求

selfemit rdquonextPlurkrdquo

reson rdquoerrorrdquo (data) -gt

consolelog rdquoError rdquo + data

requestend()

處理資料

parseResponse = (data callback) -gt

if datalength gt 0

用 trycatch 避免失敗中斷

try

去掉 JavaScript 的 Callback

data = datamatch(CometChannelscriptCallback((+))s)

jsonData = rdquordquo

if data

83 建立 Robot跟 API 75

Nodejs中文電子書

jsonData = JSONparse(data[1])

else

如果沒有任何 Match 嘗試直接 parse

jsonData = JSONparse(data)

catch err

consolelog(rdquo[Comet] Errorrdquo data err)

用 TryCatch 避免處理 JSON 出錯導致整個中斷

try

只傳入 json 的 data 部分

callback null jsonDatadata

catch err

consolelog(rdquo[Comet]Error Parse JSONrdquo + data err)

繼續執行

callback null data ||

後面的 get跟 post就簡單多了

get (path callback) -gt

request(rdquoGETrdquo path null callback)

post (path body callback) -gt

request(rdquoPOSTrdquo path body callback)

接著處理取的 Comet網址的 getChannel

getChannel -gt

self =

get rdquoAPPRealtimegetUserChannelrdquo (error data) -gt

if error

檢查是否有 comet server

if datacomet server

selfchannel = datacomet server

如果沒有 Channel Ready 就嘗試連接會失敗

selfemit(rsquochannel readyrsquo)

那麼先來處理 Plurk Adaper好處理的部份

send (plruk id strings⋯)-gt

跟 Reply 一樣直接交給 reply 做

reply plurk id strings⋯

76 8 製作一個 Hubot的 Plurk Adapter

Nodejs中文電子書

reply (plurk id strings⋯) -gt

stringsforEach (message) =gt

botreply(plruk id message)

接著把 run處理好就可以上線運作摟

run -gt

self =

options =

key processenvHUBOT PLURK KEY

secret processenvHUBOT PLURK SECRET

token processenvHUBOT PLURK TOKEN

token secret processenvHUBOT PLURK TOKEN SECRET

創建剛剛的 API

bot = new PlurkStreaming(options)

依照 Twitter 的 new RobotTextMessage 會沒有反應所以參考 hubot-minecraft 的方式

r = robotconstructor

處理噗浪河道訊息

doPlurk = (data)-gt

檢查是否為回噗

if dataresponse

datacontent raw = dataresponsecontent raw

datauser id = dataresponseuser id

確定有噗浪 ID 跟訊息

if dataplurk id and datacontent raw

selfreceive new rTextMessage(dataplurk id datacontent raw)

取得 Comet Server 完成開始第一次 Comet 連接

boton rdquochannel readyrdquo () -gt

botplurk selfdoPlurk

上一次 Comet 完成繼續 Polling

boton rdquonextPlurkrdquo ()-gt

botplurk selfdoPlurk

定時接受好友邀請

do botacceptFriends

bot = bot

83 建立 Robot跟 API 77

Nodejs中文電子書

終於完成 AdapterHubot專案裡面的 scripts資料夾內是互動部分不需要像 Adapter如此大費周章處理只新增檔案並且設計好對白之後就會回噗了我開發用的機器人在此大家可以去跟他玩玩[httpplurkcomelct9620 bot](httpplurkcomelct9620 bot)

84 原始資料提供

bull [製 作 一 個 Hubot 的 噗 浪 Adapter](http revo-skillfrosttw blog20120318create-a-hubot-plurk-adapter)

78 8 製作一個 Hubot的 Plurk Adapter

9

Nodejs好用工具介紹

91 logio

Real-time log monitoring in your browser

httplogioorg

nodejs (gt=0412 lt=0611)

npm config set unsafe-perm true

npm install -g --prefix=usrlocal logio

logio server start

httplocalhost8998

79

Nodejs中文電子書

80 9 Nodejs好用工具介紹

10

精選文章

101 Express

bull 使用 NodeJS + Express從 GETPOST Request取值 [邀稿中]

bull link2 [邀稿中]

bull link3 [邀稿中]

81

Nodejs中文電子書

82 10 精選文章

11

參考資源

111 Nodejs書籍

bull Programming Nodejs April 2012 (est) Travis Swicegood Pragmatic Bookshelf

bull e Little Book on CoffeeScript January 2012 Alex MacCaw OrsquoReilly Media

bull Node for Front-End Developers January 2012 Garann Means OrsquoReilly Media

bull Building Hypermedia APIs with HTML5 and Node November 2011 MikeAmundsen OrsquoReilly Media

bull Node Web Development August 2011 David Herron PacktPub

bull CoffeeScript Accelerated JavaScript Development July 2011 Trevor BurnhamPragmatic Bookshelf

bull Getting Started with GEO CouchDB and Nodejs July 2011 Mick omp-son OrsquoReilly Media

bull What Is Node July 2011 Brett McLaughlin OrsquoReilly Media

bull Node Up and Running May 2011 Tom Hughes-Croucher Mike WilsonOrsquoReilly Media

83

Nodejs中文電子書

bull Hands-on Nodejs e Nodejs introduction and API reference May 2011 Pe-dro Teixeira LeanPub

bull e Node Beginner Book Manuel Kiessling

bull Mastering Nodejs visionmedia

112 Nodejs影音教學

bull e Node Sessions e Best of OSCON 2011 August 2011 OrsquoReilly Media(Video)

bull Tom Hughes-Croucher on Node March 2011 OrsquoReilly Media (Video)

113 Nodejs教學網站

bull Node Tuts

114 Nodejs課程

bull Introduction To Nodejs Van Nguyen CodeLesson

84 11 參考資源

Page 12: Nodejs Wiki

Nodejs中文電子書

6 前言

1

Nodejs簡介

Nodejs是一個高效能易擴充的網站應用程式開發框架 (Web Applica-tion Framework)它誕生的原因是為了讓開發者能夠更容易開發高延展性的網路服務不需要經過太多複雜的調校效能調整及程式修改就能

滿足網路服務在不同發展階段對效能的要求

Ryan Dahl是NodeJS的催生者目前任職於 Joyent (主機託管服務公司)他開發 NodeJS的目的就是希望能解決 Apache在連線數量過高時緩衝區 (buffer)和系統資源會很快被耗盡的問題希望能建立一個新的開發框架以解決這個問題因此嘗試使用效能十分優秀的 V8 JavaScript Engine讓網站開發人員熟悉的 JavaScript程式語言也能應用於後端服務程式的開發並且具有出色的執行效能

JavaScript是功能強大的物件導向程式語言但是在 JavaScript的官方規格中主要是定義網頁 (以瀏覽器為基礎) 應用程式需要的應用程式介面(API)對 JavaScript程式的應用範圍有所侷限為使 JavaScript能夠在更多用途發展CommonJS規範一組標準函式庫 (standard library)使 JavaScript的應用範圍能夠和 RubyPython及 Java等語言同樣豐富撰寫 NodeJS的JavaScript程式碼符合 CommonJS規範可以使用 CommonJS API為基礎開發程式並且在不同的 CommonJS兼容 (compliant) JavaScript執行環境中程式碼具有可攜性

7

Nodejs中文電子書

瀏覽器的 JavaScript與實現 CommonJS規範的 NodeJS有何不同呢瀏覽器的 JavaScript 提供 XMLHttpRequest 讓程式可以和網頁伺服器建立資料傳輸連線但這通常只能適用於網站開發的需求因為我們只能用

XMLHttpRequest與網頁伺服器通訊卻無法利用它建立其他類型如 Telnet FTP NTP的伺服器通訊如果我們想開發網路服務程式例如 SMTP電子郵件伺服器就必須使用 Sockets建立 TCP (某些服務則用 UDP)監聽及連線其他程式語言如 PHPJavaPythonPerl及 Ruby等在標準開發環境中皆有提供 Sockets API而瀏覽器的 JavaScript基於安全及貼近網站設計需求的考量下並未將 Sockets列入標準函式庫之中CommonJS的規範填補這種基礎函式庫功能的空缺遵循 CommonJS規範的 NodeJS可以直接使用 Sockets API建立各種網路服務程式

JavaScript語言本身支援 Lambda的特性因此一個匿名函式 (anonymousfunction)可以被儲存成一個變數並當作參數傳遞給另一個函式

var proc1 = function(op x) return op(x)

var op1 = function(x) return x+1 var op2 = function(x) return xx

proc1(op1 3) result is 4 proc1(op2 5) result is 25

另一個 JavaScript開發者必須掌握的語言特性稱為 Closure

NodeJS符合 CommonJS的規範使得 Callback方式易於實現也能夠讓更多同好基於 JavaScript開發符合 NodeJS的外掛模組 (Module)

回想以前要寫一個能夠同時容納上百人的上線的網路服務需要花費

多大的苦工可能 10人多就需要經過一次程式調整而 NodeJS就是為了解決這個困境NodeJS因此誕生它是一種利用 V8 Javascript編譯器所開發的產品利用 V8編譯器的高效能與 Javascript的程式開發特性所產生的網路程式

開發人員所編寫出來的 Javascript腳本程式怎麼可能會比其他語言寫出來的網路程式還要快上許多呢以前的網路程式原理是將使用者每次的

連線 (connection)都開啟一個執行緒 (thread)當連線爆增的時候將會快速耗盡系統效能並且容易產生阻塞 (block)的發生

8 1 Nodejs簡介

Nodejs中文電子書

NodeJS對於資源的調校有所不同當程式接收到一筆連線 (connection)會通知作業系統透過 epoll kqueue devpoll或 select將連線保留並且放入heap中配置先讓連線進入休眠 (Sleep)狀態當系統通知時才會觸發連線的 callback這種處理連線方式只會佔用掉記憶體並不會使用到 CPU資源另外因為採用 Javascript語言的特性每個 request都會有一個 callback如此可以避免發生 Block的狀況發生

基於 Callback特性目前NodeJS大多應用於 Comet(long pulling) RequestServer或者是高連線數量的網路服務上目前也有許多公司將 NodeJS設為內部核心網路服務之一在 NodeJS也提供了外掛管理 (Node packagemanagement)讓愛好 NodeJS輕易開發更多有趣的服務外掛並且提供到 npm讓全世界使用者快速安裝使用

本書最後執行測試版本為 nodejs v068相關 API文件可查詢 lsquohttpnodejsorg lthttpnodejsorggtlsquo本書所有範例均可於 linux windows上執行如遇到任何問題歡迎至 lsquohttpnodejstw lthttpnodejstwgtlsquo詢問對於nodejs相關問題

9

Nodejs中文電子書

10 1 Nodejs簡介

2

JavaScript與 NodeJS

其實使用 JavaScript 在網頁端與伺服器端的差距並不大但是為了使NodeJS可以發揮他最強大的能力有一些知識還是必要的所以還是針對這些主題介紹一下其中 Event LoopScope以及 Callback其實是比較需要了解的基本知識cpscurrying ow control是更進階的技巧與應用

21 Event Loop

可能很多人在寫 Javascript 時並不知道他是怎麼被執行的這個時候可以參考一下 jQuery作者 John Resig一篇好文章介紹事件及 timer怎麼在瀏覽器中執行How JavaScript Timers Work通常在網頁中所有的Javascript執行完畢後(這部份全部都在 global scope跑除非執行函數)接下來就是如 John Resig解釋的這樣所有的事件處理函數以及 timer執行的函數會排在一個 queue結構中利用一個無窮迴圈不斷從 queue中取出函數來執行這個就是 event loop

(除了 John Resig的那篇文章Nicholas C Zakas的 ldquoProfessional Javascriptfor Web Developer 2nd editionrdquo 有一個試閱本httpyuiblogcomassetspdfzakas-projs-2ed-ch18pdf598頁剛好也有簡短的說明)

所以在 Javascript中雖然有非同步但是他並不是使用執行緒所有

11

Nodejs中文電子書

的事件或是非同步執行的函數都是在同一個執行緒中利用 event loop的方式在執行至於一些比較慢的動作例如 IO網頁 render re ow等實際動作會在其他執行緒跑等到有結果時才利用事件來觸發處理函數來處理

這樣的模型有幾個好處沒有執行緒的額外成本所以反應速度很快不會

有任何程式同時用到同一個變數不必考慮 lock也不會產生 dead lock所以程式撰寫很簡單但是也有一些潛在問題任一個函數執行時間較長都

會讓其他函數更慢執行(因為一個跑完才會跑另一個)在多核心硬體普遍

的現在無法用單一的應用程式 instance發揮所有的硬體能力用 NodeJS撰寫伺服器程式碰到的也是一樣的狀況要讓系統發揮 event loop的效能就要盡量利用事件的方式來組織程式架構另外對於一些有可能較為耗

時的操作可以考慮使用 processnextTick函數來讓他以非同步的方式執行避免在同一個函數中執行太久擋住所有函數的執行

如果想要測試 event loop怎樣在「瀏覽器」中運行可以在函數中呼叫alert()這樣會讓所有 Javascript的執行停下來尤其會干擾所有使用 timer的函數執行有一個簡單的例子這是一個會依照設定的時間間隔嚴格執

行動作的動畫如果時間過了就會跳過要執行的動作點按圖片以後人

物會快速旋轉但是在旋轉執行完畢前按下「delay」按鈕讓 alert訊息等久一點接下來的動畫就完全不會出現了

22 Scope與 Closure

要快速理解 JavaScript的 Scope(變數作用範圍)原理只要記住他是Lexical Scope就差不多了簡單地說變數作用範圍是依照程式定義時(或者叫做程式文本)的上下文決定而不是執行時的上下文決定

為了維護程式執行時所依賴的變數即使執行時程式運行在原本的

scope之外他的變數作用範圍仍然維持不變這時程式依賴的自由變數(定義時不是 local的而是在上一層 scope定義的變數)一樣可以使用就好像被關閉起來所以叫做 Closure用程式看比較好懂

function outter(arg1)

arg1 及 free_variable1 對 inner 函數來說都是自由變數

var free_variable1 = 3

return function inner(arg2)

12 2 JavaScript與 NodeJS

Nodejs中文電子書

var local_variable1 =2arg2 及 local_variable1 對 inner 函數來說都是本地變數

return arg1 + arg2 + free_variable + local_variable1

var a = outter(1)變數 a就是 outter函數執行後返回的 inner函數 var b =a(4)執行 inner函數執行時上下文已經在 outter函數之外但是仍然能正常執行而且可以使用定義在 outter函數裡面的 arg1及 free variable1變數 consolelog(b)結果 10

在 Javascript中scope最主要的單位是函數(另外有 global及 eval)所以有可能製造出 closure的狀況通常在形式上都是有巢狀的函數定義而且內側的函數使用到定義在外側函數裡面的變數

Closure有可能會造成記憶體洩漏主要是因為被參考的變數無法被垃圾收集機制處理造成佔用的資源無法釋放所以使用上必須考慮清楚

不要造成意外的記憶體洩漏(在上面的例子中如果 a一直未執行使用到的記憶體就不會被釋放)

跟透過函數的參數把變數傳給函數比較起來Javascript Engine會比較難對 Closure進行最佳化如果有效能上的考量這一點也需要注意

23 Callback

要介紹 Callback之前要先提到 JavaScript的特色

JavaScript是一種函數式語言(functional language)所有 Javascript語言內的函數都是高階函數 (higher order function這是數學名詞計算機用語好像是 rst class function意指函數使用沒有任何限制與其他物件一樣)也就是說函數可以作為函數的參數傳給函數也可以當作函數的返回值這個特性讓 Javascript的函數使用上非常有彈性而且功能強大

callback在形式上其實就是把函數傳給函數然後在適當的時機呼叫傳入的函數Javascript使用的事件系統通常就是使用這種形式NodeJS中有一個物件叫做 EventEmitter這是 NodeJS事件處理的核心物件所有會使用事件處理的函數都會「繼承」這個物件(這裡說的繼承實

作上應該像是 mixin)他的使用很簡單可以使用物件on(事件名稱callback

23 Callback 13

Nodejs中文電子書

函數) 或是物件addListener(事件名稱callback 函數) 把你想要處理事件的函數傳入在物件中可以使用物件emit(事件名稱 參數) 呼叫傳入的callback函數這是 Observer Pattern的簡單實作而且跟在網頁中使用 DOM的 addEventListener使用上很類似也很容易上手不過 NodeJS是大量使用非同步方式執行的應用所以程式邏輯幾乎都是寫在 callback函數中當邏輯比較複雜時大量的 callback會讓程式看起來很複雜也比較難單元測試舉例來說

var p client = new Db(lsquointegration tests 20rsquo new Server(ldquo127001rdquo 27017) lsquopkrsquoCustomPKFactory) p clientopen(function(err p client)

p clientdropDatabase(function(err done)

p clientcreateCollection(lsquotest custom keyrsquo function(err collection)

collectioninsert(lsquoarsquo1 function(err docs)

collection nd(lsquo idrsquonew ObjectID(ldquoaaaaaaaaaaaardquo) function(err cursor)

cursortoArray(function(err items) testassertEquals(1 itemslength) p clientclose()

)

)

)

)

)

)

這是在網路上看到的一段操作 mongodb的程式碼為了循序操作所以必須在一個 callback裡面呼叫下一個動作要使用的函數這個函數裡面還是會使用 callback最後就形成一個非常深的巢狀

這樣的程式碼會比較難進行單元測試有一個簡單的解決方式是

盡量不要使用匿名函數來當作 callback或是 event handler透過這樣的方式

14 2 JavaScript與 NodeJS

Nodejs中文電子書

就可以對各個 handler做單元測試了例如

var http = require(lsquohttprsquo) var tools =

cookieParser function(request response) if(requestheaders[rsquoCookiersquo]) do parsing

var server = httpcreateServer(function(request response)

thisemit(lsquoinitrsquo request response)

) serveron(lsquoinitrsquo toolscookieParser) serverlisten(8080 lsquo127001rsquo)

更進一步可以把 tools改成外部 module例如叫做 toolsjs

moduleexports = cookieParser function(request response) if(requestheaders[rsquoCookiersquo]) do parsing

然後把程式改成

var http = require(lsquohttprsquo)

var server = httpcreateServer(function(request response) thisemit(lsquoinitrsquo re-quest response)

) serveron(lsquoinitrsquo require(lsquo toolsrsquo)cookieParser) serverlisten(8080lsquo127001rsquo)

這樣就可以單元測試 cookieParser了例如使用 nodeunit時可以這樣寫

var testCase = require(lsquonodeunitrsquo)testCase moduleexports = testCase(

ldquosetUprdquo function(cb) thisrequest = headers Cookielsquoname1val1 name2val2rsquo thisresponse = thisresult= name1rsquoval1rsquoname2rsquoval2rsquo

cb()

ldquotearDownrdquo function(cb)

cb()

23 Callback 15

Nodejs中文電子書

ldquonormal caserdquo function(test)

testexpect(1) var obj = require(lsquotoolsrsquo)cookieParser(thisrequest thisresponse)testdeepEqual(obj thisresult) testdone()

)

善於利用模組可以讓程式更好維護與測試

24 CPS(Continuation-Passing Style)

cps 是 callback 使用上的特例形式上就是在函數最後呼叫 callback這樣就好像把函數執行後把結果交給 callback 繼續運行所以稱作continuation-passing style利用 cps可以在非同步執行的情況下透過傳給 callback的這個 cps callback來獲知 callback執行完畢或是取得執行結果例如

lthtmlgt ltbodygt ltdiv id=rdquopanelrdquo style=rdquovisibilityhiddenrdquogtlt divgtlt bodygt lt htmlgt ltscriptgt var request = new XMLHttpRequest() re-questopen(lsquoGETrsquo lsquotest749txt timestamp=rsquo+new Date()getTime() true) re-questaddEventListener(lsquoreadystatechangersquo function(next)

return function() if(thisreadyState===4ampampthisstatus===200) next(thisresponseText)lt== 傳入的 cps callback 在動作完成時執行並取得結果進一步處理

(function(str)lt==這個匿名函數就是 cps callback

documentgetElementById(lsquopanelrsquo)innerHTML=str docu-mentgetElementById(lsquopanelrsquo)stylevisibility = lsquovisiblersquo

) false) requestsend() ltscriptgt

進一步的應用也可以參考 2-6流程控制

16 2 JavaScript與 NodeJS

Nodejs中文電子書

25 函數返回函數與 Currying

前面的 cps範例裡面使用了函數返回函數這是為了把 cps callback傳遞給 onreadystatechange事件處理函數的方法(因為這個事件處理函數並沒有設計好會傳送接收這樣的參數)實際會執行的事件處理函數其實是內層返回的那個函數之外包覆的這個函數主要是為了利用 Closure把 next傳給內層的事件處理函數這個方法更常使用的地方是為了解決一些

scope問題例如

ltscriptgt var accu=0count=10 for(var i=0 iltcount i++)

setTimeout(

function() countndash accu+=i if(countlt=0)

consolelog(accu)

50)

ltscriptgt

最後得出的結果會是 100而不是想像中的 45這是因為等到 setTime-out指定的函數執行時變數 i已經變成 10而離開迴圈了要解決這個問題就需要透過 Closure來保存變數 i

ltscriptgt var accu=0count=10 for(var i=0 iltcount i++)

setTimeout(

function(i) return function() countndash

accu+=i if(countlt=0)

consolelog(accu)

(i)

50)

25 函數返回函數與 Currying 17

Nodejs中文電子書

淺藍色底色的部份是跟上面例子不一樣的地方 ltscriptgt

函數返回函數的另外一個用途是可以暫緩函數執行例如

function add(m n) return m+n

var a = add(20 10) consolelog(a)

add這個函數必須同時輸入兩個參數才有辦法執行如果我希望這個函數可以先給它一個參數等一些處理過後再給一個參數然後得到結

果就必須用函數返回函數的方式做修改

function add(m)

return function(n) return m+n

var wait another arg = add(20)先給一個參數 var a = function(arr)

var ret=0 for(var i=0iltarrlengthi++) ret+=arr[i] return ret

([1234])計算一下另一個參數 var b = wait another arg(a)然後再繼續執行 consolelog(b)

像這樣利用函數返回函數使得原本接受多個參數的函數可以一次

接受一個參數直到參數接收完成才執行得到結果的方式有一個學名就

叫做Currying

綜合以上許多奇技淫巧就可以透過用函數來處理函數的方式調整

程式流程接下來看看

26 流程控制

(以 sync方式使用 async函數避開巢狀 callback循序呼叫 async callback等奇技淫巧)

建議參考

bull httphowtonodeorgcontrol- ow

bull httphowtonodeorgcontrol- ow-part-ii

18 2 JavaScript與 NodeJS

Nodejs中文電子書

bull httphowtonodeorgcontrol- ow-part-iii

bull httpblogmixunet20110202essential-node-js-patterns-and-snippets

這幾篇都是非常經典的 NodeJSJavascript流程控制好文章(阿mixu是在介紹一些 pattern時提到這方面的主題)不過我還是用幾個簡單的程式介紹一下做法跟概念

並發與等待

下面的程式參考了 mixu文章中的做法

var wait = function(callbacks done) consolelog(lsquowait startrsquo) var counter = call-backslength var results = [] var next = function(result) 接收函數執行結果並判斷是否結束執行 resultspush(result) if(ndashcounter == 0) done(results)如果結束執行就把所有執行結果傳給指定的 callback處理 for(var i = 0 i lt callbackslength i++) 依次呼叫所有要執行的函數 callbacks[i](next) consolelog(lsquowait endrsquo)

wait( [ function(next) setTimeout(function() consolelog(lsquodone arsquo) var result =500 next(result) 500) function(next) setTimeout(function() con-solelog(lsquodone brsquo) var result = 1000 next(result) 1000) function(next)setTimeout(function() consolelog(lsquodone crsquo) var result = 1500 next(1500) 1500) ] function(results) var ret = 0 i=0 for( iltresultslength i++) ret+= results[i] consolelog(lsquodone all result lsquo+ret)

)

執行結果wait start wait end done a done b done c done all result 3000

可以看出來其實 wait並不是真的等到所有函數執行完才結束執行而是在所有傳給他的函數執行完畢後(不論同步非同步)才執行處理結

果的函數(也就是 done())

不過這樣的寫法還不夠實用因為沒辦法實際讓函數可以等待執行

完畢又能當作事件處理函數來實際使用上面參考到的 Tim Caswell的文

26 流程控制 19

Nodejs中文電子書

章裡面有一種解法不過還需要額外包裝(在他的例子中)NodeJS核心的 fs物件把一些函數(例如 readFile)用 Currying處理類似像這樣

var fs = require(lsquofsrsquo) var readFile = function(path)

return function(callback errback)

fsreadFile(path function(err data)

if(err) errback()

else callback(data)

)

其他部份可以參考 Tim Caswell的文章他的 Doparallel跟上面的 wait差不多意思這裡只提示一下他沒說到的地方

另外一種做法是去修飾一下 callback當他作為事件處理函數執行後再用 cps的方式取得結果

ltscriptgt function Wait(fns done)

var count = 0 var results = [] thisgetCallback = function(index)

count++ return (function(waitback)

return function() var i=0args=[]for(iltargumentslengthi++)

argspush(arguments[i])

argspush(waitback) fns[index]apply(thisargs)

)(function(result) resultspush(result) if(ndashcount == 0)

20 2 JavaScript與 NodeJS

Nodejs中文電子書

done(results)

)

var a = new Wait(

[ function(waitback) consolelog(lsquodone arsquo) var result = 500 wait-back(result) function(waitback) consolelog(lsquodone brsquo) var result =1000 waitback(result) function(waitback) consolelog(lsquodone crsquo) varresult = 1500 waitback(result) ] function(results) var ret = 0 i=0for( iltresultslength i++) ret += results[i] consolelog(lsquodone allresult lsquo+ret)

) var callbacks = [agetCallback(0)agetCallback(1)agetCallback(0)agetCallback(2)]一次取出要使用的 callbacks避免結果提早送出 setTimeout(callbacks[0]500) setTimeout(callbacks[1] 1000) setTimeout(callbacks[2] 1500) setTime-out(callbacks[3] 2000) 當所有取出的 callbacks執行完畢就呼叫 done()來處理結果 ltscriptgt

執行結果

done a done b done a done c done all result 3500

上面只是一些小實驗更成熟的作品是 Tim Caswell 的 stephttpsgithubcomcreationixstep

如果希望真正使用同步的方式寫非同步則需要使用 Promisejs這一類的 library來轉換非同步函數不過他結構比較複雜 XD(見仁見智不過有些人認為 Promise有點過頭了)httpblogsmsdncombrbucktonarchive20110815promise-js-2-0-promise-framework-for-javascriptaspx

如果想不透過其他 Library做轉換又能直接用同步方式執行非同步函數大概就要使用一些需要額外 compile原始程式碼的方法了例如 BrunoJouhier的 streamlinejshttpsgithubcomSagestreamlinejs

26 流程控制 21

Nodejs中文電子書

循序執行

循序執行可以協助把非常深的巢狀 callback結構攤平例如用這樣的簡單模組來做(serialjs)

moduleexports = function(funs) var c = 0 if(isArrayOfFunctions(funs))

throw(lsquoArgument type was not matched Should be array of func-tionsrsquo)

return function()

var args = Arrayprototypeslicecall(arguments 0) if((cgt=funslength))

c++ return funs[c-1]apply(this args)

function isArrayOfFunctions(f ) if(typeof f == lsquoobjectrsquo) return false if(flength)return false if(fconcat) return false if(fsplice) return false var i = 0 for(iltflength i++)

if(typeof f[i] == lsquofunctionrsquo) return false

return true

簡單的測試範例(testSerialjs)使用 fs 模組確定某個 path 是檔案然後讀取印出檔案內容這樣會用到兩層的 callback所以測試中有使用serial的版本與 nested callbacks的版本做對照

var serial = require(lsquoserialrsquo) fs = require(lsquofsrsquo) path = lsquodclientjsrsquo cb = serial([ func-tion(err data)

if(err)

if(dataisFile) fsreadFile(path cb)

22 2 JavaScript與 NodeJS

Nodejs中文電子書

else consolelog(err)

function(err data)

if(err) consolelog(lsquo[ attened by searial]rsquo) con-solelog(datatoString(lsquoutf8rsquo))

else consolelog(err)

]) fsstat(path cb)

fsstat(path function(err data) 第一層 callback if(err)

if(dataisFile)

fsreadFile(path function(err data) 第二層 callback if(err)

consolelog(lsquo[nested callbacks]rsquo) con-solelog(datatoString(lsquoutf8rsquo))

else consolelog(err)

)

else consolelog(err)

)

關鍵在於這些 callback的執行是有順序性的所以利用 serial返回的一個函數 cb來取代這些 callback然後在 cb中控制每次會循序呼叫的函數

26 流程控制 23

Nodejs中文電子書

就可以把巢狀的 callback攤平成循序的 function陣列(就是傳給 serial函數的參數)

測試中的dclientjs 是一個簡單的 dnode 測試程式放在跟 testSerialjs同一個目錄

var dnode = require(lsquodnodersquo)

dnodeconnect(8000 lsquolocalhostrsquo function(remote)

remoterestart(function(str) consolelog(str) processexit()

)

)

執行測試程式後出現結果

[ attened by searial] var dnode = require(lsquodnodersquo)

dnodeconnect(8000 lsquolocalhostrsquo function(remote)

remoterestart(function(str) consolelog(str) processexit()

)

)

[nested callbacks] var dnode = require(lsquodnodersquo)

dnodeconnect(8000 lsquolocalhostrsquo function(remote)

remoterestart(function(str) consolelog(str) processexit()

)

)

對照起來看兩種寫法的結果其實是一樣的但是利用 serialjs巢狀的 callback結構就會消失

不過這樣也只限於順序單純的狀況如果函數執行的順序比較複雜

(不只是一直線)還是需要用功能更完整的流程控制模組比較好例如

httpsgithubcomcaolanasync

24 2 JavaScript與 NodeJS

3

Nodejs安裝與設定

本篇將講解如何在各個不同 OS建立 NodeJS環境目前 NodeJS 048版本環境架設方式需依賴 Linux指令才可編譯完成當然在不同作業系統中也已經有 NodeJS package可以直接使用指令快速架設以下各不同作業系統解說如何安裝 NodeJS

31 Ubuntu Linux

更新推薦使用 nvm

git clone gitgithubcomcreationixnvmgit ~nvm

echo rdquo ~nvmnvmshrdquo gtgt ~bashrc

nvm install v0614

nvm alias default v0614

以 上 可 參 考 http dreamerslabcom blog tw how-to-setup-a-node-js-development-environment-on-ubuntu-11-04

使用 APT套件管理工具是常見的方法以下是使用社群提供的 PPA安裝方式

sudo apt-get install python-software-properties

sudo add-apt-repository ppachris-leanodejs-devel

25

Nodejs中文電子書

sudo apt-get update

sudo apt-get install nodejs

32 Other Linux

Linux很適合作為 NodeJS的伺服器作業系統及開發環境安裝前請先確認以下套件已正確安裝

bull curl (wget)用來下載檔案的工具

bull git先進的版本控制工具

bull g++ GNU C++軟體編譯工具

bull make GNU軟體專案建置工具

安裝指令如下如設有權限問題請在指令前面加上 sudo

git clone httpsgithubcomjoyentnodegit

cd node

git checkout v067

configure

make

sudo make install

接著測試 nodeJS是否正常執行

node --version

出現版本訊息即表示安裝成功

33 Windows

nodeJS 在 v060 版本之後開始正式支援 windows native直接使用nodeexe 就可以執行程式支援性完全與 linux 相同更棒的部份就是不需經過編譯經過下載之後簡單設定完成立即開發 node程式

26 3 Nodejs安裝與設定

Nodejs中文電子書

下載 nodejs安裝檔案 lthttpnodejsorgdownloadgt

如此完成 windows native nodeexe安裝接著可以進入 command line執行測試在 command line輸入rdquonoderdquo會出現 nodejs版本訊息畫面表示安裝完成

33 Windows 27

Nodejs中文電子書

28 3 Nodejs安裝與設定

4

Nodejs基礎

前篇文章已經由介紹安裝至設定都有完整介紹nodeJS 內部除了javascript常用的函式 (function)物件 (object)之外也有許多不同的自訂物件nodeJS預設建立這些物件為核心物件是為了要讓開發流程更為這些資料在官方文件已經具有許多具體說明接下來將會介紹在開發 nodeJS程式時常見的物件特性與使用方法

41 nodejs http伺服器建立

在 lsquonodejs官方網站 lthttpnodejsorggtlsquo裡面有舉一個最簡單的 HTTP伺服器建立一開始初步就是建立一個伺服器平台讓 nodejs可以與瀏覽器互相行為每種語言一開始的程式建立都是以 Hello world開始最初也從 Hello world帶各位進入 nodejs的世界

輸入以下程式碼儲存檔案為 node basic http hello worldjs

var server

ip = rdquo127001rdquo

port = 1337

http = require(rsquohttprsquo)

server = httpcreateServer(function (req res)

29

Nodejs中文電子書

reswriteHead(200 rsquoContent-Typersquo rsquotextplainrsquo)

resend(rsquoHello Worldnrsquo)

)

serverlisten(port ip)

consolelog(rdquoServer running at httprdquo + ip + rdquordquo + port)

程式碼解講一開始需要有幾個基本的變數

bull ip 機器本身的 ip位置因為使用本地端因此設定為 127001

bull port 需要開通的阜號通常設定為 http port 80因範例不希望與基本 port相衝隨意設定為 1337

在 nodejs 的程式中有許多預設的模組可以使用因此需要使用require方法將模組引入在這邊我們需要使用 http這個模組因此將 http載入Http模組裡面內建有許多方法可以使用這邊採用 createServer創建一個基本的 http伺服器再將 http伺服器給予一個 server變數裡面的回呼函式 (call back function)可以載入 http伺服器的資料與回應方法 (requestresponse)在程式裡面就可以看到我們直接回應給瀏覽器端所需的 Header回應內容

reswriteHead(200 rsquoContent-Typersquo rsquotextplainrsquo)

resend(rsquoHello Worldnrsquo)

Http伺服器需要設定 port ip在最後需要設定 Http監聽需要使用到listen事件監聽所有 Http伺服器行為

httplisten(port ip)

所有事情都完成之後需要確認伺服器正確執行因此使用 console在javascript裡就有這個原生物件console所印出的資料都會顯示於 nodejs伺服器頁面這邊印出的資料並不會傳送到使用者頁面上之後許多除壞

(debug)都會用到 console物件

consolelog(rdquoServer running at httprdquo + ip + rdquordquo + port)

30 4 Nodejs基礎

Nodejs中文電子書

42 nodejs http路徑建立

前面已經介紹如何建立一個簡單的 http伺服器接下來本章節將會介紹如何處理伺服器路徑 (rout)問題在 http的協定下所有從瀏覽器發出的要求 (request)都需要經過處理路徑上的建立也是如此

路徑就是指伺服器 ip位置或者是網域名稱之後對於伺服器給予的要求修改剛才的 hello world檔案修改如下

server = httpcreateServer(function (req res)

consolelog(requrl)

reswriteHead(200 rsquoContent-Typersquo rsquotextplainrsquo)

resend(rsquohello worldnrsquo)

)

重新啟動 nodejs程式後在瀏覽器端測試一下路徑行為結果如下圖

當在瀏覽器輸入 http1270011337test 在伺服器端會收到兩個要求一個是我們輸入的test要求另外一個則是faviconicotest的路徑要求http伺服器本身需要經過程式設定才有辦法回應給瀏覽器端所需要的回應在伺服器中所有的路徑要求都是需要被解析才有辦法取得資料從

42 nodejs http路徑建立 31

Nodejs中文電子書

上面解說可以了解到在 nodejs當中所有的路徑都需要經過設定未經過設定的路由會讓瀏覽器無法取得任何資料導致錯誤頁面的發生底下將會解

說如何設定路由同時避免發生錯誤情形先前 nodejs程式需要增加一些修改才能讓使用者透過瀏覽器在不同路徑時有不同的結果根據剛才

的程式做如下的修改

var server

ip = rdquo127001rdquo

port = 1337

http = require(rsquohttprsquo)

url = require(rsquourlrsquo)

path

server = httpcreateServer(function (req res)

path = urlparse(requrl)

reswriteHead(200 rsquoContent-Typersquo rsquotextplainrsquo)

switch (pathpathname)

case rdquoindexrdquo

resend(rsquoI am indexnrsquo)

break

case rdquotestrdquo

resend(rsquothis is test pagenrsquo)

break

default

resend(rsquodefault pagenrsquo)

break

)

serverlisten(port ip)

consolelog(rdquoServer running at httprdquo + ip + rdquordquo + port)

程式做了片段的修改首先載入 url模組另外增加一個 path變數url模組就跟如同他的命名一般專門處理 url字串處理裡面提供了許多方法來解決路徑上的問題因為從瀏覽器發出的要求路徑可能會帶有多種需求

或者 GET參數組合等因此我們需要將路徑單純化取用路徑部分的資料即可例如使用者可能會送出 http1270011337testsend=1如果直接

32 4 Nodejs基礎

Nodejs中文電子書

信任 requrl就會收到結果為testsend=1所以需要透過 url模組的方法將路徑資料過濾

在這邊使用 urlparse的方法裡面帶入網址格式資料會回傳路徑資料為了後需方便使用將回傳的資料設定到 path變數當中在回傳的路徑資料裡面包含資訊如下圖

這邊只需要使用單純的路徑要求直接取用 pathpathname就可以達到我們的目的

最後要做路徑的判別在不同的路徑可以指定不同的輸出在範例中

有三個可能結果第一個從瀏覽器輸入index就會顯示 index結果test 就會呈現出 test頁面最後如果都不符合預期的輸入會直接顯示 default的頁面最後的預防可以讓瀏覽器不會出現非預期結果讓程式的可靠性提昇

底下為測試結果

42 nodejs http路徑建立 33

Nodejs中文電子書

43 nodejs檔案讀取

前面已經介紹如何使用路由(rount)做出不同的回應實際應用只有在瀏覽器只有輸出幾個文字資料總是不夠的在本章節中將介紹如何使

用檔案讀取輸出檔案資料讓使用者在前端瀏覽器也可以讀取到完整的

html css javascript檔案輸出

檔案管理最重要的部分就是 lsquoFile system lthttpnodejsorgdocslatestapifshtmlgtlsquo這個模組此模組可以針對檔案做管理監控讀取等行為裡面有許多預設的方法底下是檔案輸出的基本範例底下會有兩個檔案

第一個是靜態 html檔案

ltDOCTYPE htmlgt

lthtml xmlns=rdquohttpwwww3org1999xhtmlrdquo xmllang=rdquozh-CNrdquo lang=rdquozh-CNrdquogt

ltheadgt

ltmeta http-equiv=rdquoContent-Typerdquo content=rdquotexthtml charset=UTF-7rdquo gt

lttitlegtnodejs index html filelttitlegt

ltheadgt

ltbodygt

34 4 Nodejs基礎

Nodejs中文電子書

lth1gtnodejs index html filelth1gt

ltbodygt

lthtmlgt

另一個為 nodejs程式

var fs = require(rdquofsrdquo)

filename = rdquostaticindexhtmlrdquo

encode = rdquoutf8rdquo

fsreadFile(filename encode function(err file)

consolelog(file)

)

一開始直接載入 le system模組 載入名稱為 fs讀取檔案主要使

用的方法為 readFile裡面以三個參數路徑 ( le path) 編碼方式 (encoding)

回應函式 (callback)路徑必須要設定為靜態 html所在位置才能指定到正確的檔案靜態檔案的編碼方式也必須正確這邊使用靜態檔案的編

碼為 utf8如果編碼設定錯誤nodejs讀取出來檔案結果會使用 byte raw格式輸出如果錯誤編碼格式會導致輸出資料為 byte raw

回應函式中裡面會使用兩個變數error為錯誤資訊如果讀取的檔案不存在或者發生錯誤error數值會是 true如果成功讀取資料 error將會是 falsecontent則是檔案內容資料讀取後將會把資料全數丟到 content這個變數當中

最後程式的輸出結果畫面如下

43 nodejs檔案讀取 35

Nodejs中文電子書

44 nodejs http靜態檔案輸出

前面已經了解如何讀取本地端檔案接下來將配合 http伺服器路由讓每個路由都能夠輸出相對應的靜態 html檔案首先新增加幾個靜態 html檔案

ltDOCTYPE htmlgt

lthtml xmlns=rdquohttpwwww3org1999xhtmlrdquo xmllang=rdquozh-CNrdquo lang=rdquozh-CNrdquogt

ltheadgt

ltmeta http-equiv=rdquoContent-Typerdquo content=rdquotexthtml charset=UTF-7rdquo gt

lttitlegtnodejs index html filelttitlegt

ltheadgt

ltbodygt

lth1gtnodejs index html filelth1gt

ltbodygt

lthtmlgt

ltDOCTYPE htmlgt

lthtml xmlns=rdquohttpwwww3org1999xhtmlrdquo xmllang=rdquozh-CNrdquo lang=rdquozh-CNrdquogt

ltheadgt

ltmeta http-equiv=rdquoContent-Typerdquo content=rdquotexthtml charset=UTF-7rdquo gt

lttitlegtnodejs test html filelttitlegt

ltheadgt

ltbodygt

lth1gtnodejs test html filelth1gt

ltbodygt

lthtmlgt

ltDOCTYPE htmlgt

lthtml xmlns=rdquohttpwwww3org1999xhtmlrdquo xmllang=rdquozh-CNrdquo lang=rdquozh-CNrdquogt

ltheadgt

ltmeta http-equiv=rdquoContent-Typerdquo content=rdquotexthtml charset=UTF-7rdquo gt

lttitlegtnodejs static html filelttitlegt

ltheadgt

ltbodygt

lth1gtnodejs static html filelth1gt

ltbodygt

lthtmlgt

36 4 Nodejs基礎

Nodejs中文電子書

準備一個包含基本路由功能的 http伺服器

var server

ip = rdquo127001rdquo

port = 1337

http = require(rsquohttprsquo)

url = require(rsquourlrsquo)

server = httpcreateServer(function (req res)

var path = urlparse(requrl)

)

serverlisten(port ip)

consolelog(rdquoServer running at httprdquo + ip + rdquordquo + port)

加入 le system 模組使用 readFile 的功能將這一段程式放置於

createServer的回應函式中

fsreadFile(filePath encode function(err file)

)

readFile的回應函式裡面加入頁面輸出讓瀏覽器可以正確讀到檔案在這邊我們設定讀取的檔案為 html 靜態檔案所以 Content-type 設定為texthtml讀取到檔案的內容將會正確輸出成 html靜態檔案

fsreadFile(filePath encode function(err file)

reswriteHead(200 rsquoContent-Typersquo rsquotexthtmlrsquo)

reswrite(file)

resend()

)

到這邊為止基本的程式內容都已經完成剩下一些細節的調整首先

路徑上必須做調整目前的靜態檔案全部都放置於 static資料夾底下設定一個變數來記住資料夾位置

接著將瀏覽器發出要求路徑與資料夾組合讀取正確 html靜態檔案使用者有可能會輸入錯誤路徑所以在讀取檔案的時候要加入錯誤處理

同時回應 404伺服器無法正確回應的 http header格式

加入這些細節的修改一個基本的 http 靜態 html輸出伺服器就完成了完整程式碼如下

44 nodejs http靜態檔案輸出 37

Nodejs中文電子書

var server

ip = rdquo127001rdquo

port = 1337

http = require(rsquohttprsquo)

fs = require(rdquofsrdquo)

folderPath = rdquostaticrdquo

url = require(rsquourlrsquo)

path

filePath

encode = rdquoutf8rdquo

server = httpcreateServer(function (req res)

path = urlparse(requrl)

filePath = folderPath + pathpathname

fsreadFile(filePath encode function(err file)

if (err)

reswriteHead(404 rsquoContent-Typersquo rsquotextplainrsquo)

resend()

return

reswriteHead(200 rsquoContent-Typersquo rsquotextapplicationrsquo)

reswrite(file)

resend()

)

)

serverlisten(port ip)

consolelog(rdquoServer running at httprdquo + ip + rdquordquo + port)

45 nodejs http GET資料擷取

http伺服器中除了路由之外另一個最常使用的方法就是擷取 GET資料本單元將會介紹如何透過基本 http伺服器擷取瀏覽器傳來的要求擷取 GET資料

在 http協定中GET參數都是藉由 URL從瀏覽器發出要求送至伺服器

38 4 Nodejs基礎

Nodejs中文電子書

端基本的傳送網址格式可能如下

http127001testsend=1amptest=2

上面這段網址裡面的 GET參數就是 send而這個變數的數值就為 1如果想要在 http伺服器取得 GET資料需要在瀏覽器給予的要求 (request)做處理

首先需要載入 query string這個模組這個模組主要是用來將字串資料

過濾後轉換成javascript物件

qs = require(rsquoquerystringrsquo)

接著在第一階段利用 url模組過濾瀏覽器發出的 URL資料後將回應的物件裡面的 query這個變數是一個字串值資料過濾後如下

send=1amptest=2

透過 query string使用 parse這個方法將資料轉換成 javascript物件就表示 GET的資料已經被伺服器端正式擷取下來

path = urlparse(requrl)

parameter = qsparse(pathquery)

整個 nodejs http GET參數完整擷取程式碼如下

var server

ip = rdquo127001rdquo

port = 1337

http = require(rsquohttprsquo)

qs = require(rsquoquerystringrsquo)

url = require(rsquourlrsquo)

server = httpcreateServer(function (req res)

var path = urlparse(requrl)

parameter = qsparse(pathquery)

consoledir(parameter)

reswriteHead(200 rsquoContent-Typersquo rsquotextplainrsquo)

reswrite(rsquoBrowser test GET parameternrsquo)

resend()

)

45 nodejs http GET資料擷取 39

Nodejs中文電子書

serverlisten(port ip)

consolelog(rdquoServer running at httprdquo + ip + rdquordquo + port)

程式運作之後由瀏覽器輸入要求網址之後nodejs伺服器端回應資料為

Server running at http1270011337

send rsquo1rsquo test rsquo1rsquo

46 本章結語

前面所解說的部份一大部分主要是處理 http伺服器基本問題雖然在某些部分有牽扯到 http 伺服器基本運作原理主要還是希望可以藉由這些基本範例練習 nodejs練習回應函式與語法串接的特點習慣編寫javascript風格程式當然直接這樣開發 nodejs是非常辛苦的接下來在模組實戰開發的部份將會介紹特定的模組一步一步帶領各位從無到有進行

nodejs應用程式開發

40 4 Nodejs基礎

5

NPM套件管理工具

npm全名為 Node Package Manager是 Nodejs的套件(package)管理工具類似 Perl的 ppm或 PHP的 PEAR等安裝 npm後使用npm install

module name指令即可安裝新套件維護管理套件的工作會更加輕鬆

npm可以讓Nodejs的開發者直接利用擴充線上的套件庫(packagesregistry)加速軟體專案的開發npm提供很友善的搜尋功能可以快速找到安裝需要的套件當這些套件發行新版本時npm也可以協助開發者自動更新這些套件

npm不僅可用於安裝新的套件它也支援搜尋列出已安裝模組及更新的功能

51 安裝 NPM

Nodejs在 063版本開始內建 npm讀者安裝的版本若是此版本或更新的版本就可以略過以下安裝說明

若要檢查 npm是否正確安裝可以使用以下的指令

npm -v

41

Nodejs中文電子書

執行結果說明

若 npm正確安裝執行 npm -v將會看到類似 110-2的版本訊息

若讀者安裝的 Nodejs版本比較舊或是有興趣嘗試自己動手安裝 npm工具則可以參考以下的說明

安裝於Windows系統

Nodejs for Windows 於 062 版開始內建 npm使用 nodejsorg 官方提供的安裝程式不需要進一步的設定就可以立即使用 npm指令對於Windows的開發者來說大幅降低環境設定的問題與門檻

除了使用 Nodejs內建的 npm讀者也可以從 npm官方提供的以下網址

httpnpmjsorgdist

這是由 npm提供的 Fancy Windows Install版本請下載壓縮檔(例如npm-110-3zip)並將壓縮檔內容解壓縮至 Nodejs的安裝路徑(例如CProgram Filesnodejs)

解壓縮後在 Nodejs的安裝路徑下應該有以下的檔案及資料夾

bull npmcmd(檔案)

bull node modules(資料夾)

安裝於 Linux系統

Ubuntu Linux的使用者可以加入 NPM Unoffcial PPA這個 repository即可使用 apt-get完成 npm安裝

42 5 NPM套件管理工具

Nodejs中文電子書

Ubuntu Linux使用 apt-get安裝 npm

sudo apt-get install python-software-properties

sudo add-apt-repository ppagias-kay-leenpm

sudo apt-get update

sudo apt-get install npm

npm官方提供的安裝程式 installsh可以適用於大多數的 Linux系統使用這個安裝程式請先確認

1 系統已安裝 curl工具(請使用 curl --version查看版本訊息)

2 已安裝 Nodejs並且 PATH正確設置

3 Nodejs的版本必須大於 04x

以下為 npm提供的安裝指令

curl httpnpmjsorginstallsh | sh

安裝成功會看到如下訊息

installsh安裝成功的訊息

npm10105 homeUSERNAMElocalnodelibnode_modulesnpm

It worked

安裝於Mac OS X

建議採用與 Nodejs相同的方式進行 npm的安裝例如使用MacPorts安裝 Nodejs就同樣使用MacPorts安裝 npm這樣對日後的維護才會更方便容易

使用 MacPorts安裝 npm是本書比較建議的方式它可以讓 npm的安裝移除及更新工作自動化將會幫助開發者節省寶貴時間

51 安裝 NPM 43

Nodejs中文電子書

安裝MacPorts的提示

在MacPorts網站可以取得 OS X系統版本對應的安裝程式(例如 106或 107)httpwwwmacportsorg安裝過程會詢問系統管理者密碼使用預設的選項完成安裝即可安

裝MacPorts之後在終端機執行 port -v將會看到MacPorts的版本訊息

安裝 npm之前先更新MacPorts的套件清單以確保安裝的 npm是最新版本

sudo port -d selfupdate

接著安裝 npm

sudo port install npm

若讀者的 Nodejs並非使用MacPorts安裝則不建議使用MacPorts安裝npm因為 MacPorts會自動檢查並安裝相依套件而 npm相依 nodejs所以MacPorts也會一併將 nodejs套件安裝造成先前讀者使用其它方式安裝的 nodejs被覆蓋

讀者可以先使用 MacPorts安裝 curl(sudo port install curl)

再參考 Linux的 installsh安裝方式即可使用 npm官方提供的安裝程式

NPM安裝後測試

npm是指令列工具(command-line tool)使用時請先打開系統的文字終端機工具

測試 npm安裝與設定是否正確請輸入指令如下

npm -v

或是

npm --version

如果 npm已經正確安裝設定就會顯示版本訊息

44 5 NPM套件管理工具

Nodejs中文電子書

執行結果(範例)

110-2

52 使用 NPM安裝套件

npm目前擁有超過 6000種套件(packages)可以在 npm registry使用關鍵字搜尋套件

httpsearchnpmjsorg

舉例來說在關鍵字欄位輸入「coffee-script」下方的清單就會自動列出包含 coffee-script關鍵字的套件

接著我們回到終端機模式的操作npm的指令工具本身就可以完成套

件搜尋的任務

例如以下的指令同樣可以找出 coffee-script相關套件

npm search coffee-script

以下是搜尋結果的參考畫面

52 使用 NPM安裝套件 45

Nodejs中文電子書

找到需要的套件後(例如 express)即可使用以下指令安裝

npm install coffee-script

值得注意的一點是使用 npm install會將指定的套件安裝在工

作目錄(Working Directory)的 node modules資料夾下

以Windows為例如果執行 npm install的目錄位於

Cproject1

那麼 npm將會自動建立一個 node modules的子目錄(如果不存在)

Cproject1node modules

並且將下載的套件放置於這個子目錄例如

Cproject1node modulescoffee-script

這個設計讓專案可以個別管理相依的套件並且可以在專案佈署或發

行時將這些套件(位於 node modules)一併打包方便其它專案的使用者不必再重新下載套件

這個 npm install的預設安裝模式為 local(本地)只會變更當前專案的資料夾不會影響系統

另一種安裝模式稱為 global(全域)這種模式會將套件安裝到系統資

料夾也就是 npm安裝路徑的 node modules資料夾例如

CProgram Filesnodejsnode modules

46 5 NPM套件管理工具

Nodejs中文電子書

是否要使用全域安裝可以依照套件是否提供新指令來判斷舉例來說express 套件提供 express 這個指令而 coffee-script 則提供 coffee

指令

在 local 安 裝 模 式 中這 些 指 令 的 程 式 檔 案會 被 安 裝 到node modules的 bin這個隱藏資料夾下除非將bin的路徑加入 PATH環境變數否則要執行這些指令將會相當不便

為了方便指令的執行我們可以在 npm install 加上 -g 或 --

global參數啟用 global安裝模式例如

npm install -g coffee-script

npm install -g express

使用 global安裝模式需要注意執行權限的問題若權限不足可能會出現類似以下的錯誤訊息

npm ERR Error EACCES permission denied rsquorsquo

npm ERR

npm ERR Please try running this command again as rootAdministrator

要獲得足夠得執行權限請參考以下說明

bull Windows 7 或 2008 以上在「命令提示字元」的捷徑按右鍵選擇「以系統管理員身分執行」執行 npm指令時就會具有 Administrator身分

bull Mac OS X或 Linux系統可以使用 sudo指令例如

sudo npm install -g express

bull Linux系統可以使用 root權限登入或是以「sudo su -」切換成 root身分(使用 root權限操作系統相當危險因此並不建議使用這種方式)

若加上 -g參數使用 npm install -g coffee-script完成安裝

後就可以在終端機執行 coffee指令例如

coffee -v

52 使用 NPM安裝套件 47

Nodejs中文電子書

執行結果(範例)

CoffeeScript version 120

53 套件的更新及維護

除了前一節說明的 search 及 install 用法npm 還提供其他許多指令(commands)

使用 npm help可以查詢可用的指令

npm help

執行結果(部分)

where ltcommandgt is one of

adduser apihelp author bin bugs c cache completion

config deprecate docs edit explore faq find get

help help-search home i info init install la link

list ll ln login ls outdated owner pack prefix

prune publish r rb rebuild remove restart rm root

run-script s se search set show star start stop

submodule tag test un uninstall unlink unpublish

unstar up update version view whoami

使用 npm help command可以查詢指令的詳細用法例如

npm help list

接下來本節要介紹開發過程常用的 npm指令

使用 list可以列出已安裝套件

npm list

48 5 NPM套件管理工具

Nodejs中文電子書

執行結果(範例)

-- coffee-script120

- express256

- connect185

| -- formidable108

-- mime124

-- mkdirp007

-- qs041

檢視某個套件的詳細資訊例如

npm show express

升級所有套件(如果該套件已發佈更新版本)

npm update

升級指定的套件

npm update express

移除指定的套件

npm uninstall express

54 使用 packagejson

對於正式的 Nodejs專案可以建立一個命名為 packagejson的設

定檔(純文字格式)檔案內容參考範例如下

54 使用 packagejson 49

Nodejs中文電子書

packagejson(範例)

rdquonamerdquo rdquoapplication-namerdquo

rdquoversionrdquo rdquo001rdquo

rdquoprivaterdquo true

rdquodependenciesrdquo

rdquoexpressrdquo rdquo255rdquo

rdquocoffee-scriptrdquo rdquolatestrdquo

rdquomongooserdquo rdquogt= 253rdquo

其中 name與 version依照專案的需求設置

需要注意的是 dependencies的設定它用於指定專案相依的套件名

稱及版本

bull rdquoexpressrdquo rdquo255rdquo

代表此專案相依版本 255的 express套件

bull rdquocoffee-scriptrdquo rdquolatestrdquo

使用最新版的 coffee-script套件(每次更新都會檢查新版)

bull rdquomongooserdquo rdquogt= 253rdquo

使用版本大於 253的 mongoose套件

假設某個套件的新版可能造成專案無法正常運作就必須指定套件的

版本避免專案的程式碼來不及更新以相容新版套件通常在開發初期的

專案需要盡可能維持新套件的相容性(以取得套件的更新或修正)可以

用「gt=」設定最低相容的版本或是使用「latest」設定永遠保持最新

套件

50 5 NPM套件管理工具

6

Express介紹

在前面的 nodejs基礎當中介紹許多許多開設 http的使用方法及介紹以及許多基本的 nodejs基本應用

接下來要介紹一個套件稱為 express [Express](httpexpressjscom)這個套件主要幫忙解決許多 nodejs http server所需要的基本服務讓開發 httpservice變得更為容易不需要像之前需要透過層層模組(module)才有辦法開始編寫自己的程式

這個套件是由 TJ Holowaychuk 製作而成的套件裡面包含基本的路由處理 (route)http資料處理(GETPOSTPUT)另外還與樣板套件(jshtml template engine)搭配同時也可以處理許多複雜化的問題

61 Express安裝

安裝方式十分簡單只要透過之前介紹的 NPM就可以使用簡單的指令安裝指令如下

這邊建議需要將此套件安裝成為全域模組方便日後使用

51

Nodejs中文電子書

62 Express基本操作

express的使用也十分簡單先來建立一個基本的 hello world

var app = require(rsquoexpressrsquo)createServer()

port = 1337

applisten(port)

appget(rsquorsquo function(req res)

ressend(rsquohello worldrsquo)

)

consolelog(rsquostart express servernrsquo)

可以從上面的程式碼發現基本操作與 nodejs http的建立方式沒有太大差異主要差在當我們設定路由時可以直接透過 appget方式設定回應與接受方式

63 Express路由處理

Express對於 http服務上有許多包裝讓開發者使用及設定上更為方便例如有幾個路由設定那我們就統一藉由 appget來處理

Create http server

appget(rsquorsquo function(req res)

ressend(rsquohello worldrsquo)

)

appget(rsquotestrsquo function(req res)

ressend(rsquotest renderrsquo)

)

appget(rsquouserrsquo function(req res)

ressend(rsquouser pagersquo)

)

52 6 Express介紹

Nodejs中文電子書

如上面的程式碼所表示appget可以帶入兩個參數第一個是路徑名稱設定第二個為回應函式 (call back function)回應函式裡面就如同之前的 createServer方法裡面包含 requestresponse兩個物件可供使用使用者就可以透過瀏覽器輸入不同的 url切換到不同的頁面顯示不同的結果

路由設定上也有基本的配對方式讓使用者從瀏覽器輸入的網址可以

是一個變數只要符合型態就可以有對應的頁面產出例如

Create http server

appget(rsquouseridrsquo function(req res)

ressend(rsquouser rsquo + reqparamsid)

)

appget(rsquonumberrsquo function(req res)

ressend(rsquonumber rsquo + reqparamsnumber)

)

裡 面 使 用 到number 從 網 址 輸 入 之 後 就 可 以 直 接 使 用reqparamsnumber 取得所輸入的資料變成 url 參數使用當然前面也是可以加上路徑的設定userid在瀏覽器上路徑必須符合userxxx透

過 reqparamsid就可以取到 xxx這個字串值

另外express參數處理也提供了路由參數配對處理也可以透過正規表示法作為參數設定

var app = require(rsquoexpressrsquo)createServer()

port = 1337

applisten(port)

appget(^ip((d23)((d23))((d23))((d23))) function(req res)

ressend(reqparams)

)

上面程式碼可以發現後面路由設定的型態是正規表示法裡面設定

格式為ip之後必須要加上 ip型態才會符合資料格式同時取得 ip資料已經由正規表示法將資料做分群因此可以取得 ip的四個數字

此程式執行之後可以透過瀏覽器測試輸入網址為 localhost3000ip25525510010可以從頁面獲得資料

63 Express路由處理 53

Nodejs中文電子書

此章節全部範例程式碼如下

overview

author Caesar Chi

blog clonnblogspotcom

version 20120226

create server

var app = require(rsquoexpressrsquo)createServer()

port = 1337

applisten(port)

normal style

appget(rsquorsquo function(req res)

ressend(rsquohello worldrsquo)

)

appget(rsquotestrsquo function(req res)

ressend(rsquotest renderrsquo)

)

parameter style

appget(rsquouseridrsquo function(req res)

ressend(rsquouser rsquo + reqparamsid)

)

appget(rsquonumberrsquo function(req res)

ressend(rsquonumber rsquo + reqparamsnumber)

)

REGX style

appget(^ip((d23)((d23))((d23))) function(req res)

ressend(reqparams)

)

appget(rsquorsquo function(req res)

ressend(rsquoPage not foundrsquo 404)

54 6 Express介紹

Nodejs中文電子書

)

consolelog(rsquostart express servernrsquo)

64 Express middleware

Express 裡面有一個十分好用的應用概念稱為 middleware可以透過middleware做出複雜的效果同時上面也有介紹 next方法參數傳遞就是靠 middleware的概念來傳遞參數讓開發者可以明確的控制程式邏輯

create http server

appuse(expressbodyParser())

appuse(expressmethodOverride())

appuse(expresssession())

上面都是一種 middleware的使用方式透過 appuse方式裡面載入函式執行方法回應函式會包含三個基本參數responserequestnext其中next表示下一個 middleware執行函式同時會自動將預設三個參數繼續帶往下個函式執行底下有個實驗

上面的片段程式執行後開啟瀏覽器連結上 localhost1337會發現伺服器回應結果順序如下

first middle ware

second middle ware

execute middle ware

end middleware function

從上面的結果可以得知剛才設定的 middleware都生效了在 appuse設定的 middleware是所有 url皆會執行方法如果有指定特定方法就可以使用 appget的 middleware設定在 appget函式的第二個參數就可以帶入函式或者是匿名函式只要函式裡面最後會接受 request response next這三個參數同時也有正確指定 next函式的執行時機最後都會執行到最後一個方法當然開發者也可以評估程式邏輯要執行到哪一個階段讓邏輯

可以更為分明

64 Express middleware 55

Nodejs中文電子書

65 Express路由應用

在實際開發上可能會遇到需要使用參數等方式混和變數一起使用

express裡面提供了一個很棒的處理方法 appall這個方式可以先採用基本路由配對再將設定為每個不同的處理方式開發者可以透過這個方式簡

化自己的程式邏輯

overview

author

version 20120226

create server

var app = require(rsquoexpressrsquo)createServer()

port = 1337

users = [

name rsquoClonnrsquo

name rsquoChirsquo

]

applisten(port)

appall(rsquouseridoprsquo function(req res next)

requser = users[reqparamsid]

if (requser)

next()

else

next(new Error(rsquocannot find user rsquo + reqparamsid))

)

appget(rsquouseridrsquo function(req res)

ressend(rsquoviewing rsquo + requsername)

)

appget(rsquouserideditrsquo function(req res)

ressend(rsquoediting rsquo + requsername)

)

56 6 Express介紹

Nodejs中文電子書

appget(rsquouseriddeletersquo function(req res)

ressend(rsquodeleting rsquo + requsername)

)

appget(rsquorsquo function(req res)

ressend(rsquoPage not foundrsquo 404)

)

consolelog(rsquostart express servernrsquo)

內部宣告一組預設的使用者分別給予名稱設定藉由 appall這個方法可以先將路由雛形建立再接下來設定 appget的路徑格式只要符合格式就會分配進入對應的方法中像上面的程式當中如果使用者輸入路徑為user0除了執行 appall程式之後執行 next方法就會對應到路徑設定為userid的這個方法當中如果使用者輸入路徑為user0edit就會執行到useridedit的對應方法

66 Express GET應用範例

我們準備一個使用 GET方法傳送資料的表單

ltDOCTYPE htmlgt

lthtmlgt

ltheadgt

ltmeta http-equiv=rdquoContent-Typerdquo content=rdquotexthtml charset=utf8rdquogt

lttitlegtNodejs 菜鳥筆記 (1)lttitlegt

ltlink rel=rdquostylesheetrdquo href=rdquocssstylecssrdquo type=rdquotextcssrdquo media=rdquoallrdquo gt

ltheadgt

ltbodygt

lth1gtNodejs 菜鳥筆記-註冊lth1gt

ltform id=rdquosignuprdquo method=rdquoGETrdquo action=rdquohttplocalhost3000Signuprdquogt

ltlabelgt 使用者名稱ltlabelgtltinput type=rdquotextrdquo id=rdquousernamerdquo name=rdquousernamerdquo gtltbrgt

ltlabelgt 電子郵件ltlabelgtltinput type=rdquotextrdquo id=rdquoemailrdquo name=rdquoemailrdquo gtltbrgt

ltinput type=rdquosubmitrdquo value=rdquo 註冊我的帳號rdquo gtltbrgt

ltformgt

ltbodygt

lthtmlgt

66 Express GET應用範例 57

Nodejs中文電子書

這個表單沒有什麼特別的地方我們只需要看第 9 行form 使用的method是 GET然後 action是rdquohttplocalhost3000Signupldquo等一下我們要來撰寫Signup這個 URL Path的處理程式

處理 Signup行為

我們知道所謂的 GET方法會透過 URL來把表單的值給帶過去以上面的表單來說到時候 URL會以這樣的形式傳遞

httplocalhost3000Signupusername=xxxampemail=xxx

所以要能處理這樣的資料必須有以下功能

bull 解析 URL

bull 辨別動作是 Signup

bull 解析出 username和 email

一旦能取得 username和 email的值程式就能加以應用了

處理 Signup的程式碼雛形

load module

var url = require(rsquourlrsquo)

urlData = urlparse(requrltrue)

action = urlDatapathname

reswriteHead(200 rdquoContent-Typerdquordquotexthtml charset=utf-8rdquo)

if (action === rdquoSignuprdquo)

user = urlDataquery

resend(rdquolth1gtrdquo + userusername + rdquo 歡迎您的加入 lth1gtltpgt 我們已經將會員啟用信寄至rdquo + useremail + rdquoltpgtrdquo)

首先需要加載 url module它是用來協助我們解析 URL的模組接著使用 urlparse方法第一個傳入 url字串變數也就是 requrl另外第二個參數的用意是設為 ture則引進 querystring模組來協助處理預設是 false它影響到的是 urlDataquery設為 true會傳回物件不然就只是一般的字串urlparse會將字串內容整理成一個物件我們把它指定給 urlData

action變數作為記錄 pathname這是我們稍後要來判斷目前網頁的動作是什麼接著先將 html表頭資訊 (Header)準備好再來判斷路徑邏輯如

58 6 Express介紹

Nodejs中文電子書

果是 Signup這個動作就把 urlDataquery裡的資料指定給 user然後輸出userusername和 useremail把使用者從表單註冊的資料顯示於頁面中

最後進行程式測試啟動 nodejs主程式之後開啟瀏覽器就會看到表單填寫完畢按下送出就可以看到結果了

完整 nodejs程式碼如下

var http = require(rsquohttprsquo)

url = require(rsquourlrsquo)

fs = require(rdquofsrdquo)

server

server = httpcreateServer(function (reqres)

var urlData

encode = rdquoutf8rdquo

filePath = rdquoviewexpress_get_example_formhtmlrdquo

action

urlData = urlparse(requrltrue)

action = urlDatapathname

reswriteHead(200 rdquoContent-Typerdquordquotexthtml charset=utf-8rdquo)

if (action === rdquoSignuprdquo)

user = urlDataquery

resend(rdquolth1gtrdquo + userusername + rdquo 歡迎您的加入 lth1gtltpgt 我們已經將會員啟用信寄至rdquo + useremail + rdquoltpgtrdquo)

else

fsreadFile(filePath encode function(err file)

reswrite(file)

resend()

)

)

serverlisten(3000)

consolelog(rsquoServer 跑起來了現在時間是rsquo + new Date())

66 Express GET應用範例 59

Nodejs中文電子書

67 Express POST應用範例

一開始準備基本的 html表單傳送內容以 POST方式form的 action屬性設定為 POST其餘 html內容與前一個範例應用相同

ltDOCTYPE htmlgt

lthtmlgt

ltheadgt

ltmeta http-equiv=rdquoContent-Typerdquo content=rdquotexthtml charset=utf8rdquogt

lttitlegtNodejs 菜鳥筆記 (1)lttitlegt

ltlink rel=rdquostylesheetrdquo href=rdquocssstylecssrdquo type=rdquotextcssrdquo media=rdquoallrdquo gt

ltheadgt

ltbodygt

lth1gtNodejs 菜鳥筆記-註冊lth1gt

ltform id=rdquosignuprdquo method=rdquoPOSTrdquo action=rdquohttplocalhost3000Signuprdquogt

ltlabelgt 使用者名稱ltlabelgtltinput type=rdquotextrdquo id=rdquousernamerdquo name=rdquousernamerdquo gtltbrgt

ltlabelgt 電子郵件ltlabelgtltinput type=rdquotextrdquo id=rdquoemailrdquo name=rdquoemailrdquo gtltbrgt

ltinput type=rdquosubmitrdquo value=rdquo 註冊我的帳號rdquo gtltbrgt

ltformgt

ltbodygt

lthtmlgt

nodejs的程式處理邏輯與前面 GET範例類似部分程式碼如下

qs = require(rsquoquerystringrsquo)

if (action === rdquoSignuprdquo)

formData = rsquorsquo

reqon(rdquodatardquo function (data)

formData += data

)

reqon(rdquoendrdquo function ()

user = qsparse(formData)

resend(rdquolth1gtrdquo + userusername + rdquo 歡迎您的加入 lth1gtltpgt 我們已經將會員啟用信寄至rdquo + useremail + rdquoltpgtrdquo)

)

60 6 Express介紹

Nodejs中文電子書

主要加入了rsquoquerystringrsquo這個moduel方便我們等一下解析由表單 POST回來的資料另外加入一個 formData的變數用來搜集待等一下表單回傳的資料前面的 GET範例我們只從 req拿出 url的資料這次要在利用req身上的事件處理

JavaScript 在訂閱事件時使用 addEventListener而 nodejs 使用的則是on這邊加上了監聽 data的事件會在瀏覽器傳送資料到Web Server時被執行參數是它所接收到的資料型態是字串

接著再增加 end的事件當瀏覽器的請求事件結束時它就會動作

由於瀏覽器使用 POST在上傳資料時會將資料一塊塊地上傳因為我們在監聽 data事件時透過 formData變數將它累加起來lt不過由於我們

上傳的資料很少一次就結束不過如果日後需要傳的是資料比較大的檔

案這個累加動作就很重要

當資料傳完就進到 end 事件中會用到 qsparse 來解析 formDataformData的內容是字串內容是

username=wordsmithampemail=wordsmith40somewhere

而 qsparse可以幫我們把這個 querystring轉成物件的格式也就是

username=wordsmithampemail=wordsmith40somewhere

一旦轉成物件並指定給 user之後其他的事情就和 GET方法時操作的一樣寫 response的表頭將內容回傳並將 userusername和 useremail代入到內容中

修改完成後接著執行 nodejs程式啟動 web server開啟瀏覽器進入表單測試看看POST的方式能否順利運作

完整程式碼如下

var http = require(rsquohttprsquo)

url = require(rsquourlrsquo)

fs = require(rdquofsrdquo)

qs = require(rsquoquerystringrsquo)

server

server = httpcreateServer(function (reqres)

var urlData

67 Express POST應用範例 61

Nodejs中文電子書

encode = rdquoutf8rdquo

filePath = rdquoviewexpress_post_example_formhtmlrdquo

formData

action

urlData = urlparse(requrltrue)

action = urlDatapathname

reswriteHead(200 rdquoContent-Typerdquordquotexthtml charset=utf-8rdquo)

if (action === rdquoSignuprdquo)

formData = rsquorsquo

reqon(rdquodatardquo function (data)

formData += data

)

reqon(rdquoendrdquo function ()

user = qsparse(formData)

resend(rdquolth1gtrdquo + userusername + rdquo 歡迎您的加入 lth1gtltpgt 我們已經將會員啟用信寄至rdquo + useremail + rdquoltpgtrdquo)

)

else

fsreadFile(filePath encode function(err file)

reswrite(file)

resend()

)

)

serverlisten(3000)

consolelog(rsquoServer 跑起來了現在時間是rsquo + new Date())

62 6 Express介紹

Nodejs中文電子書

68 Express AJAX應用範例

在 Nodejs要使用 Ajax傳送資料並且與之互動在接受資料的部份沒有太大的差別client端不是用 GET就是用 POST來傳資料重點在處理完後用 JSON格式回傳當然 Ajax不見得只傳 JSON格式有時是回傳一段 HTML碼不過後者對伺服器來說基本上就和前兩篇沒有差別了所以我們還是以回傳 JSON做為這一回的主題

這一回其實大多數的工作都會落在前端 Ajax上面前端要負責發送與接收資料並在接收資料後撤掉原先發送資料的表單並將取得的資料

改成 HTML格式之後放上頁面

首先先準備 HTML靜態頁面資料

ltDOCTYPE htmlgt

lthtmlgt

ltheadgt

lttitlegtNodejs 菜鳥筆記 (3)lttitlegt

ltlink rel=rdquostylesheetrdquo href=rdquocssstylecssrdquo type=rdquotextcssrdquo media=rdquoallrdquo gt

ltscript src=rdquohttpsajaxgoogleapiscomajaxlibsjquery171jqueryminjsrdquogtltscriptgt

ltheadgt

ltbodygt

lth1gtNodejs 菜鳥筆記-註冊 (用 Ajax)lth1gt

ltform id=rdquosignuprdquo action=rdquoSignuprdquo method=rdquoPOSTrdquogt

ltlabelgt 使用者名稱ltlabelgtltinput type=rdquotextrdquo id=rdquousernamerdquo name=rdquousernamerdquo gtltbrgt

ltlabelgt 電子郵件ltlabelgtltinput type=rdquotextrdquo id=rdquoemailrdquo name=rdquoemailrdquo gtltbrgt

ltinput type=rdquosubmitrdquo value=rdquo 註冊我的帳號rdquo gtltbrgt

ltformgt

ltdiv id=rdquoresultrdquogt

ltdivgt

ltscript type=rdquotextjavascriptrdquogt

$(function()$(rsquosignup input[type=rdquosubmitrdquo]rsquo)click(function(e)

var user =

userusername = $(rdquousernamerdquo)val()useremail = $(rdquoemailrdquo)val()

$post(rdquoSignuprdquouserfunction(data)greet(data)

)

68 Express AJAX應用範例 63

Nodejs中文電子書

epreventDefault()

)

var greet = function(msg)

var resultNode = $(rsquoresultrsquo)greeting = $(rsquolth2gtHirsquo+msgusername+rsquo 你的會員 id 是rsquo+ msgid +rsquo 我們會將會員啟動信寄至rsquo+msgemail+rsquolth2gtrsquo)

$(rsquosignuprsquo)hide()resultNodehtml(rdquordquo)

resultNodeappend(greeting)

)

ltscriptgt

ltbodygt

lthtmlgt

HTML頁面上準備了一個表單用來傳送註冊資料接著直接引用了Google CDN來載入 jQuery用來幫我們處理 Ajax的工作這次要傳送和接收的工作很大的變動都在 HTML頁面上的 JavaScript當中我們要做的事有 (相關 jQuery處理這邊不多做贅述指提起主要功能解說)

bull 用 jQUery取得 submit按鈕綁定它的 click動作

bull 取得表單 username和 email的值存放在 user這個物件中

bull 用 jQuery的 $post方法將 user的資料傳到 Server

bull 一旦成功取得資料後透過 greet這個 function組成回報給 user的訊息

bull 清空原本給使用者填資料的表單

bull 將 Server回傳的 usernameemail和 id這 3個資料組成回應的訊息

bull 將訊息放到原本表單的位置

經過以上的處理後一個 Ajax的表單的基本功能已經完成

接著進行 nodejs主要程式的編輯部分程式碼如下

var fs = require(rdquofsrdquo)

64 6 Express介紹

Nodejs中文電子書

qs = require(rsquoquerystringrsquo)

if (action === rdquoSignuprdquo)

formData = rsquorsquo

reqon(rdquodatardquo function (data)

formData += data

)

reqon(rdquoendrdquo function ()

var msg

user = qsparse(formData)

userid = rdquo123456rdquo

msg = JSONstringify(user)

reswriteHead(200 rdquoContent-Typerdquordquoapplicationjson charset=utf-8rdquordquoContent-Lengthrdquomsglength)

resend(msg)

)

這裡的程式和前面 POST範例基本上大同小異差別在

bull 幫 user的資料加上 id隨意存放一些文字進去讓 Server回傳的資料多於 Client端傳上來的不然會覺得 Server都沒做事

bull 增加了 msg 這個變數存放將 user 物件 JSON 文字化的結果JSONstringify這個轉換函式是 V8引擎所提供的如果你好奇的話

bull 大重點來了我們要告訴 Client端這次回傳的資料格式是 JSON所在 Content-type和 Content-Length要提供給 Client

Server很輕鬆就完成任務了最後進行程式測試啟動 nodejs主程式之後開啟瀏覽器就會看到表單填寫完畢按下送出就可以看到結果了

最後 nodejs本篇範例程式碼如下

var http = require(rsquohttprsquo)

url = require(rsquourlrsquo)

fs = require(rdquofsrdquo)

qs = require(rsquoquerystringrsquo)

server

68 Express AJAX應用範例 65

Nodejs中文電子書

server = httpcreateServer(function (reqres)

var urlData

encode = rdquoutf8rdquo

filePath = rdquoviewexpress_ajax_example_formhtmlrdquo

formData

action

urlData = urlparse(requrltrue)

action = urlDatapathname

if (action === rdquoSignuprdquo)

formData = rsquorsquo

reqon(rdquodatardquo function (data)

formData += data

)

reqon(rdquoendrdquo function ()

var msg

user = qsparse(formData)

userid = rdquo123456rdquo

msg = JSONstringify(user)

reswriteHead(200 rdquoContent-TyperdquordquoapplicationjsonrdquordquoContent-Lengthrdquomsglength)

resend(msg)

)

else

fsreadFile(filePath encode function(err file)

reswriteHead(200 rdquoContent-Typerdquordquotexthtml charset=utf-8rdquo)

reswrite(file)

resend()

)

)

serverlisten(3000)

66 6 Express介紹

Nodejs中文電子書

consolelog(rsquoServer 跑起來了現在時間是rsquo + new Date())

69 原始資料提供

bull [NodeJS初學者筆記 (1)-用 GET傳送資料] (httpithelpithomecomtwquestion10087402)

bull [NodeJS初學者筆記 (2)-用 POST傳送資料] (httpithelpithomecomtwquestion10087489)

bull [NodeJS 初學者筆記 (3)-用 Ajax 傳送資料] (httpithelpithomecomtwquestion10087627)

69 原始資料提供 67

Nodejs中文電子書

68 6 Express介紹

7

CoffeeScript

bull Spine

bull Hem

bull Spineapp

Letrsquos Have a Cup of CoffeeScript

bull es5-shim ECMAScript 5 compatibility shims for legacy JavaScript engines

ESnext

node-iform - A middleware for node-validator httpsgithubcomguileennode-iform

69

Nodejs中文電子書

70 7 CoffeeScript

8

製作一個 Hubot的 Plurk Adapter

81 應用事項提醒

此應用範例以CoffeeScript編寫主要程式架構採用Github釋放的Hubot專案以下有幾個主要注意事項請讀者注意

bull Hubot 一開始的架構除了內建的兩個 Adapter 之外其餘都要以Nodejs的Module方式才能運作

bull Hubot的 binhubot裡面寫著 npm install所以不管你怎麼改原始碼也不能改變第一點的狀況

其實上述都在討論同一件事情NodeJS的模組而且模組不能設定相對路徑之類的來安裝一定要包裝成 npm相符和格式才有辦法正確執行

82 建立 Adapter

首先需要準備兩個檔案

bull packagejson

bull plurkcoffee

71

Nodejs中文電子書

有這兩個就足以變成 NodeJS的模組了在開始 Coding之前先來設定一下 packagejson相依性

rdquonamerdquo rdquohubot-plurkrdquo

rdquoversionrdquo rdquo011rdquo

rdquomainrdquo rdquoplurkrdquo

rdquodependenciesrdquo

rdquohubotrdquo rdquogt=205rdquo

rdquooauthrdquo rdquordquo

rdquocronrdquordquordquo

這邊會使用到NodeJS的 cron模組(Module)而登入 Plurk需要OAuth標準授權才行所以也需要加載 OAuth模組

注意Hubot在讀取非內建模組時會自動在前面加上 hubot-的前置

83 建立 Robot跟 API

本篇應用基本上程式碼大部分參考 Hubot - Twitter Adapter來製作主要差異只有在使用 OAuth這個模組Twitter有 Streaming可用而 Plurk則得用 Comet方式來達到即時讀取

plurkcoffee程式碼

Robot = require(rdquohubotrdquo)robot()

Adapter = require(rdquohubtordquo)adapter()

EventEmitter = require(rdquoeventsrdquo)EventEmitter

oauth = require(rdquooauthrdquo)

cronJob = require(rdquocronrdquo)CronJob

class Plurk exntends Adapter

class PlurkStreaming exnteds EventEmitter

72 8 製作一個 Hubot的 Plurk Adapter

Nodejs中文電子書

先弄個基本架構主要 Class皆參考 Twitter Adapter命名方式接著再將 Plurk這個 Class增加幾個Method基本上只要有 run send reply就夠了而 run用來做初始化的部分

class Plurk entends Adapter

send (plurk id strings⋯) -gt

reply (plurk id strings⋯) -gt

run -gt

看起來有點東西接著來處理主要的 Plurk API結合部分

class PlurkStreaming extends EventEmitter

consuctor (options) -gt

plurk (callback) -gt

觀察河道

getChannel -gt

取得 Comet 網址

reply (plurk id message) -gt

回噗

acceptFriends -gt

接受好友

get (path callback) -gt

GET 請求

post (path body callback)-gt

POST 請求(其實是裝飾)

request (method path body callback)-gt

主要的 OAuth 請求

comet (server callback)-gt

噗浪的 Comet 傳回是 JavaScript Callback 要另外處理後才會變成 JSON

然後把注意力集中到 constructor上先把建構子弄好

constructor (options) -gt

super()

if optionskey and optionssecret and optionstoken and optionstoken secret

key = optionskey

secret = optionssecret

token = optionstoken

token secret = optionstoken secret

83 建立 Robot跟 API 73

Nodejs中文電子書

建立 OAuth 連接

consumer = new oauthOAuth(

rdquohttpwwwplurkcomOAuthrequest tokenrdquo

rdquohttpwwwplurkcomOAuthaccess tokenrdquo

key

secret

rdquo10rdquo

rdquohttpwwwplurkcomOAuthauthorizerdquo

rdquoHMAC-SHA1rdquo

)

domain = rdquowwwplurkcomrdquo

初始化取得 Comet 網址

do getChannel

else

throw new Error(rdquo 參數不足需要 Key Secret Token Token Secretrdquo)

接著來處理 request這個 method

request (method path body callback) -gt

記錄一下這次的 Request

consolelog(rdquohttpdomainpathrdquo)

Callback 這邊先不丟進去要用另一種方式處理

request = consumerget(rdquohttpdomainpathrdquo token token secret null)

requeston rdquoresponserdquo (res) -gt

reson rdquodatardquo (chunk) -gt

parseResponse(chunk+rsquorsquo callback)

reson rdquoendrdquo (data) -gt

consolelog rdquoEnd Request pathrdquo

reson rdquoerrorrdquo (data) -gt

consolelog rdquoError rdquo + data

requestend()

處理資料

parseResponse = (data callback) -gt

if datalength gt 0

用 TryCatch 避免處理 JSON 出錯導致整個中斷

try

callback null JSONparse(data)

catch err

74 8 製作一個 Hubot的 Plurk Adapter

Nodejs中文電子書

consolelog(rdquoError Parse JSONrdquo + data err)

繼續執行

callback null data ||

大致上就是這樣上面程式的架構已經將整個 Hubot Plurk Adapter完成因為在測試時竟然因為噗浪 Lag而沒讀到完整的 Comet資料然後造成程式異常為了避免這個問題發生因此需要加上為了完美呈現需要再

加上 Comet的處理所以要使用到 EventEmitter的功能

comet (server callback) -gt

在 Callback 裡面會找不到自身所以設定區域變數

self =

記錄一下這次的 Request

consolelog(rdquo[Comet] serverrdquo)

Callback 這邊先不丟進去要用另一種方式處理

request = consumerget(rdquohttpdomainpathrdquo token token secret null)

requeston rdquoresponserdquo (res) -gt

reson rdquodatardquo (chunk) -gt

parseResponse(chunk+rsquorsquo callback)

reson rdquoendrdquo (data) -gt

consolelog rdquoEnd Request pathrdquo

請求結束發出事件通知可以進行下一次請求

selfemit rdquonextPlurkrdquo

reson rdquoerrorrdquo (data) -gt

consolelog rdquoError rdquo + data

requestend()

處理資料

parseResponse = (data callback) -gt

if datalength gt 0

用 trycatch 避免失敗中斷

try

去掉 JavaScript 的 Callback

data = datamatch(CometChannelscriptCallback((+))s)

jsonData = rdquordquo

if data

83 建立 Robot跟 API 75

Nodejs中文電子書

jsonData = JSONparse(data[1])

else

如果沒有任何 Match 嘗試直接 parse

jsonData = JSONparse(data)

catch err

consolelog(rdquo[Comet] Errorrdquo data err)

用 TryCatch 避免處理 JSON 出錯導致整個中斷

try

只傳入 json 的 data 部分

callback null jsonDatadata

catch err

consolelog(rdquo[Comet]Error Parse JSONrdquo + data err)

繼續執行

callback null data ||

後面的 get跟 post就簡單多了

get (path callback) -gt

request(rdquoGETrdquo path null callback)

post (path body callback) -gt

request(rdquoPOSTrdquo path body callback)

接著處理取的 Comet網址的 getChannel

getChannel -gt

self =

get rdquoAPPRealtimegetUserChannelrdquo (error data) -gt

if error

檢查是否有 comet server

if datacomet server

selfchannel = datacomet server

如果沒有 Channel Ready 就嘗試連接會失敗

selfemit(rsquochannel readyrsquo)

那麼先來處理 Plurk Adaper好處理的部份

send (plruk id strings⋯)-gt

跟 Reply 一樣直接交給 reply 做

reply plurk id strings⋯

76 8 製作一個 Hubot的 Plurk Adapter

Nodejs中文電子書

reply (plurk id strings⋯) -gt

stringsforEach (message) =gt

botreply(plruk id message)

接著把 run處理好就可以上線運作摟

run -gt

self =

options =

key processenvHUBOT PLURK KEY

secret processenvHUBOT PLURK SECRET

token processenvHUBOT PLURK TOKEN

token secret processenvHUBOT PLURK TOKEN SECRET

創建剛剛的 API

bot = new PlurkStreaming(options)

依照 Twitter 的 new RobotTextMessage 會沒有反應所以參考 hubot-minecraft 的方式

r = robotconstructor

處理噗浪河道訊息

doPlurk = (data)-gt

檢查是否為回噗

if dataresponse

datacontent raw = dataresponsecontent raw

datauser id = dataresponseuser id

確定有噗浪 ID 跟訊息

if dataplurk id and datacontent raw

selfreceive new rTextMessage(dataplurk id datacontent raw)

取得 Comet Server 完成開始第一次 Comet 連接

boton rdquochannel readyrdquo () -gt

botplurk selfdoPlurk

上一次 Comet 完成繼續 Polling

boton rdquonextPlurkrdquo ()-gt

botplurk selfdoPlurk

定時接受好友邀請

do botacceptFriends

bot = bot

83 建立 Robot跟 API 77

Nodejs中文電子書

終於完成 AdapterHubot專案裡面的 scripts資料夾內是互動部分不需要像 Adapter如此大費周章處理只新增檔案並且設計好對白之後就會回噗了我開發用的機器人在此大家可以去跟他玩玩[httpplurkcomelct9620 bot](httpplurkcomelct9620 bot)

84 原始資料提供

bull [製 作 一 個 Hubot 的 噗 浪 Adapter](http revo-skillfrosttw blog20120318create-a-hubot-plurk-adapter)

78 8 製作一個 Hubot的 Plurk Adapter

9

Nodejs好用工具介紹

91 logio

Real-time log monitoring in your browser

httplogioorg

nodejs (gt=0412 lt=0611)

npm config set unsafe-perm true

npm install -g --prefix=usrlocal logio

logio server start

httplocalhost8998

79

Nodejs中文電子書

80 9 Nodejs好用工具介紹

10

精選文章

101 Express

bull 使用 NodeJS + Express從 GETPOST Request取值 [邀稿中]

bull link2 [邀稿中]

bull link3 [邀稿中]

81

Nodejs中文電子書

82 10 精選文章

11

參考資源

111 Nodejs書籍

bull Programming Nodejs April 2012 (est) Travis Swicegood Pragmatic Bookshelf

bull e Little Book on CoffeeScript January 2012 Alex MacCaw OrsquoReilly Media

bull Node for Front-End Developers January 2012 Garann Means OrsquoReilly Media

bull Building Hypermedia APIs with HTML5 and Node November 2011 MikeAmundsen OrsquoReilly Media

bull Node Web Development August 2011 David Herron PacktPub

bull CoffeeScript Accelerated JavaScript Development July 2011 Trevor BurnhamPragmatic Bookshelf

bull Getting Started with GEO CouchDB and Nodejs July 2011 Mick omp-son OrsquoReilly Media

bull What Is Node July 2011 Brett McLaughlin OrsquoReilly Media

bull Node Up and Running May 2011 Tom Hughes-Croucher Mike WilsonOrsquoReilly Media

83

Nodejs中文電子書

bull Hands-on Nodejs e Nodejs introduction and API reference May 2011 Pe-dro Teixeira LeanPub

bull e Node Beginner Book Manuel Kiessling

bull Mastering Nodejs visionmedia

112 Nodejs影音教學

bull e Node Sessions e Best of OSCON 2011 August 2011 OrsquoReilly Media(Video)

bull Tom Hughes-Croucher on Node March 2011 OrsquoReilly Media (Video)

113 Nodejs教學網站

bull Node Tuts

114 Nodejs課程

bull Introduction To Nodejs Van Nguyen CodeLesson

84 11 參考資源

Page 13: Nodejs Wiki

1

Nodejs簡介

Nodejs是一個高效能易擴充的網站應用程式開發框架 (Web Applica-tion Framework)它誕生的原因是為了讓開發者能夠更容易開發高延展性的網路服務不需要經過太多複雜的調校效能調整及程式修改就能

滿足網路服務在不同發展階段對效能的要求

Ryan Dahl是NodeJS的催生者目前任職於 Joyent (主機託管服務公司)他開發 NodeJS的目的就是希望能解決 Apache在連線數量過高時緩衝區 (buffer)和系統資源會很快被耗盡的問題希望能建立一個新的開發框架以解決這個問題因此嘗試使用效能十分優秀的 V8 JavaScript Engine讓網站開發人員熟悉的 JavaScript程式語言也能應用於後端服務程式的開發並且具有出色的執行效能

JavaScript是功能強大的物件導向程式語言但是在 JavaScript的官方規格中主要是定義網頁 (以瀏覽器為基礎) 應用程式需要的應用程式介面(API)對 JavaScript程式的應用範圍有所侷限為使 JavaScript能夠在更多用途發展CommonJS規範一組標準函式庫 (standard library)使 JavaScript的應用範圍能夠和 RubyPython及 Java等語言同樣豐富撰寫 NodeJS的JavaScript程式碼符合 CommonJS規範可以使用 CommonJS API為基礎開發程式並且在不同的 CommonJS兼容 (compliant) JavaScript執行環境中程式碼具有可攜性

7

Nodejs中文電子書

瀏覽器的 JavaScript與實現 CommonJS規範的 NodeJS有何不同呢瀏覽器的 JavaScript 提供 XMLHttpRequest 讓程式可以和網頁伺服器建立資料傳輸連線但這通常只能適用於網站開發的需求因為我們只能用

XMLHttpRequest與網頁伺服器通訊卻無法利用它建立其他類型如 Telnet FTP NTP的伺服器通訊如果我們想開發網路服務程式例如 SMTP電子郵件伺服器就必須使用 Sockets建立 TCP (某些服務則用 UDP)監聽及連線其他程式語言如 PHPJavaPythonPerl及 Ruby等在標準開發環境中皆有提供 Sockets API而瀏覽器的 JavaScript基於安全及貼近網站設計需求的考量下並未將 Sockets列入標準函式庫之中CommonJS的規範填補這種基礎函式庫功能的空缺遵循 CommonJS規範的 NodeJS可以直接使用 Sockets API建立各種網路服務程式

JavaScript語言本身支援 Lambda的特性因此一個匿名函式 (anonymousfunction)可以被儲存成一個變數並當作參數傳遞給另一個函式

var proc1 = function(op x) return op(x)

var op1 = function(x) return x+1 var op2 = function(x) return xx

proc1(op1 3) result is 4 proc1(op2 5) result is 25

另一個 JavaScript開發者必須掌握的語言特性稱為 Closure

NodeJS符合 CommonJS的規範使得 Callback方式易於實現也能夠讓更多同好基於 JavaScript開發符合 NodeJS的外掛模組 (Module)

回想以前要寫一個能夠同時容納上百人的上線的網路服務需要花費

多大的苦工可能 10人多就需要經過一次程式調整而 NodeJS就是為了解決這個困境NodeJS因此誕生它是一種利用 V8 Javascript編譯器所開發的產品利用 V8編譯器的高效能與 Javascript的程式開發特性所產生的網路程式

開發人員所編寫出來的 Javascript腳本程式怎麼可能會比其他語言寫出來的網路程式還要快上許多呢以前的網路程式原理是將使用者每次的

連線 (connection)都開啟一個執行緒 (thread)當連線爆增的時候將會快速耗盡系統效能並且容易產生阻塞 (block)的發生

8 1 Nodejs簡介

Nodejs中文電子書

NodeJS對於資源的調校有所不同當程式接收到一筆連線 (connection)會通知作業系統透過 epoll kqueue devpoll或 select將連線保留並且放入heap中配置先讓連線進入休眠 (Sleep)狀態當系統通知時才會觸發連線的 callback這種處理連線方式只會佔用掉記憶體並不會使用到 CPU資源另外因為採用 Javascript語言的特性每個 request都會有一個 callback如此可以避免發生 Block的狀況發生

基於 Callback特性目前NodeJS大多應用於 Comet(long pulling) RequestServer或者是高連線數量的網路服務上目前也有許多公司將 NodeJS設為內部核心網路服務之一在 NodeJS也提供了外掛管理 (Node packagemanagement)讓愛好 NodeJS輕易開發更多有趣的服務外掛並且提供到 npm讓全世界使用者快速安裝使用

本書最後執行測試版本為 nodejs v068相關 API文件可查詢 lsquohttpnodejsorg lthttpnodejsorggtlsquo本書所有範例均可於 linux windows上執行如遇到任何問題歡迎至 lsquohttpnodejstw lthttpnodejstwgtlsquo詢問對於nodejs相關問題

9

Nodejs中文電子書

10 1 Nodejs簡介

2

JavaScript與 NodeJS

其實使用 JavaScript 在網頁端與伺服器端的差距並不大但是為了使NodeJS可以發揮他最強大的能力有一些知識還是必要的所以還是針對這些主題介紹一下其中 Event LoopScope以及 Callback其實是比較需要了解的基本知識cpscurrying ow control是更進階的技巧與應用

21 Event Loop

可能很多人在寫 Javascript 時並不知道他是怎麼被執行的這個時候可以參考一下 jQuery作者 John Resig一篇好文章介紹事件及 timer怎麼在瀏覽器中執行How JavaScript Timers Work通常在網頁中所有的Javascript執行完畢後(這部份全部都在 global scope跑除非執行函數)接下來就是如 John Resig解釋的這樣所有的事件處理函數以及 timer執行的函數會排在一個 queue結構中利用一個無窮迴圈不斷從 queue中取出函數來執行這個就是 event loop

(除了 John Resig的那篇文章Nicholas C Zakas的 ldquoProfessional Javascriptfor Web Developer 2nd editionrdquo 有一個試閱本httpyuiblogcomassetspdfzakas-projs-2ed-ch18pdf598頁剛好也有簡短的說明)

所以在 Javascript中雖然有非同步但是他並不是使用執行緒所有

11

Nodejs中文電子書

的事件或是非同步執行的函數都是在同一個執行緒中利用 event loop的方式在執行至於一些比較慢的動作例如 IO網頁 render re ow等實際動作會在其他執行緒跑等到有結果時才利用事件來觸發處理函數來處理

這樣的模型有幾個好處沒有執行緒的額外成本所以反應速度很快不會

有任何程式同時用到同一個變數不必考慮 lock也不會產生 dead lock所以程式撰寫很簡單但是也有一些潛在問題任一個函數執行時間較長都

會讓其他函數更慢執行(因為一個跑完才會跑另一個)在多核心硬體普遍

的現在無法用單一的應用程式 instance發揮所有的硬體能力用 NodeJS撰寫伺服器程式碰到的也是一樣的狀況要讓系統發揮 event loop的效能就要盡量利用事件的方式來組織程式架構另外對於一些有可能較為耗

時的操作可以考慮使用 processnextTick函數來讓他以非同步的方式執行避免在同一個函數中執行太久擋住所有函數的執行

如果想要測試 event loop怎樣在「瀏覽器」中運行可以在函數中呼叫alert()這樣會讓所有 Javascript的執行停下來尤其會干擾所有使用 timer的函數執行有一個簡單的例子這是一個會依照設定的時間間隔嚴格執

行動作的動畫如果時間過了就會跳過要執行的動作點按圖片以後人

物會快速旋轉但是在旋轉執行完畢前按下「delay」按鈕讓 alert訊息等久一點接下來的動畫就完全不會出現了

22 Scope與 Closure

要快速理解 JavaScript的 Scope(變數作用範圍)原理只要記住他是Lexical Scope就差不多了簡單地說變數作用範圍是依照程式定義時(或者叫做程式文本)的上下文決定而不是執行時的上下文決定

為了維護程式執行時所依賴的變數即使執行時程式運行在原本的

scope之外他的變數作用範圍仍然維持不變這時程式依賴的自由變數(定義時不是 local的而是在上一層 scope定義的變數)一樣可以使用就好像被關閉起來所以叫做 Closure用程式看比較好懂

function outter(arg1)

arg1 及 free_variable1 對 inner 函數來說都是自由變數

var free_variable1 = 3

return function inner(arg2)

12 2 JavaScript與 NodeJS

Nodejs中文電子書

var local_variable1 =2arg2 及 local_variable1 對 inner 函數來說都是本地變數

return arg1 + arg2 + free_variable + local_variable1

var a = outter(1)變數 a就是 outter函數執行後返回的 inner函數 var b =a(4)執行 inner函數執行時上下文已經在 outter函數之外但是仍然能正常執行而且可以使用定義在 outter函數裡面的 arg1及 free variable1變數 consolelog(b)結果 10

在 Javascript中scope最主要的單位是函數(另外有 global及 eval)所以有可能製造出 closure的狀況通常在形式上都是有巢狀的函數定義而且內側的函數使用到定義在外側函數裡面的變數

Closure有可能會造成記憶體洩漏主要是因為被參考的變數無法被垃圾收集機制處理造成佔用的資源無法釋放所以使用上必須考慮清楚

不要造成意外的記憶體洩漏(在上面的例子中如果 a一直未執行使用到的記憶體就不會被釋放)

跟透過函數的參數把變數傳給函數比較起來Javascript Engine會比較難對 Closure進行最佳化如果有效能上的考量這一點也需要注意

23 Callback

要介紹 Callback之前要先提到 JavaScript的特色

JavaScript是一種函數式語言(functional language)所有 Javascript語言內的函數都是高階函數 (higher order function這是數學名詞計算機用語好像是 rst class function意指函數使用沒有任何限制與其他物件一樣)也就是說函數可以作為函數的參數傳給函數也可以當作函數的返回值這個特性讓 Javascript的函數使用上非常有彈性而且功能強大

callback在形式上其實就是把函數傳給函數然後在適當的時機呼叫傳入的函數Javascript使用的事件系統通常就是使用這種形式NodeJS中有一個物件叫做 EventEmitter這是 NodeJS事件處理的核心物件所有會使用事件處理的函數都會「繼承」這個物件(這裡說的繼承實

作上應該像是 mixin)他的使用很簡單可以使用物件on(事件名稱callback

23 Callback 13

Nodejs中文電子書

函數) 或是物件addListener(事件名稱callback 函數) 把你想要處理事件的函數傳入在物件中可以使用物件emit(事件名稱 參數) 呼叫傳入的callback函數這是 Observer Pattern的簡單實作而且跟在網頁中使用 DOM的 addEventListener使用上很類似也很容易上手不過 NodeJS是大量使用非同步方式執行的應用所以程式邏輯幾乎都是寫在 callback函數中當邏輯比較複雜時大量的 callback會讓程式看起來很複雜也比較難單元測試舉例來說

var p client = new Db(lsquointegration tests 20rsquo new Server(ldquo127001rdquo 27017) lsquopkrsquoCustomPKFactory) p clientopen(function(err p client)

p clientdropDatabase(function(err done)

p clientcreateCollection(lsquotest custom keyrsquo function(err collection)

collectioninsert(lsquoarsquo1 function(err docs)

collection nd(lsquo idrsquonew ObjectID(ldquoaaaaaaaaaaaardquo) function(err cursor)

cursortoArray(function(err items) testassertEquals(1 itemslength) p clientclose()

)

)

)

)

)

)

這是在網路上看到的一段操作 mongodb的程式碼為了循序操作所以必須在一個 callback裡面呼叫下一個動作要使用的函數這個函數裡面還是會使用 callback最後就形成一個非常深的巢狀

這樣的程式碼會比較難進行單元測試有一個簡單的解決方式是

盡量不要使用匿名函數來當作 callback或是 event handler透過這樣的方式

14 2 JavaScript與 NodeJS

Nodejs中文電子書

就可以對各個 handler做單元測試了例如

var http = require(lsquohttprsquo) var tools =

cookieParser function(request response) if(requestheaders[rsquoCookiersquo]) do parsing

var server = httpcreateServer(function(request response)

thisemit(lsquoinitrsquo request response)

) serveron(lsquoinitrsquo toolscookieParser) serverlisten(8080 lsquo127001rsquo)

更進一步可以把 tools改成外部 module例如叫做 toolsjs

moduleexports = cookieParser function(request response) if(requestheaders[rsquoCookiersquo]) do parsing

然後把程式改成

var http = require(lsquohttprsquo)

var server = httpcreateServer(function(request response) thisemit(lsquoinitrsquo re-quest response)

) serveron(lsquoinitrsquo require(lsquo toolsrsquo)cookieParser) serverlisten(8080lsquo127001rsquo)

這樣就可以單元測試 cookieParser了例如使用 nodeunit時可以這樣寫

var testCase = require(lsquonodeunitrsquo)testCase moduleexports = testCase(

ldquosetUprdquo function(cb) thisrequest = headers Cookielsquoname1val1 name2val2rsquo thisresponse = thisresult= name1rsquoval1rsquoname2rsquoval2rsquo

cb()

ldquotearDownrdquo function(cb)

cb()

23 Callback 15

Nodejs中文電子書

ldquonormal caserdquo function(test)

testexpect(1) var obj = require(lsquotoolsrsquo)cookieParser(thisrequest thisresponse)testdeepEqual(obj thisresult) testdone()

)

善於利用模組可以讓程式更好維護與測試

24 CPS(Continuation-Passing Style)

cps 是 callback 使用上的特例形式上就是在函數最後呼叫 callback這樣就好像把函數執行後把結果交給 callback 繼續運行所以稱作continuation-passing style利用 cps可以在非同步執行的情況下透過傳給 callback的這個 cps callback來獲知 callback執行完畢或是取得執行結果例如

lthtmlgt ltbodygt ltdiv id=rdquopanelrdquo style=rdquovisibilityhiddenrdquogtlt divgtlt bodygt lt htmlgt ltscriptgt var request = new XMLHttpRequest() re-questopen(lsquoGETrsquo lsquotest749txt timestamp=rsquo+new Date()getTime() true) re-questaddEventListener(lsquoreadystatechangersquo function(next)

return function() if(thisreadyState===4ampampthisstatus===200) next(thisresponseText)lt== 傳入的 cps callback 在動作完成時執行並取得結果進一步處理

(function(str)lt==這個匿名函數就是 cps callback

documentgetElementById(lsquopanelrsquo)innerHTML=str docu-mentgetElementById(lsquopanelrsquo)stylevisibility = lsquovisiblersquo

) false) requestsend() ltscriptgt

進一步的應用也可以參考 2-6流程控制

16 2 JavaScript與 NodeJS

Nodejs中文電子書

25 函數返回函數與 Currying

前面的 cps範例裡面使用了函數返回函數這是為了把 cps callback傳遞給 onreadystatechange事件處理函數的方法(因為這個事件處理函數並沒有設計好會傳送接收這樣的參數)實際會執行的事件處理函數其實是內層返回的那個函數之外包覆的這個函數主要是為了利用 Closure把 next傳給內層的事件處理函數這個方法更常使用的地方是為了解決一些

scope問題例如

ltscriptgt var accu=0count=10 for(var i=0 iltcount i++)

setTimeout(

function() countndash accu+=i if(countlt=0)

consolelog(accu)

50)

ltscriptgt

最後得出的結果會是 100而不是想像中的 45這是因為等到 setTime-out指定的函數執行時變數 i已經變成 10而離開迴圈了要解決這個問題就需要透過 Closure來保存變數 i

ltscriptgt var accu=0count=10 for(var i=0 iltcount i++)

setTimeout(

function(i) return function() countndash

accu+=i if(countlt=0)

consolelog(accu)

(i)

50)

25 函數返回函數與 Currying 17

Nodejs中文電子書

淺藍色底色的部份是跟上面例子不一樣的地方 ltscriptgt

函數返回函數的另外一個用途是可以暫緩函數執行例如

function add(m n) return m+n

var a = add(20 10) consolelog(a)

add這個函數必須同時輸入兩個參數才有辦法執行如果我希望這個函數可以先給它一個參數等一些處理過後再給一個參數然後得到結

果就必須用函數返回函數的方式做修改

function add(m)

return function(n) return m+n

var wait another arg = add(20)先給一個參數 var a = function(arr)

var ret=0 for(var i=0iltarrlengthi++) ret+=arr[i] return ret

([1234])計算一下另一個參數 var b = wait another arg(a)然後再繼續執行 consolelog(b)

像這樣利用函數返回函數使得原本接受多個參數的函數可以一次

接受一個參數直到參數接收完成才執行得到結果的方式有一個學名就

叫做Currying

綜合以上許多奇技淫巧就可以透過用函數來處理函數的方式調整

程式流程接下來看看

26 流程控制

(以 sync方式使用 async函數避開巢狀 callback循序呼叫 async callback等奇技淫巧)

建議參考

bull httphowtonodeorgcontrol- ow

bull httphowtonodeorgcontrol- ow-part-ii

18 2 JavaScript與 NodeJS

Nodejs中文電子書

bull httphowtonodeorgcontrol- ow-part-iii

bull httpblogmixunet20110202essential-node-js-patterns-and-snippets

這幾篇都是非常經典的 NodeJSJavascript流程控制好文章(阿mixu是在介紹一些 pattern時提到這方面的主題)不過我還是用幾個簡單的程式介紹一下做法跟概念

並發與等待

下面的程式參考了 mixu文章中的做法

var wait = function(callbacks done) consolelog(lsquowait startrsquo) var counter = call-backslength var results = [] var next = function(result) 接收函數執行結果並判斷是否結束執行 resultspush(result) if(ndashcounter == 0) done(results)如果結束執行就把所有執行結果傳給指定的 callback處理 for(var i = 0 i lt callbackslength i++) 依次呼叫所有要執行的函數 callbacks[i](next) consolelog(lsquowait endrsquo)

wait( [ function(next) setTimeout(function() consolelog(lsquodone arsquo) var result =500 next(result) 500) function(next) setTimeout(function() con-solelog(lsquodone brsquo) var result = 1000 next(result) 1000) function(next)setTimeout(function() consolelog(lsquodone crsquo) var result = 1500 next(1500) 1500) ] function(results) var ret = 0 i=0 for( iltresultslength i++) ret+= results[i] consolelog(lsquodone all result lsquo+ret)

)

執行結果wait start wait end done a done b done c done all result 3000

可以看出來其實 wait並不是真的等到所有函數執行完才結束執行而是在所有傳給他的函數執行完畢後(不論同步非同步)才執行處理結

果的函數(也就是 done())

不過這樣的寫法還不夠實用因為沒辦法實際讓函數可以等待執行

完畢又能當作事件處理函數來實際使用上面參考到的 Tim Caswell的文

26 流程控制 19

Nodejs中文電子書

章裡面有一種解法不過還需要額外包裝(在他的例子中)NodeJS核心的 fs物件把一些函數(例如 readFile)用 Currying處理類似像這樣

var fs = require(lsquofsrsquo) var readFile = function(path)

return function(callback errback)

fsreadFile(path function(err data)

if(err) errback()

else callback(data)

)

其他部份可以參考 Tim Caswell的文章他的 Doparallel跟上面的 wait差不多意思這裡只提示一下他沒說到的地方

另外一種做法是去修飾一下 callback當他作為事件處理函數執行後再用 cps的方式取得結果

ltscriptgt function Wait(fns done)

var count = 0 var results = [] thisgetCallback = function(index)

count++ return (function(waitback)

return function() var i=0args=[]for(iltargumentslengthi++)

argspush(arguments[i])

argspush(waitback) fns[index]apply(thisargs)

)(function(result) resultspush(result) if(ndashcount == 0)

20 2 JavaScript與 NodeJS

Nodejs中文電子書

done(results)

)

var a = new Wait(

[ function(waitback) consolelog(lsquodone arsquo) var result = 500 wait-back(result) function(waitback) consolelog(lsquodone brsquo) var result =1000 waitback(result) function(waitback) consolelog(lsquodone crsquo) varresult = 1500 waitback(result) ] function(results) var ret = 0 i=0for( iltresultslength i++) ret += results[i] consolelog(lsquodone allresult lsquo+ret)

) var callbacks = [agetCallback(0)agetCallback(1)agetCallback(0)agetCallback(2)]一次取出要使用的 callbacks避免結果提早送出 setTimeout(callbacks[0]500) setTimeout(callbacks[1] 1000) setTimeout(callbacks[2] 1500) setTime-out(callbacks[3] 2000) 當所有取出的 callbacks執行完畢就呼叫 done()來處理結果 ltscriptgt

執行結果

done a done b done a done c done all result 3500

上面只是一些小實驗更成熟的作品是 Tim Caswell 的 stephttpsgithubcomcreationixstep

如果希望真正使用同步的方式寫非同步則需要使用 Promisejs這一類的 library來轉換非同步函數不過他結構比較複雜 XD(見仁見智不過有些人認為 Promise有點過頭了)httpblogsmsdncombrbucktonarchive20110815promise-js-2-0-promise-framework-for-javascriptaspx

如果想不透過其他 Library做轉換又能直接用同步方式執行非同步函數大概就要使用一些需要額外 compile原始程式碼的方法了例如 BrunoJouhier的 streamlinejshttpsgithubcomSagestreamlinejs

26 流程控制 21

Nodejs中文電子書

循序執行

循序執行可以協助把非常深的巢狀 callback結構攤平例如用這樣的簡單模組來做(serialjs)

moduleexports = function(funs) var c = 0 if(isArrayOfFunctions(funs))

throw(lsquoArgument type was not matched Should be array of func-tionsrsquo)

return function()

var args = Arrayprototypeslicecall(arguments 0) if((cgt=funslength))

c++ return funs[c-1]apply(this args)

function isArrayOfFunctions(f ) if(typeof f == lsquoobjectrsquo) return false if(flength)return false if(fconcat) return false if(fsplice) return false var i = 0 for(iltflength i++)

if(typeof f[i] == lsquofunctionrsquo) return false

return true

簡單的測試範例(testSerialjs)使用 fs 模組確定某個 path 是檔案然後讀取印出檔案內容這樣會用到兩層的 callback所以測試中有使用serial的版本與 nested callbacks的版本做對照

var serial = require(lsquoserialrsquo) fs = require(lsquofsrsquo) path = lsquodclientjsrsquo cb = serial([ func-tion(err data)

if(err)

if(dataisFile) fsreadFile(path cb)

22 2 JavaScript與 NodeJS

Nodejs中文電子書

else consolelog(err)

function(err data)

if(err) consolelog(lsquo[ attened by searial]rsquo) con-solelog(datatoString(lsquoutf8rsquo))

else consolelog(err)

]) fsstat(path cb)

fsstat(path function(err data) 第一層 callback if(err)

if(dataisFile)

fsreadFile(path function(err data) 第二層 callback if(err)

consolelog(lsquo[nested callbacks]rsquo) con-solelog(datatoString(lsquoutf8rsquo))

else consolelog(err)

)

else consolelog(err)

)

關鍵在於這些 callback的執行是有順序性的所以利用 serial返回的一個函數 cb來取代這些 callback然後在 cb中控制每次會循序呼叫的函數

26 流程控制 23

Nodejs中文電子書

就可以把巢狀的 callback攤平成循序的 function陣列(就是傳給 serial函數的參數)

測試中的dclientjs 是一個簡單的 dnode 測試程式放在跟 testSerialjs同一個目錄

var dnode = require(lsquodnodersquo)

dnodeconnect(8000 lsquolocalhostrsquo function(remote)

remoterestart(function(str) consolelog(str) processexit()

)

)

執行測試程式後出現結果

[ attened by searial] var dnode = require(lsquodnodersquo)

dnodeconnect(8000 lsquolocalhostrsquo function(remote)

remoterestart(function(str) consolelog(str) processexit()

)

)

[nested callbacks] var dnode = require(lsquodnodersquo)

dnodeconnect(8000 lsquolocalhostrsquo function(remote)

remoterestart(function(str) consolelog(str) processexit()

)

)

對照起來看兩種寫法的結果其實是一樣的但是利用 serialjs巢狀的 callback結構就會消失

不過這樣也只限於順序單純的狀況如果函數執行的順序比較複雜

(不只是一直線)還是需要用功能更完整的流程控制模組比較好例如

httpsgithubcomcaolanasync

24 2 JavaScript與 NodeJS

3

Nodejs安裝與設定

本篇將講解如何在各個不同 OS建立 NodeJS環境目前 NodeJS 048版本環境架設方式需依賴 Linux指令才可編譯完成當然在不同作業系統中也已經有 NodeJS package可以直接使用指令快速架設以下各不同作業系統解說如何安裝 NodeJS

31 Ubuntu Linux

更新推薦使用 nvm

git clone gitgithubcomcreationixnvmgit ~nvm

echo rdquo ~nvmnvmshrdquo gtgt ~bashrc

nvm install v0614

nvm alias default v0614

以 上 可 參 考 http dreamerslabcom blog tw how-to-setup-a-node-js-development-environment-on-ubuntu-11-04

使用 APT套件管理工具是常見的方法以下是使用社群提供的 PPA安裝方式

sudo apt-get install python-software-properties

sudo add-apt-repository ppachris-leanodejs-devel

25

Nodejs中文電子書

sudo apt-get update

sudo apt-get install nodejs

32 Other Linux

Linux很適合作為 NodeJS的伺服器作業系統及開發環境安裝前請先確認以下套件已正確安裝

bull curl (wget)用來下載檔案的工具

bull git先進的版本控制工具

bull g++ GNU C++軟體編譯工具

bull make GNU軟體專案建置工具

安裝指令如下如設有權限問題請在指令前面加上 sudo

git clone httpsgithubcomjoyentnodegit

cd node

git checkout v067

configure

make

sudo make install

接著測試 nodeJS是否正常執行

node --version

出現版本訊息即表示安裝成功

33 Windows

nodeJS 在 v060 版本之後開始正式支援 windows native直接使用nodeexe 就可以執行程式支援性完全與 linux 相同更棒的部份就是不需經過編譯經過下載之後簡單設定完成立即開發 node程式

26 3 Nodejs安裝與設定

Nodejs中文電子書

下載 nodejs安裝檔案 lthttpnodejsorgdownloadgt

如此完成 windows native nodeexe安裝接著可以進入 command line執行測試在 command line輸入rdquonoderdquo會出現 nodejs版本訊息畫面表示安裝完成

33 Windows 27

Nodejs中文電子書

28 3 Nodejs安裝與設定

4

Nodejs基礎

前篇文章已經由介紹安裝至設定都有完整介紹nodeJS 內部除了javascript常用的函式 (function)物件 (object)之外也有許多不同的自訂物件nodeJS預設建立這些物件為核心物件是為了要讓開發流程更為這些資料在官方文件已經具有許多具體說明接下來將會介紹在開發 nodeJS程式時常見的物件特性與使用方法

41 nodejs http伺服器建立

在 lsquonodejs官方網站 lthttpnodejsorggtlsquo裡面有舉一個最簡單的 HTTP伺服器建立一開始初步就是建立一個伺服器平台讓 nodejs可以與瀏覽器互相行為每種語言一開始的程式建立都是以 Hello world開始最初也從 Hello world帶各位進入 nodejs的世界

輸入以下程式碼儲存檔案為 node basic http hello worldjs

var server

ip = rdquo127001rdquo

port = 1337

http = require(rsquohttprsquo)

server = httpcreateServer(function (req res)

29

Nodejs中文電子書

reswriteHead(200 rsquoContent-Typersquo rsquotextplainrsquo)

resend(rsquoHello Worldnrsquo)

)

serverlisten(port ip)

consolelog(rdquoServer running at httprdquo + ip + rdquordquo + port)

程式碼解講一開始需要有幾個基本的變數

bull ip 機器本身的 ip位置因為使用本地端因此設定為 127001

bull port 需要開通的阜號通常設定為 http port 80因範例不希望與基本 port相衝隨意設定為 1337

在 nodejs 的程式中有許多預設的模組可以使用因此需要使用require方法將模組引入在這邊我們需要使用 http這個模組因此將 http載入Http模組裡面內建有許多方法可以使用這邊採用 createServer創建一個基本的 http伺服器再將 http伺服器給予一個 server變數裡面的回呼函式 (call back function)可以載入 http伺服器的資料與回應方法 (requestresponse)在程式裡面就可以看到我們直接回應給瀏覽器端所需的 Header回應內容

reswriteHead(200 rsquoContent-Typersquo rsquotextplainrsquo)

resend(rsquoHello Worldnrsquo)

Http伺服器需要設定 port ip在最後需要設定 Http監聽需要使用到listen事件監聽所有 Http伺服器行為

httplisten(port ip)

所有事情都完成之後需要確認伺服器正確執行因此使用 console在javascript裡就有這個原生物件console所印出的資料都會顯示於 nodejs伺服器頁面這邊印出的資料並不會傳送到使用者頁面上之後許多除壞

(debug)都會用到 console物件

consolelog(rdquoServer running at httprdquo + ip + rdquordquo + port)

30 4 Nodejs基礎

Nodejs中文電子書

42 nodejs http路徑建立

前面已經介紹如何建立一個簡單的 http伺服器接下來本章節將會介紹如何處理伺服器路徑 (rout)問題在 http的協定下所有從瀏覽器發出的要求 (request)都需要經過處理路徑上的建立也是如此

路徑就是指伺服器 ip位置或者是網域名稱之後對於伺服器給予的要求修改剛才的 hello world檔案修改如下

server = httpcreateServer(function (req res)

consolelog(requrl)

reswriteHead(200 rsquoContent-Typersquo rsquotextplainrsquo)

resend(rsquohello worldnrsquo)

)

重新啟動 nodejs程式後在瀏覽器端測試一下路徑行為結果如下圖

當在瀏覽器輸入 http1270011337test 在伺服器端會收到兩個要求一個是我們輸入的test要求另外一個則是faviconicotest的路徑要求http伺服器本身需要經過程式設定才有辦法回應給瀏覽器端所需要的回應在伺服器中所有的路徑要求都是需要被解析才有辦法取得資料從

42 nodejs http路徑建立 31

Nodejs中文電子書

上面解說可以了解到在 nodejs當中所有的路徑都需要經過設定未經過設定的路由會讓瀏覽器無法取得任何資料導致錯誤頁面的發生底下將會解

說如何設定路由同時避免發生錯誤情形先前 nodejs程式需要增加一些修改才能讓使用者透過瀏覽器在不同路徑時有不同的結果根據剛才

的程式做如下的修改

var server

ip = rdquo127001rdquo

port = 1337

http = require(rsquohttprsquo)

url = require(rsquourlrsquo)

path

server = httpcreateServer(function (req res)

path = urlparse(requrl)

reswriteHead(200 rsquoContent-Typersquo rsquotextplainrsquo)

switch (pathpathname)

case rdquoindexrdquo

resend(rsquoI am indexnrsquo)

break

case rdquotestrdquo

resend(rsquothis is test pagenrsquo)

break

default

resend(rsquodefault pagenrsquo)

break

)

serverlisten(port ip)

consolelog(rdquoServer running at httprdquo + ip + rdquordquo + port)

程式做了片段的修改首先載入 url模組另外增加一個 path變數url模組就跟如同他的命名一般專門處理 url字串處理裡面提供了許多方法來解決路徑上的問題因為從瀏覽器發出的要求路徑可能會帶有多種需求

或者 GET參數組合等因此我們需要將路徑單純化取用路徑部分的資料即可例如使用者可能會送出 http1270011337testsend=1如果直接

32 4 Nodejs基礎

Nodejs中文電子書

信任 requrl就會收到結果為testsend=1所以需要透過 url模組的方法將路徑資料過濾

在這邊使用 urlparse的方法裡面帶入網址格式資料會回傳路徑資料為了後需方便使用將回傳的資料設定到 path變數當中在回傳的路徑資料裡面包含資訊如下圖

這邊只需要使用單純的路徑要求直接取用 pathpathname就可以達到我們的目的

最後要做路徑的判別在不同的路徑可以指定不同的輸出在範例中

有三個可能結果第一個從瀏覽器輸入index就會顯示 index結果test 就會呈現出 test頁面最後如果都不符合預期的輸入會直接顯示 default的頁面最後的預防可以讓瀏覽器不會出現非預期結果讓程式的可靠性提昇

底下為測試結果

42 nodejs http路徑建立 33

Nodejs中文電子書

43 nodejs檔案讀取

前面已經介紹如何使用路由(rount)做出不同的回應實際應用只有在瀏覽器只有輸出幾個文字資料總是不夠的在本章節中將介紹如何使

用檔案讀取輸出檔案資料讓使用者在前端瀏覽器也可以讀取到完整的

html css javascript檔案輸出

檔案管理最重要的部分就是 lsquoFile system lthttpnodejsorgdocslatestapifshtmlgtlsquo這個模組此模組可以針對檔案做管理監控讀取等行為裡面有許多預設的方法底下是檔案輸出的基本範例底下會有兩個檔案

第一個是靜態 html檔案

ltDOCTYPE htmlgt

lthtml xmlns=rdquohttpwwww3org1999xhtmlrdquo xmllang=rdquozh-CNrdquo lang=rdquozh-CNrdquogt

ltheadgt

ltmeta http-equiv=rdquoContent-Typerdquo content=rdquotexthtml charset=UTF-7rdquo gt

lttitlegtnodejs index html filelttitlegt

ltheadgt

ltbodygt

34 4 Nodejs基礎

Nodejs中文電子書

lth1gtnodejs index html filelth1gt

ltbodygt

lthtmlgt

另一個為 nodejs程式

var fs = require(rdquofsrdquo)

filename = rdquostaticindexhtmlrdquo

encode = rdquoutf8rdquo

fsreadFile(filename encode function(err file)

consolelog(file)

)

一開始直接載入 le system模組 載入名稱為 fs讀取檔案主要使

用的方法為 readFile裡面以三個參數路徑 ( le path) 編碼方式 (encoding)

回應函式 (callback)路徑必須要設定為靜態 html所在位置才能指定到正確的檔案靜態檔案的編碼方式也必須正確這邊使用靜態檔案的編

碼為 utf8如果編碼設定錯誤nodejs讀取出來檔案結果會使用 byte raw格式輸出如果錯誤編碼格式會導致輸出資料為 byte raw

回應函式中裡面會使用兩個變數error為錯誤資訊如果讀取的檔案不存在或者發生錯誤error數值會是 true如果成功讀取資料 error將會是 falsecontent則是檔案內容資料讀取後將會把資料全數丟到 content這個變數當中

最後程式的輸出結果畫面如下

43 nodejs檔案讀取 35

Nodejs中文電子書

44 nodejs http靜態檔案輸出

前面已經了解如何讀取本地端檔案接下來將配合 http伺服器路由讓每個路由都能夠輸出相對應的靜態 html檔案首先新增加幾個靜態 html檔案

ltDOCTYPE htmlgt

lthtml xmlns=rdquohttpwwww3org1999xhtmlrdquo xmllang=rdquozh-CNrdquo lang=rdquozh-CNrdquogt

ltheadgt

ltmeta http-equiv=rdquoContent-Typerdquo content=rdquotexthtml charset=UTF-7rdquo gt

lttitlegtnodejs index html filelttitlegt

ltheadgt

ltbodygt

lth1gtnodejs index html filelth1gt

ltbodygt

lthtmlgt

ltDOCTYPE htmlgt

lthtml xmlns=rdquohttpwwww3org1999xhtmlrdquo xmllang=rdquozh-CNrdquo lang=rdquozh-CNrdquogt

ltheadgt

ltmeta http-equiv=rdquoContent-Typerdquo content=rdquotexthtml charset=UTF-7rdquo gt

lttitlegtnodejs test html filelttitlegt

ltheadgt

ltbodygt

lth1gtnodejs test html filelth1gt

ltbodygt

lthtmlgt

ltDOCTYPE htmlgt

lthtml xmlns=rdquohttpwwww3org1999xhtmlrdquo xmllang=rdquozh-CNrdquo lang=rdquozh-CNrdquogt

ltheadgt

ltmeta http-equiv=rdquoContent-Typerdquo content=rdquotexthtml charset=UTF-7rdquo gt

lttitlegtnodejs static html filelttitlegt

ltheadgt

ltbodygt

lth1gtnodejs static html filelth1gt

ltbodygt

lthtmlgt

36 4 Nodejs基礎

Nodejs中文電子書

準備一個包含基本路由功能的 http伺服器

var server

ip = rdquo127001rdquo

port = 1337

http = require(rsquohttprsquo)

url = require(rsquourlrsquo)

server = httpcreateServer(function (req res)

var path = urlparse(requrl)

)

serverlisten(port ip)

consolelog(rdquoServer running at httprdquo + ip + rdquordquo + port)

加入 le system 模組使用 readFile 的功能將這一段程式放置於

createServer的回應函式中

fsreadFile(filePath encode function(err file)

)

readFile的回應函式裡面加入頁面輸出讓瀏覽器可以正確讀到檔案在這邊我們設定讀取的檔案為 html 靜態檔案所以 Content-type 設定為texthtml讀取到檔案的內容將會正確輸出成 html靜態檔案

fsreadFile(filePath encode function(err file)

reswriteHead(200 rsquoContent-Typersquo rsquotexthtmlrsquo)

reswrite(file)

resend()

)

到這邊為止基本的程式內容都已經完成剩下一些細節的調整首先

路徑上必須做調整目前的靜態檔案全部都放置於 static資料夾底下設定一個變數來記住資料夾位置

接著將瀏覽器發出要求路徑與資料夾組合讀取正確 html靜態檔案使用者有可能會輸入錯誤路徑所以在讀取檔案的時候要加入錯誤處理

同時回應 404伺服器無法正確回應的 http header格式

加入這些細節的修改一個基本的 http 靜態 html輸出伺服器就完成了完整程式碼如下

44 nodejs http靜態檔案輸出 37

Nodejs中文電子書

var server

ip = rdquo127001rdquo

port = 1337

http = require(rsquohttprsquo)

fs = require(rdquofsrdquo)

folderPath = rdquostaticrdquo

url = require(rsquourlrsquo)

path

filePath

encode = rdquoutf8rdquo

server = httpcreateServer(function (req res)

path = urlparse(requrl)

filePath = folderPath + pathpathname

fsreadFile(filePath encode function(err file)

if (err)

reswriteHead(404 rsquoContent-Typersquo rsquotextplainrsquo)

resend()

return

reswriteHead(200 rsquoContent-Typersquo rsquotextapplicationrsquo)

reswrite(file)

resend()

)

)

serverlisten(port ip)

consolelog(rdquoServer running at httprdquo + ip + rdquordquo + port)

45 nodejs http GET資料擷取

http伺服器中除了路由之外另一個最常使用的方法就是擷取 GET資料本單元將會介紹如何透過基本 http伺服器擷取瀏覽器傳來的要求擷取 GET資料

在 http協定中GET參數都是藉由 URL從瀏覽器發出要求送至伺服器

38 4 Nodejs基礎

Nodejs中文電子書

端基本的傳送網址格式可能如下

http127001testsend=1amptest=2

上面這段網址裡面的 GET參數就是 send而這個變數的數值就為 1如果想要在 http伺服器取得 GET資料需要在瀏覽器給予的要求 (request)做處理

首先需要載入 query string這個模組這個模組主要是用來將字串資料

過濾後轉換成javascript物件

qs = require(rsquoquerystringrsquo)

接著在第一階段利用 url模組過濾瀏覽器發出的 URL資料後將回應的物件裡面的 query這個變數是一個字串值資料過濾後如下

send=1amptest=2

透過 query string使用 parse這個方法將資料轉換成 javascript物件就表示 GET的資料已經被伺服器端正式擷取下來

path = urlparse(requrl)

parameter = qsparse(pathquery)

整個 nodejs http GET參數完整擷取程式碼如下

var server

ip = rdquo127001rdquo

port = 1337

http = require(rsquohttprsquo)

qs = require(rsquoquerystringrsquo)

url = require(rsquourlrsquo)

server = httpcreateServer(function (req res)

var path = urlparse(requrl)

parameter = qsparse(pathquery)

consoledir(parameter)

reswriteHead(200 rsquoContent-Typersquo rsquotextplainrsquo)

reswrite(rsquoBrowser test GET parameternrsquo)

resend()

)

45 nodejs http GET資料擷取 39

Nodejs中文電子書

serverlisten(port ip)

consolelog(rdquoServer running at httprdquo + ip + rdquordquo + port)

程式運作之後由瀏覽器輸入要求網址之後nodejs伺服器端回應資料為

Server running at http1270011337

send rsquo1rsquo test rsquo1rsquo

46 本章結語

前面所解說的部份一大部分主要是處理 http伺服器基本問題雖然在某些部分有牽扯到 http 伺服器基本運作原理主要還是希望可以藉由這些基本範例練習 nodejs練習回應函式與語法串接的特點習慣編寫javascript風格程式當然直接這樣開發 nodejs是非常辛苦的接下來在模組實戰開發的部份將會介紹特定的模組一步一步帶領各位從無到有進行

nodejs應用程式開發

40 4 Nodejs基礎

5

NPM套件管理工具

npm全名為 Node Package Manager是 Nodejs的套件(package)管理工具類似 Perl的 ppm或 PHP的 PEAR等安裝 npm後使用npm install

module name指令即可安裝新套件維護管理套件的工作會更加輕鬆

npm可以讓Nodejs的開發者直接利用擴充線上的套件庫(packagesregistry)加速軟體專案的開發npm提供很友善的搜尋功能可以快速找到安裝需要的套件當這些套件發行新版本時npm也可以協助開發者自動更新這些套件

npm不僅可用於安裝新的套件它也支援搜尋列出已安裝模組及更新的功能

51 安裝 NPM

Nodejs在 063版本開始內建 npm讀者安裝的版本若是此版本或更新的版本就可以略過以下安裝說明

若要檢查 npm是否正確安裝可以使用以下的指令

npm -v

41

Nodejs中文電子書

執行結果說明

若 npm正確安裝執行 npm -v將會看到類似 110-2的版本訊息

若讀者安裝的 Nodejs版本比較舊或是有興趣嘗試自己動手安裝 npm工具則可以參考以下的說明

安裝於Windows系統

Nodejs for Windows 於 062 版開始內建 npm使用 nodejsorg 官方提供的安裝程式不需要進一步的設定就可以立即使用 npm指令對於Windows的開發者來說大幅降低環境設定的問題與門檻

除了使用 Nodejs內建的 npm讀者也可以從 npm官方提供的以下網址

httpnpmjsorgdist

這是由 npm提供的 Fancy Windows Install版本請下載壓縮檔(例如npm-110-3zip)並將壓縮檔內容解壓縮至 Nodejs的安裝路徑(例如CProgram Filesnodejs)

解壓縮後在 Nodejs的安裝路徑下應該有以下的檔案及資料夾

bull npmcmd(檔案)

bull node modules(資料夾)

安裝於 Linux系統

Ubuntu Linux的使用者可以加入 NPM Unoffcial PPA這個 repository即可使用 apt-get完成 npm安裝

42 5 NPM套件管理工具

Nodejs中文電子書

Ubuntu Linux使用 apt-get安裝 npm

sudo apt-get install python-software-properties

sudo add-apt-repository ppagias-kay-leenpm

sudo apt-get update

sudo apt-get install npm

npm官方提供的安裝程式 installsh可以適用於大多數的 Linux系統使用這個安裝程式請先確認

1 系統已安裝 curl工具(請使用 curl --version查看版本訊息)

2 已安裝 Nodejs並且 PATH正確設置

3 Nodejs的版本必須大於 04x

以下為 npm提供的安裝指令

curl httpnpmjsorginstallsh | sh

安裝成功會看到如下訊息

installsh安裝成功的訊息

npm10105 homeUSERNAMElocalnodelibnode_modulesnpm

It worked

安裝於Mac OS X

建議採用與 Nodejs相同的方式進行 npm的安裝例如使用MacPorts安裝 Nodejs就同樣使用MacPorts安裝 npm這樣對日後的維護才會更方便容易

使用 MacPorts安裝 npm是本書比較建議的方式它可以讓 npm的安裝移除及更新工作自動化將會幫助開發者節省寶貴時間

51 安裝 NPM 43

Nodejs中文電子書

安裝MacPorts的提示

在MacPorts網站可以取得 OS X系統版本對應的安裝程式(例如 106或 107)httpwwwmacportsorg安裝過程會詢問系統管理者密碼使用預設的選項完成安裝即可安

裝MacPorts之後在終端機執行 port -v將會看到MacPorts的版本訊息

安裝 npm之前先更新MacPorts的套件清單以確保安裝的 npm是最新版本

sudo port -d selfupdate

接著安裝 npm

sudo port install npm

若讀者的 Nodejs並非使用MacPorts安裝則不建議使用MacPorts安裝npm因為 MacPorts會自動檢查並安裝相依套件而 npm相依 nodejs所以MacPorts也會一併將 nodejs套件安裝造成先前讀者使用其它方式安裝的 nodejs被覆蓋

讀者可以先使用 MacPorts安裝 curl(sudo port install curl)

再參考 Linux的 installsh安裝方式即可使用 npm官方提供的安裝程式

NPM安裝後測試

npm是指令列工具(command-line tool)使用時請先打開系統的文字終端機工具

測試 npm安裝與設定是否正確請輸入指令如下

npm -v

或是

npm --version

如果 npm已經正確安裝設定就會顯示版本訊息

44 5 NPM套件管理工具

Nodejs中文電子書

執行結果(範例)

110-2

52 使用 NPM安裝套件

npm目前擁有超過 6000種套件(packages)可以在 npm registry使用關鍵字搜尋套件

httpsearchnpmjsorg

舉例來說在關鍵字欄位輸入「coffee-script」下方的清單就會自動列出包含 coffee-script關鍵字的套件

接著我們回到終端機模式的操作npm的指令工具本身就可以完成套

件搜尋的任務

例如以下的指令同樣可以找出 coffee-script相關套件

npm search coffee-script

以下是搜尋結果的參考畫面

52 使用 NPM安裝套件 45

Nodejs中文電子書

找到需要的套件後(例如 express)即可使用以下指令安裝

npm install coffee-script

值得注意的一點是使用 npm install會將指定的套件安裝在工

作目錄(Working Directory)的 node modules資料夾下

以Windows為例如果執行 npm install的目錄位於

Cproject1

那麼 npm將會自動建立一個 node modules的子目錄(如果不存在)

Cproject1node modules

並且將下載的套件放置於這個子目錄例如

Cproject1node modulescoffee-script

這個設計讓專案可以個別管理相依的套件並且可以在專案佈署或發

行時將這些套件(位於 node modules)一併打包方便其它專案的使用者不必再重新下載套件

這個 npm install的預設安裝模式為 local(本地)只會變更當前專案的資料夾不會影響系統

另一種安裝模式稱為 global(全域)這種模式會將套件安裝到系統資

料夾也就是 npm安裝路徑的 node modules資料夾例如

CProgram Filesnodejsnode modules

46 5 NPM套件管理工具

Nodejs中文電子書

是否要使用全域安裝可以依照套件是否提供新指令來判斷舉例來說express 套件提供 express 這個指令而 coffee-script 則提供 coffee

指令

在 local 安 裝 模 式 中這 些 指 令 的 程 式 檔 案會 被 安 裝 到node modules的 bin這個隱藏資料夾下除非將bin的路徑加入 PATH環境變數否則要執行這些指令將會相當不便

為了方便指令的執行我們可以在 npm install 加上 -g 或 --

global參數啟用 global安裝模式例如

npm install -g coffee-script

npm install -g express

使用 global安裝模式需要注意執行權限的問題若權限不足可能會出現類似以下的錯誤訊息

npm ERR Error EACCES permission denied rsquorsquo

npm ERR

npm ERR Please try running this command again as rootAdministrator

要獲得足夠得執行權限請參考以下說明

bull Windows 7 或 2008 以上在「命令提示字元」的捷徑按右鍵選擇「以系統管理員身分執行」執行 npm指令時就會具有 Administrator身分

bull Mac OS X或 Linux系統可以使用 sudo指令例如

sudo npm install -g express

bull Linux系統可以使用 root權限登入或是以「sudo su -」切換成 root身分(使用 root權限操作系統相當危險因此並不建議使用這種方式)

若加上 -g參數使用 npm install -g coffee-script完成安裝

後就可以在終端機執行 coffee指令例如

coffee -v

52 使用 NPM安裝套件 47

Nodejs中文電子書

執行結果(範例)

CoffeeScript version 120

53 套件的更新及維護

除了前一節說明的 search 及 install 用法npm 還提供其他許多指令(commands)

使用 npm help可以查詢可用的指令

npm help

執行結果(部分)

where ltcommandgt is one of

adduser apihelp author bin bugs c cache completion

config deprecate docs edit explore faq find get

help help-search home i info init install la link

list ll ln login ls outdated owner pack prefix

prune publish r rb rebuild remove restart rm root

run-script s se search set show star start stop

submodule tag test un uninstall unlink unpublish

unstar up update version view whoami

使用 npm help command可以查詢指令的詳細用法例如

npm help list

接下來本節要介紹開發過程常用的 npm指令

使用 list可以列出已安裝套件

npm list

48 5 NPM套件管理工具

Nodejs中文電子書

執行結果(範例)

-- coffee-script120

- express256

- connect185

| -- formidable108

-- mime124

-- mkdirp007

-- qs041

檢視某個套件的詳細資訊例如

npm show express

升級所有套件(如果該套件已發佈更新版本)

npm update

升級指定的套件

npm update express

移除指定的套件

npm uninstall express

54 使用 packagejson

對於正式的 Nodejs專案可以建立一個命名為 packagejson的設

定檔(純文字格式)檔案內容參考範例如下

54 使用 packagejson 49

Nodejs中文電子書

packagejson(範例)

rdquonamerdquo rdquoapplication-namerdquo

rdquoversionrdquo rdquo001rdquo

rdquoprivaterdquo true

rdquodependenciesrdquo

rdquoexpressrdquo rdquo255rdquo

rdquocoffee-scriptrdquo rdquolatestrdquo

rdquomongooserdquo rdquogt= 253rdquo

其中 name與 version依照專案的需求設置

需要注意的是 dependencies的設定它用於指定專案相依的套件名

稱及版本

bull rdquoexpressrdquo rdquo255rdquo

代表此專案相依版本 255的 express套件

bull rdquocoffee-scriptrdquo rdquolatestrdquo

使用最新版的 coffee-script套件(每次更新都會檢查新版)

bull rdquomongooserdquo rdquogt= 253rdquo

使用版本大於 253的 mongoose套件

假設某個套件的新版可能造成專案無法正常運作就必須指定套件的

版本避免專案的程式碼來不及更新以相容新版套件通常在開發初期的

專案需要盡可能維持新套件的相容性(以取得套件的更新或修正)可以

用「gt=」設定最低相容的版本或是使用「latest」設定永遠保持最新

套件

50 5 NPM套件管理工具

6

Express介紹

在前面的 nodejs基礎當中介紹許多許多開設 http的使用方法及介紹以及許多基本的 nodejs基本應用

接下來要介紹一個套件稱為 express [Express](httpexpressjscom)這個套件主要幫忙解決許多 nodejs http server所需要的基本服務讓開發 httpservice變得更為容易不需要像之前需要透過層層模組(module)才有辦法開始編寫自己的程式

這個套件是由 TJ Holowaychuk 製作而成的套件裡面包含基本的路由處理 (route)http資料處理(GETPOSTPUT)另外還與樣板套件(jshtml template engine)搭配同時也可以處理許多複雜化的問題

61 Express安裝

安裝方式十分簡單只要透過之前介紹的 NPM就可以使用簡單的指令安裝指令如下

這邊建議需要將此套件安裝成為全域模組方便日後使用

51

Nodejs中文電子書

62 Express基本操作

express的使用也十分簡單先來建立一個基本的 hello world

var app = require(rsquoexpressrsquo)createServer()

port = 1337

applisten(port)

appget(rsquorsquo function(req res)

ressend(rsquohello worldrsquo)

)

consolelog(rsquostart express servernrsquo)

可以從上面的程式碼發現基本操作與 nodejs http的建立方式沒有太大差異主要差在當我們設定路由時可以直接透過 appget方式設定回應與接受方式

63 Express路由處理

Express對於 http服務上有許多包裝讓開發者使用及設定上更為方便例如有幾個路由設定那我們就統一藉由 appget來處理

Create http server

appget(rsquorsquo function(req res)

ressend(rsquohello worldrsquo)

)

appget(rsquotestrsquo function(req res)

ressend(rsquotest renderrsquo)

)

appget(rsquouserrsquo function(req res)

ressend(rsquouser pagersquo)

)

52 6 Express介紹

Nodejs中文電子書

如上面的程式碼所表示appget可以帶入兩個參數第一個是路徑名稱設定第二個為回應函式 (call back function)回應函式裡面就如同之前的 createServer方法裡面包含 requestresponse兩個物件可供使用使用者就可以透過瀏覽器輸入不同的 url切換到不同的頁面顯示不同的結果

路由設定上也有基本的配對方式讓使用者從瀏覽器輸入的網址可以

是一個變數只要符合型態就可以有對應的頁面產出例如

Create http server

appget(rsquouseridrsquo function(req res)

ressend(rsquouser rsquo + reqparamsid)

)

appget(rsquonumberrsquo function(req res)

ressend(rsquonumber rsquo + reqparamsnumber)

)

裡 面 使 用 到number 從 網 址 輸 入 之 後 就 可 以 直 接 使 用reqparamsnumber 取得所輸入的資料變成 url 參數使用當然前面也是可以加上路徑的設定userid在瀏覽器上路徑必須符合userxxx透

過 reqparamsid就可以取到 xxx這個字串值

另外express參數處理也提供了路由參數配對處理也可以透過正規表示法作為參數設定

var app = require(rsquoexpressrsquo)createServer()

port = 1337

applisten(port)

appget(^ip((d23)((d23))((d23))((d23))) function(req res)

ressend(reqparams)

)

上面程式碼可以發現後面路由設定的型態是正規表示法裡面設定

格式為ip之後必須要加上 ip型態才會符合資料格式同時取得 ip資料已經由正規表示法將資料做分群因此可以取得 ip的四個數字

此程式執行之後可以透過瀏覽器測試輸入網址為 localhost3000ip25525510010可以從頁面獲得資料

63 Express路由處理 53

Nodejs中文電子書

此章節全部範例程式碼如下

overview

author Caesar Chi

blog clonnblogspotcom

version 20120226

create server

var app = require(rsquoexpressrsquo)createServer()

port = 1337

applisten(port)

normal style

appget(rsquorsquo function(req res)

ressend(rsquohello worldrsquo)

)

appget(rsquotestrsquo function(req res)

ressend(rsquotest renderrsquo)

)

parameter style

appget(rsquouseridrsquo function(req res)

ressend(rsquouser rsquo + reqparamsid)

)

appget(rsquonumberrsquo function(req res)

ressend(rsquonumber rsquo + reqparamsnumber)

)

REGX style

appget(^ip((d23)((d23))((d23))) function(req res)

ressend(reqparams)

)

appget(rsquorsquo function(req res)

ressend(rsquoPage not foundrsquo 404)

54 6 Express介紹

Nodejs中文電子書

)

consolelog(rsquostart express servernrsquo)

64 Express middleware

Express 裡面有一個十分好用的應用概念稱為 middleware可以透過middleware做出複雜的效果同時上面也有介紹 next方法參數傳遞就是靠 middleware的概念來傳遞參數讓開發者可以明確的控制程式邏輯

create http server

appuse(expressbodyParser())

appuse(expressmethodOverride())

appuse(expresssession())

上面都是一種 middleware的使用方式透過 appuse方式裡面載入函式執行方法回應函式會包含三個基本參數responserequestnext其中next表示下一個 middleware執行函式同時會自動將預設三個參數繼續帶往下個函式執行底下有個實驗

上面的片段程式執行後開啟瀏覽器連結上 localhost1337會發現伺服器回應結果順序如下

first middle ware

second middle ware

execute middle ware

end middleware function

從上面的結果可以得知剛才設定的 middleware都生效了在 appuse設定的 middleware是所有 url皆會執行方法如果有指定特定方法就可以使用 appget的 middleware設定在 appget函式的第二個參數就可以帶入函式或者是匿名函式只要函式裡面最後會接受 request response next這三個參數同時也有正確指定 next函式的執行時機最後都會執行到最後一個方法當然開發者也可以評估程式邏輯要執行到哪一個階段讓邏輯

可以更為分明

64 Express middleware 55

Nodejs中文電子書

65 Express路由應用

在實際開發上可能會遇到需要使用參數等方式混和變數一起使用

express裡面提供了一個很棒的處理方法 appall這個方式可以先採用基本路由配對再將設定為每個不同的處理方式開發者可以透過這個方式簡

化自己的程式邏輯

overview

author

version 20120226

create server

var app = require(rsquoexpressrsquo)createServer()

port = 1337

users = [

name rsquoClonnrsquo

name rsquoChirsquo

]

applisten(port)

appall(rsquouseridoprsquo function(req res next)

requser = users[reqparamsid]

if (requser)

next()

else

next(new Error(rsquocannot find user rsquo + reqparamsid))

)

appget(rsquouseridrsquo function(req res)

ressend(rsquoviewing rsquo + requsername)

)

appget(rsquouserideditrsquo function(req res)

ressend(rsquoediting rsquo + requsername)

)

56 6 Express介紹

Nodejs中文電子書

appget(rsquouseriddeletersquo function(req res)

ressend(rsquodeleting rsquo + requsername)

)

appget(rsquorsquo function(req res)

ressend(rsquoPage not foundrsquo 404)

)

consolelog(rsquostart express servernrsquo)

內部宣告一組預設的使用者分別給予名稱設定藉由 appall這個方法可以先將路由雛形建立再接下來設定 appget的路徑格式只要符合格式就會分配進入對應的方法中像上面的程式當中如果使用者輸入路徑為user0除了執行 appall程式之後執行 next方法就會對應到路徑設定為userid的這個方法當中如果使用者輸入路徑為user0edit就會執行到useridedit的對應方法

66 Express GET應用範例

我們準備一個使用 GET方法傳送資料的表單

ltDOCTYPE htmlgt

lthtmlgt

ltheadgt

ltmeta http-equiv=rdquoContent-Typerdquo content=rdquotexthtml charset=utf8rdquogt

lttitlegtNodejs 菜鳥筆記 (1)lttitlegt

ltlink rel=rdquostylesheetrdquo href=rdquocssstylecssrdquo type=rdquotextcssrdquo media=rdquoallrdquo gt

ltheadgt

ltbodygt

lth1gtNodejs 菜鳥筆記-註冊lth1gt

ltform id=rdquosignuprdquo method=rdquoGETrdquo action=rdquohttplocalhost3000Signuprdquogt

ltlabelgt 使用者名稱ltlabelgtltinput type=rdquotextrdquo id=rdquousernamerdquo name=rdquousernamerdquo gtltbrgt

ltlabelgt 電子郵件ltlabelgtltinput type=rdquotextrdquo id=rdquoemailrdquo name=rdquoemailrdquo gtltbrgt

ltinput type=rdquosubmitrdquo value=rdquo 註冊我的帳號rdquo gtltbrgt

ltformgt

ltbodygt

lthtmlgt

66 Express GET應用範例 57

Nodejs中文電子書

這個表單沒有什麼特別的地方我們只需要看第 9 行form 使用的method是 GET然後 action是rdquohttplocalhost3000Signupldquo等一下我們要來撰寫Signup這個 URL Path的處理程式

處理 Signup行為

我們知道所謂的 GET方法會透過 URL來把表單的值給帶過去以上面的表單來說到時候 URL會以這樣的形式傳遞

httplocalhost3000Signupusername=xxxampemail=xxx

所以要能處理這樣的資料必須有以下功能

bull 解析 URL

bull 辨別動作是 Signup

bull 解析出 username和 email

一旦能取得 username和 email的值程式就能加以應用了

處理 Signup的程式碼雛形

load module

var url = require(rsquourlrsquo)

urlData = urlparse(requrltrue)

action = urlDatapathname

reswriteHead(200 rdquoContent-Typerdquordquotexthtml charset=utf-8rdquo)

if (action === rdquoSignuprdquo)

user = urlDataquery

resend(rdquolth1gtrdquo + userusername + rdquo 歡迎您的加入 lth1gtltpgt 我們已經將會員啟用信寄至rdquo + useremail + rdquoltpgtrdquo)

首先需要加載 url module它是用來協助我們解析 URL的模組接著使用 urlparse方法第一個傳入 url字串變數也就是 requrl另外第二個參數的用意是設為 ture則引進 querystring模組來協助處理預設是 false它影響到的是 urlDataquery設為 true會傳回物件不然就只是一般的字串urlparse會將字串內容整理成一個物件我們把它指定給 urlData

action變數作為記錄 pathname這是我們稍後要來判斷目前網頁的動作是什麼接著先將 html表頭資訊 (Header)準備好再來判斷路徑邏輯如

58 6 Express介紹

Nodejs中文電子書

果是 Signup這個動作就把 urlDataquery裡的資料指定給 user然後輸出userusername和 useremail把使用者從表單註冊的資料顯示於頁面中

最後進行程式測試啟動 nodejs主程式之後開啟瀏覽器就會看到表單填寫完畢按下送出就可以看到結果了

完整 nodejs程式碼如下

var http = require(rsquohttprsquo)

url = require(rsquourlrsquo)

fs = require(rdquofsrdquo)

server

server = httpcreateServer(function (reqres)

var urlData

encode = rdquoutf8rdquo

filePath = rdquoviewexpress_get_example_formhtmlrdquo

action

urlData = urlparse(requrltrue)

action = urlDatapathname

reswriteHead(200 rdquoContent-Typerdquordquotexthtml charset=utf-8rdquo)

if (action === rdquoSignuprdquo)

user = urlDataquery

resend(rdquolth1gtrdquo + userusername + rdquo 歡迎您的加入 lth1gtltpgt 我們已經將會員啟用信寄至rdquo + useremail + rdquoltpgtrdquo)

else

fsreadFile(filePath encode function(err file)

reswrite(file)

resend()

)

)

serverlisten(3000)

consolelog(rsquoServer 跑起來了現在時間是rsquo + new Date())

66 Express GET應用範例 59

Nodejs中文電子書

67 Express POST應用範例

一開始準備基本的 html表單傳送內容以 POST方式form的 action屬性設定為 POST其餘 html內容與前一個範例應用相同

ltDOCTYPE htmlgt

lthtmlgt

ltheadgt

ltmeta http-equiv=rdquoContent-Typerdquo content=rdquotexthtml charset=utf8rdquogt

lttitlegtNodejs 菜鳥筆記 (1)lttitlegt

ltlink rel=rdquostylesheetrdquo href=rdquocssstylecssrdquo type=rdquotextcssrdquo media=rdquoallrdquo gt

ltheadgt

ltbodygt

lth1gtNodejs 菜鳥筆記-註冊lth1gt

ltform id=rdquosignuprdquo method=rdquoPOSTrdquo action=rdquohttplocalhost3000Signuprdquogt

ltlabelgt 使用者名稱ltlabelgtltinput type=rdquotextrdquo id=rdquousernamerdquo name=rdquousernamerdquo gtltbrgt

ltlabelgt 電子郵件ltlabelgtltinput type=rdquotextrdquo id=rdquoemailrdquo name=rdquoemailrdquo gtltbrgt

ltinput type=rdquosubmitrdquo value=rdquo 註冊我的帳號rdquo gtltbrgt

ltformgt

ltbodygt

lthtmlgt

nodejs的程式處理邏輯與前面 GET範例類似部分程式碼如下

qs = require(rsquoquerystringrsquo)

if (action === rdquoSignuprdquo)

formData = rsquorsquo

reqon(rdquodatardquo function (data)

formData += data

)

reqon(rdquoendrdquo function ()

user = qsparse(formData)

resend(rdquolth1gtrdquo + userusername + rdquo 歡迎您的加入 lth1gtltpgt 我們已經將會員啟用信寄至rdquo + useremail + rdquoltpgtrdquo)

)

60 6 Express介紹

Nodejs中文電子書

主要加入了rsquoquerystringrsquo這個moduel方便我們等一下解析由表單 POST回來的資料另外加入一個 formData的變數用來搜集待等一下表單回傳的資料前面的 GET範例我們只從 req拿出 url的資料這次要在利用req身上的事件處理

JavaScript 在訂閱事件時使用 addEventListener而 nodejs 使用的則是on這邊加上了監聽 data的事件會在瀏覽器傳送資料到Web Server時被執行參數是它所接收到的資料型態是字串

接著再增加 end的事件當瀏覽器的請求事件結束時它就會動作

由於瀏覽器使用 POST在上傳資料時會將資料一塊塊地上傳因為我們在監聽 data事件時透過 formData變數將它累加起來lt不過由於我們

上傳的資料很少一次就結束不過如果日後需要傳的是資料比較大的檔

案這個累加動作就很重要

當資料傳完就進到 end 事件中會用到 qsparse 來解析 formDataformData的內容是字串內容是

username=wordsmithampemail=wordsmith40somewhere

而 qsparse可以幫我們把這個 querystring轉成物件的格式也就是

username=wordsmithampemail=wordsmith40somewhere

一旦轉成物件並指定給 user之後其他的事情就和 GET方法時操作的一樣寫 response的表頭將內容回傳並將 userusername和 useremail代入到內容中

修改完成後接著執行 nodejs程式啟動 web server開啟瀏覽器進入表單測試看看POST的方式能否順利運作

完整程式碼如下

var http = require(rsquohttprsquo)

url = require(rsquourlrsquo)

fs = require(rdquofsrdquo)

qs = require(rsquoquerystringrsquo)

server

server = httpcreateServer(function (reqres)

var urlData

67 Express POST應用範例 61

Nodejs中文電子書

encode = rdquoutf8rdquo

filePath = rdquoviewexpress_post_example_formhtmlrdquo

formData

action

urlData = urlparse(requrltrue)

action = urlDatapathname

reswriteHead(200 rdquoContent-Typerdquordquotexthtml charset=utf-8rdquo)

if (action === rdquoSignuprdquo)

formData = rsquorsquo

reqon(rdquodatardquo function (data)

formData += data

)

reqon(rdquoendrdquo function ()

user = qsparse(formData)

resend(rdquolth1gtrdquo + userusername + rdquo 歡迎您的加入 lth1gtltpgt 我們已經將會員啟用信寄至rdquo + useremail + rdquoltpgtrdquo)

)

else

fsreadFile(filePath encode function(err file)

reswrite(file)

resend()

)

)

serverlisten(3000)

consolelog(rsquoServer 跑起來了現在時間是rsquo + new Date())

62 6 Express介紹

Nodejs中文電子書

68 Express AJAX應用範例

在 Nodejs要使用 Ajax傳送資料並且與之互動在接受資料的部份沒有太大的差別client端不是用 GET就是用 POST來傳資料重點在處理完後用 JSON格式回傳當然 Ajax不見得只傳 JSON格式有時是回傳一段 HTML碼不過後者對伺服器來說基本上就和前兩篇沒有差別了所以我們還是以回傳 JSON做為這一回的主題

這一回其實大多數的工作都會落在前端 Ajax上面前端要負責發送與接收資料並在接收資料後撤掉原先發送資料的表單並將取得的資料

改成 HTML格式之後放上頁面

首先先準備 HTML靜態頁面資料

ltDOCTYPE htmlgt

lthtmlgt

ltheadgt

lttitlegtNodejs 菜鳥筆記 (3)lttitlegt

ltlink rel=rdquostylesheetrdquo href=rdquocssstylecssrdquo type=rdquotextcssrdquo media=rdquoallrdquo gt

ltscript src=rdquohttpsajaxgoogleapiscomajaxlibsjquery171jqueryminjsrdquogtltscriptgt

ltheadgt

ltbodygt

lth1gtNodejs 菜鳥筆記-註冊 (用 Ajax)lth1gt

ltform id=rdquosignuprdquo action=rdquoSignuprdquo method=rdquoPOSTrdquogt

ltlabelgt 使用者名稱ltlabelgtltinput type=rdquotextrdquo id=rdquousernamerdquo name=rdquousernamerdquo gtltbrgt

ltlabelgt 電子郵件ltlabelgtltinput type=rdquotextrdquo id=rdquoemailrdquo name=rdquoemailrdquo gtltbrgt

ltinput type=rdquosubmitrdquo value=rdquo 註冊我的帳號rdquo gtltbrgt

ltformgt

ltdiv id=rdquoresultrdquogt

ltdivgt

ltscript type=rdquotextjavascriptrdquogt

$(function()$(rsquosignup input[type=rdquosubmitrdquo]rsquo)click(function(e)

var user =

userusername = $(rdquousernamerdquo)val()useremail = $(rdquoemailrdquo)val()

$post(rdquoSignuprdquouserfunction(data)greet(data)

)

68 Express AJAX應用範例 63

Nodejs中文電子書

epreventDefault()

)

var greet = function(msg)

var resultNode = $(rsquoresultrsquo)greeting = $(rsquolth2gtHirsquo+msgusername+rsquo 你的會員 id 是rsquo+ msgid +rsquo 我們會將會員啟動信寄至rsquo+msgemail+rsquolth2gtrsquo)

$(rsquosignuprsquo)hide()resultNodehtml(rdquordquo)

resultNodeappend(greeting)

)

ltscriptgt

ltbodygt

lthtmlgt

HTML頁面上準備了一個表單用來傳送註冊資料接著直接引用了Google CDN來載入 jQuery用來幫我們處理 Ajax的工作這次要傳送和接收的工作很大的變動都在 HTML頁面上的 JavaScript當中我們要做的事有 (相關 jQuery處理這邊不多做贅述指提起主要功能解說)

bull 用 jQUery取得 submit按鈕綁定它的 click動作

bull 取得表單 username和 email的值存放在 user這個物件中

bull 用 jQuery的 $post方法將 user的資料傳到 Server

bull 一旦成功取得資料後透過 greet這個 function組成回報給 user的訊息

bull 清空原本給使用者填資料的表單

bull 將 Server回傳的 usernameemail和 id這 3個資料組成回應的訊息

bull 將訊息放到原本表單的位置

經過以上的處理後一個 Ajax的表單的基本功能已經完成

接著進行 nodejs主要程式的編輯部分程式碼如下

var fs = require(rdquofsrdquo)

64 6 Express介紹

Nodejs中文電子書

qs = require(rsquoquerystringrsquo)

if (action === rdquoSignuprdquo)

formData = rsquorsquo

reqon(rdquodatardquo function (data)

formData += data

)

reqon(rdquoendrdquo function ()

var msg

user = qsparse(formData)

userid = rdquo123456rdquo

msg = JSONstringify(user)

reswriteHead(200 rdquoContent-Typerdquordquoapplicationjson charset=utf-8rdquordquoContent-Lengthrdquomsglength)

resend(msg)

)

這裡的程式和前面 POST範例基本上大同小異差別在

bull 幫 user的資料加上 id隨意存放一些文字進去讓 Server回傳的資料多於 Client端傳上來的不然會覺得 Server都沒做事

bull 增加了 msg 這個變數存放將 user 物件 JSON 文字化的結果JSONstringify這個轉換函式是 V8引擎所提供的如果你好奇的話

bull 大重點來了我們要告訴 Client端這次回傳的資料格式是 JSON所在 Content-type和 Content-Length要提供給 Client

Server很輕鬆就完成任務了最後進行程式測試啟動 nodejs主程式之後開啟瀏覽器就會看到表單填寫完畢按下送出就可以看到結果了

最後 nodejs本篇範例程式碼如下

var http = require(rsquohttprsquo)

url = require(rsquourlrsquo)

fs = require(rdquofsrdquo)

qs = require(rsquoquerystringrsquo)

server

68 Express AJAX應用範例 65

Nodejs中文電子書

server = httpcreateServer(function (reqres)

var urlData

encode = rdquoutf8rdquo

filePath = rdquoviewexpress_ajax_example_formhtmlrdquo

formData

action

urlData = urlparse(requrltrue)

action = urlDatapathname

if (action === rdquoSignuprdquo)

formData = rsquorsquo

reqon(rdquodatardquo function (data)

formData += data

)

reqon(rdquoendrdquo function ()

var msg

user = qsparse(formData)

userid = rdquo123456rdquo

msg = JSONstringify(user)

reswriteHead(200 rdquoContent-TyperdquordquoapplicationjsonrdquordquoContent-Lengthrdquomsglength)

resend(msg)

)

else

fsreadFile(filePath encode function(err file)

reswriteHead(200 rdquoContent-Typerdquordquotexthtml charset=utf-8rdquo)

reswrite(file)

resend()

)

)

serverlisten(3000)

66 6 Express介紹

Nodejs中文電子書

consolelog(rsquoServer 跑起來了現在時間是rsquo + new Date())

69 原始資料提供

bull [NodeJS初學者筆記 (1)-用 GET傳送資料] (httpithelpithomecomtwquestion10087402)

bull [NodeJS初學者筆記 (2)-用 POST傳送資料] (httpithelpithomecomtwquestion10087489)

bull [NodeJS 初學者筆記 (3)-用 Ajax 傳送資料] (httpithelpithomecomtwquestion10087627)

69 原始資料提供 67

Nodejs中文電子書

68 6 Express介紹

7

CoffeeScript

bull Spine

bull Hem

bull Spineapp

Letrsquos Have a Cup of CoffeeScript

bull es5-shim ECMAScript 5 compatibility shims for legacy JavaScript engines

ESnext

node-iform - A middleware for node-validator httpsgithubcomguileennode-iform

69

Nodejs中文電子書

70 7 CoffeeScript

8

製作一個 Hubot的 Plurk Adapter

81 應用事項提醒

此應用範例以CoffeeScript編寫主要程式架構採用Github釋放的Hubot專案以下有幾個主要注意事項請讀者注意

bull Hubot 一開始的架構除了內建的兩個 Adapter 之外其餘都要以Nodejs的Module方式才能運作

bull Hubot的 binhubot裡面寫著 npm install所以不管你怎麼改原始碼也不能改變第一點的狀況

其實上述都在討論同一件事情NodeJS的模組而且模組不能設定相對路徑之類的來安裝一定要包裝成 npm相符和格式才有辦法正確執行

82 建立 Adapter

首先需要準備兩個檔案

bull packagejson

bull plurkcoffee

71

Nodejs中文電子書

有這兩個就足以變成 NodeJS的模組了在開始 Coding之前先來設定一下 packagejson相依性

rdquonamerdquo rdquohubot-plurkrdquo

rdquoversionrdquo rdquo011rdquo

rdquomainrdquo rdquoplurkrdquo

rdquodependenciesrdquo

rdquohubotrdquo rdquogt=205rdquo

rdquooauthrdquo rdquordquo

rdquocronrdquordquordquo

這邊會使用到NodeJS的 cron模組(Module)而登入 Plurk需要OAuth標準授權才行所以也需要加載 OAuth模組

注意Hubot在讀取非內建模組時會自動在前面加上 hubot-的前置

83 建立 Robot跟 API

本篇應用基本上程式碼大部分參考 Hubot - Twitter Adapter來製作主要差異只有在使用 OAuth這個模組Twitter有 Streaming可用而 Plurk則得用 Comet方式來達到即時讀取

plurkcoffee程式碼

Robot = require(rdquohubotrdquo)robot()

Adapter = require(rdquohubtordquo)adapter()

EventEmitter = require(rdquoeventsrdquo)EventEmitter

oauth = require(rdquooauthrdquo)

cronJob = require(rdquocronrdquo)CronJob

class Plurk exntends Adapter

class PlurkStreaming exnteds EventEmitter

72 8 製作一個 Hubot的 Plurk Adapter

Nodejs中文電子書

先弄個基本架構主要 Class皆參考 Twitter Adapter命名方式接著再將 Plurk這個 Class增加幾個Method基本上只要有 run send reply就夠了而 run用來做初始化的部分

class Plurk entends Adapter

send (plurk id strings⋯) -gt

reply (plurk id strings⋯) -gt

run -gt

看起來有點東西接著來處理主要的 Plurk API結合部分

class PlurkStreaming extends EventEmitter

consuctor (options) -gt

plurk (callback) -gt

觀察河道

getChannel -gt

取得 Comet 網址

reply (plurk id message) -gt

回噗

acceptFriends -gt

接受好友

get (path callback) -gt

GET 請求

post (path body callback)-gt

POST 請求(其實是裝飾)

request (method path body callback)-gt

主要的 OAuth 請求

comet (server callback)-gt

噗浪的 Comet 傳回是 JavaScript Callback 要另外處理後才會變成 JSON

然後把注意力集中到 constructor上先把建構子弄好

constructor (options) -gt

super()

if optionskey and optionssecret and optionstoken and optionstoken secret

key = optionskey

secret = optionssecret

token = optionstoken

token secret = optionstoken secret

83 建立 Robot跟 API 73

Nodejs中文電子書

建立 OAuth 連接

consumer = new oauthOAuth(

rdquohttpwwwplurkcomOAuthrequest tokenrdquo

rdquohttpwwwplurkcomOAuthaccess tokenrdquo

key

secret

rdquo10rdquo

rdquohttpwwwplurkcomOAuthauthorizerdquo

rdquoHMAC-SHA1rdquo

)

domain = rdquowwwplurkcomrdquo

初始化取得 Comet 網址

do getChannel

else

throw new Error(rdquo 參數不足需要 Key Secret Token Token Secretrdquo)

接著來處理 request這個 method

request (method path body callback) -gt

記錄一下這次的 Request

consolelog(rdquohttpdomainpathrdquo)

Callback 這邊先不丟進去要用另一種方式處理

request = consumerget(rdquohttpdomainpathrdquo token token secret null)

requeston rdquoresponserdquo (res) -gt

reson rdquodatardquo (chunk) -gt

parseResponse(chunk+rsquorsquo callback)

reson rdquoendrdquo (data) -gt

consolelog rdquoEnd Request pathrdquo

reson rdquoerrorrdquo (data) -gt

consolelog rdquoError rdquo + data

requestend()

處理資料

parseResponse = (data callback) -gt

if datalength gt 0

用 TryCatch 避免處理 JSON 出錯導致整個中斷

try

callback null JSONparse(data)

catch err

74 8 製作一個 Hubot的 Plurk Adapter

Nodejs中文電子書

consolelog(rdquoError Parse JSONrdquo + data err)

繼續執行

callback null data ||

大致上就是這樣上面程式的架構已經將整個 Hubot Plurk Adapter完成因為在測試時竟然因為噗浪 Lag而沒讀到完整的 Comet資料然後造成程式異常為了避免這個問題發生因此需要加上為了完美呈現需要再

加上 Comet的處理所以要使用到 EventEmitter的功能

comet (server callback) -gt

在 Callback 裡面會找不到自身所以設定區域變數

self =

記錄一下這次的 Request

consolelog(rdquo[Comet] serverrdquo)

Callback 這邊先不丟進去要用另一種方式處理

request = consumerget(rdquohttpdomainpathrdquo token token secret null)

requeston rdquoresponserdquo (res) -gt

reson rdquodatardquo (chunk) -gt

parseResponse(chunk+rsquorsquo callback)

reson rdquoendrdquo (data) -gt

consolelog rdquoEnd Request pathrdquo

請求結束發出事件通知可以進行下一次請求

selfemit rdquonextPlurkrdquo

reson rdquoerrorrdquo (data) -gt

consolelog rdquoError rdquo + data

requestend()

處理資料

parseResponse = (data callback) -gt

if datalength gt 0

用 trycatch 避免失敗中斷

try

去掉 JavaScript 的 Callback

data = datamatch(CometChannelscriptCallback((+))s)

jsonData = rdquordquo

if data

83 建立 Robot跟 API 75

Nodejs中文電子書

jsonData = JSONparse(data[1])

else

如果沒有任何 Match 嘗試直接 parse

jsonData = JSONparse(data)

catch err

consolelog(rdquo[Comet] Errorrdquo data err)

用 TryCatch 避免處理 JSON 出錯導致整個中斷

try

只傳入 json 的 data 部分

callback null jsonDatadata

catch err

consolelog(rdquo[Comet]Error Parse JSONrdquo + data err)

繼續執行

callback null data ||

後面的 get跟 post就簡單多了

get (path callback) -gt

request(rdquoGETrdquo path null callback)

post (path body callback) -gt

request(rdquoPOSTrdquo path body callback)

接著處理取的 Comet網址的 getChannel

getChannel -gt

self =

get rdquoAPPRealtimegetUserChannelrdquo (error data) -gt

if error

檢查是否有 comet server

if datacomet server

selfchannel = datacomet server

如果沒有 Channel Ready 就嘗試連接會失敗

selfemit(rsquochannel readyrsquo)

那麼先來處理 Plurk Adaper好處理的部份

send (plruk id strings⋯)-gt

跟 Reply 一樣直接交給 reply 做

reply plurk id strings⋯

76 8 製作一個 Hubot的 Plurk Adapter

Nodejs中文電子書

reply (plurk id strings⋯) -gt

stringsforEach (message) =gt

botreply(plruk id message)

接著把 run處理好就可以上線運作摟

run -gt

self =

options =

key processenvHUBOT PLURK KEY

secret processenvHUBOT PLURK SECRET

token processenvHUBOT PLURK TOKEN

token secret processenvHUBOT PLURK TOKEN SECRET

創建剛剛的 API

bot = new PlurkStreaming(options)

依照 Twitter 的 new RobotTextMessage 會沒有反應所以參考 hubot-minecraft 的方式

r = robotconstructor

處理噗浪河道訊息

doPlurk = (data)-gt

檢查是否為回噗

if dataresponse

datacontent raw = dataresponsecontent raw

datauser id = dataresponseuser id

確定有噗浪 ID 跟訊息

if dataplurk id and datacontent raw

selfreceive new rTextMessage(dataplurk id datacontent raw)

取得 Comet Server 完成開始第一次 Comet 連接

boton rdquochannel readyrdquo () -gt

botplurk selfdoPlurk

上一次 Comet 完成繼續 Polling

boton rdquonextPlurkrdquo ()-gt

botplurk selfdoPlurk

定時接受好友邀請

do botacceptFriends

bot = bot

83 建立 Robot跟 API 77

Nodejs中文電子書

終於完成 AdapterHubot專案裡面的 scripts資料夾內是互動部分不需要像 Adapter如此大費周章處理只新增檔案並且設計好對白之後就會回噗了我開發用的機器人在此大家可以去跟他玩玩[httpplurkcomelct9620 bot](httpplurkcomelct9620 bot)

84 原始資料提供

bull [製 作 一 個 Hubot 的 噗 浪 Adapter](http revo-skillfrosttw blog20120318create-a-hubot-plurk-adapter)

78 8 製作一個 Hubot的 Plurk Adapter

9

Nodejs好用工具介紹

91 logio

Real-time log monitoring in your browser

httplogioorg

nodejs (gt=0412 lt=0611)

npm config set unsafe-perm true

npm install -g --prefix=usrlocal logio

logio server start

httplocalhost8998

79

Nodejs中文電子書

80 9 Nodejs好用工具介紹

10

精選文章

101 Express

bull 使用 NodeJS + Express從 GETPOST Request取值 [邀稿中]

bull link2 [邀稿中]

bull link3 [邀稿中]

81

Nodejs中文電子書

82 10 精選文章

11

參考資源

111 Nodejs書籍

bull Programming Nodejs April 2012 (est) Travis Swicegood Pragmatic Bookshelf

bull e Little Book on CoffeeScript January 2012 Alex MacCaw OrsquoReilly Media

bull Node for Front-End Developers January 2012 Garann Means OrsquoReilly Media

bull Building Hypermedia APIs with HTML5 and Node November 2011 MikeAmundsen OrsquoReilly Media

bull Node Web Development August 2011 David Herron PacktPub

bull CoffeeScript Accelerated JavaScript Development July 2011 Trevor BurnhamPragmatic Bookshelf

bull Getting Started with GEO CouchDB and Nodejs July 2011 Mick omp-son OrsquoReilly Media

bull What Is Node July 2011 Brett McLaughlin OrsquoReilly Media

bull Node Up and Running May 2011 Tom Hughes-Croucher Mike WilsonOrsquoReilly Media

83

Nodejs中文電子書

bull Hands-on Nodejs e Nodejs introduction and API reference May 2011 Pe-dro Teixeira LeanPub

bull e Node Beginner Book Manuel Kiessling

bull Mastering Nodejs visionmedia

112 Nodejs影音教學

bull e Node Sessions e Best of OSCON 2011 August 2011 OrsquoReilly Media(Video)

bull Tom Hughes-Croucher on Node March 2011 OrsquoReilly Media (Video)

113 Nodejs教學網站

bull Node Tuts

114 Nodejs課程

bull Introduction To Nodejs Van Nguyen CodeLesson

84 11 參考資源

Page 14: Nodejs Wiki

Nodejs中文電子書

瀏覽器的 JavaScript與實現 CommonJS規範的 NodeJS有何不同呢瀏覽器的 JavaScript 提供 XMLHttpRequest 讓程式可以和網頁伺服器建立資料傳輸連線但這通常只能適用於網站開發的需求因為我們只能用

XMLHttpRequest與網頁伺服器通訊卻無法利用它建立其他類型如 Telnet FTP NTP的伺服器通訊如果我們想開發網路服務程式例如 SMTP電子郵件伺服器就必須使用 Sockets建立 TCP (某些服務則用 UDP)監聽及連線其他程式語言如 PHPJavaPythonPerl及 Ruby等在標準開發環境中皆有提供 Sockets API而瀏覽器的 JavaScript基於安全及貼近網站設計需求的考量下並未將 Sockets列入標準函式庫之中CommonJS的規範填補這種基礎函式庫功能的空缺遵循 CommonJS規範的 NodeJS可以直接使用 Sockets API建立各種網路服務程式

JavaScript語言本身支援 Lambda的特性因此一個匿名函式 (anonymousfunction)可以被儲存成一個變數並當作參數傳遞給另一個函式

var proc1 = function(op x) return op(x)

var op1 = function(x) return x+1 var op2 = function(x) return xx

proc1(op1 3) result is 4 proc1(op2 5) result is 25

另一個 JavaScript開發者必須掌握的語言特性稱為 Closure

NodeJS符合 CommonJS的規範使得 Callback方式易於實現也能夠讓更多同好基於 JavaScript開發符合 NodeJS的外掛模組 (Module)

回想以前要寫一個能夠同時容納上百人的上線的網路服務需要花費

多大的苦工可能 10人多就需要經過一次程式調整而 NodeJS就是為了解決這個困境NodeJS因此誕生它是一種利用 V8 Javascript編譯器所開發的產品利用 V8編譯器的高效能與 Javascript的程式開發特性所產生的網路程式

開發人員所編寫出來的 Javascript腳本程式怎麼可能會比其他語言寫出來的網路程式還要快上許多呢以前的網路程式原理是將使用者每次的

連線 (connection)都開啟一個執行緒 (thread)當連線爆增的時候將會快速耗盡系統效能並且容易產生阻塞 (block)的發生

8 1 Nodejs簡介

Nodejs中文電子書

NodeJS對於資源的調校有所不同當程式接收到一筆連線 (connection)會通知作業系統透過 epoll kqueue devpoll或 select將連線保留並且放入heap中配置先讓連線進入休眠 (Sleep)狀態當系統通知時才會觸發連線的 callback這種處理連線方式只會佔用掉記憶體並不會使用到 CPU資源另外因為採用 Javascript語言的特性每個 request都會有一個 callback如此可以避免發生 Block的狀況發生

基於 Callback特性目前NodeJS大多應用於 Comet(long pulling) RequestServer或者是高連線數量的網路服務上目前也有許多公司將 NodeJS設為內部核心網路服務之一在 NodeJS也提供了外掛管理 (Node packagemanagement)讓愛好 NodeJS輕易開發更多有趣的服務外掛並且提供到 npm讓全世界使用者快速安裝使用

本書最後執行測試版本為 nodejs v068相關 API文件可查詢 lsquohttpnodejsorg lthttpnodejsorggtlsquo本書所有範例均可於 linux windows上執行如遇到任何問題歡迎至 lsquohttpnodejstw lthttpnodejstwgtlsquo詢問對於nodejs相關問題

9

Nodejs中文電子書

10 1 Nodejs簡介

2

JavaScript與 NodeJS

其實使用 JavaScript 在網頁端與伺服器端的差距並不大但是為了使NodeJS可以發揮他最強大的能力有一些知識還是必要的所以還是針對這些主題介紹一下其中 Event LoopScope以及 Callback其實是比較需要了解的基本知識cpscurrying ow control是更進階的技巧與應用

21 Event Loop

可能很多人在寫 Javascript 時並不知道他是怎麼被執行的這個時候可以參考一下 jQuery作者 John Resig一篇好文章介紹事件及 timer怎麼在瀏覽器中執行How JavaScript Timers Work通常在網頁中所有的Javascript執行完畢後(這部份全部都在 global scope跑除非執行函數)接下來就是如 John Resig解釋的這樣所有的事件處理函數以及 timer執行的函數會排在一個 queue結構中利用一個無窮迴圈不斷從 queue中取出函數來執行這個就是 event loop

(除了 John Resig的那篇文章Nicholas C Zakas的 ldquoProfessional Javascriptfor Web Developer 2nd editionrdquo 有一個試閱本httpyuiblogcomassetspdfzakas-projs-2ed-ch18pdf598頁剛好也有簡短的說明)

所以在 Javascript中雖然有非同步但是他並不是使用執行緒所有

11

Nodejs中文電子書

的事件或是非同步執行的函數都是在同一個執行緒中利用 event loop的方式在執行至於一些比較慢的動作例如 IO網頁 render re ow等實際動作會在其他執行緒跑等到有結果時才利用事件來觸發處理函數來處理

這樣的模型有幾個好處沒有執行緒的額外成本所以反應速度很快不會

有任何程式同時用到同一個變數不必考慮 lock也不會產生 dead lock所以程式撰寫很簡單但是也有一些潛在問題任一個函數執行時間較長都

會讓其他函數更慢執行(因為一個跑完才會跑另一個)在多核心硬體普遍

的現在無法用單一的應用程式 instance發揮所有的硬體能力用 NodeJS撰寫伺服器程式碰到的也是一樣的狀況要讓系統發揮 event loop的效能就要盡量利用事件的方式來組織程式架構另外對於一些有可能較為耗

時的操作可以考慮使用 processnextTick函數來讓他以非同步的方式執行避免在同一個函數中執行太久擋住所有函數的執行

如果想要測試 event loop怎樣在「瀏覽器」中運行可以在函數中呼叫alert()這樣會讓所有 Javascript的執行停下來尤其會干擾所有使用 timer的函數執行有一個簡單的例子這是一個會依照設定的時間間隔嚴格執

行動作的動畫如果時間過了就會跳過要執行的動作點按圖片以後人

物會快速旋轉但是在旋轉執行完畢前按下「delay」按鈕讓 alert訊息等久一點接下來的動畫就完全不會出現了

22 Scope與 Closure

要快速理解 JavaScript的 Scope(變數作用範圍)原理只要記住他是Lexical Scope就差不多了簡單地說變數作用範圍是依照程式定義時(或者叫做程式文本)的上下文決定而不是執行時的上下文決定

為了維護程式執行時所依賴的變數即使執行時程式運行在原本的

scope之外他的變數作用範圍仍然維持不變這時程式依賴的自由變數(定義時不是 local的而是在上一層 scope定義的變數)一樣可以使用就好像被關閉起來所以叫做 Closure用程式看比較好懂

function outter(arg1)

arg1 及 free_variable1 對 inner 函數來說都是自由變數

var free_variable1 = 3

return function inner(arg2)

12 2 JavaScript與 NodeJS

Nodejs中文電子書

var local_variable1 =2arg2 及 local_variable1 對 inner 函數來說都是本地變數

return arg1 + arg2 + free_variable + local_variable1

var a = outter(1)變數 a就是 outter函數執行後返回的 inner函數 var b =a(4)執行 inner函數執行時上下文已經在 outter函數之外但是仍然能正常執行而且可以使用定義在 outter函數裡面的 arg1及 free variable1變數 consolelog(b)結果 10

在 Javascript中scope最主要的單位是函數(另外有 global及 eval)所以有可能製造出 closure的狀況通常在形式上都是有巢狀的函數定義而且內側的函數使用到定義在外側函數裡面的變數

Closure有可能會造成記憶體洩漏主要是因為被參考的變數無法被垃圾收集機制處理造成佔用的資源無法釋放所以使用上必須考慮清楚

不要造成意外的記憶體洩漏(在上面的例子中如果 a一直未執行使用到的記憶體就不會被釋放)

跟透過函數的參數把變數傳給函數比較起來Javascript Engine會比較難對 Closure進行最佳化如果有效能上的考量這一點也需要注意

23 Callback

要介紹 Callback之前要先提到 JavaScript的特色

JavaScript是一種函數式語言(functional language)所有 Javascript語言內的函數都是高階函數 (higher order function這是數學名詞計算機用語好像是 rst class function意指函數使用沒有任何限制與其他物件一樣)也就是說函數可以作為函數的參數傳給函數也可以當作函數的返回值這個特性讓 Javascript的函數使用上非常有彈性而且功能強大

callback在形式上其實就是把函數傳給函數然後在適當的時機呼叫傳入的函數Javascript使用的事件系統通常就是使用這種形式NodeJS中有一個物件叫做 EventEmitter這是 NodeJS事件處理的核心物件所有會使用事件處理的函數都會「繼承」這個物件(這裡說的繼承實

作上應該像是 mixin)他的使用很簡單可以使用物件on(事件名稱callback

23 Callback 13

Nodejs中文電子書

函數) 或是物件addListener(事件名稱callback 函數) 把你想要處理事件的函數傳入在物件中可以使用物件emit(事件名稱 參數) 呼叫傳入的callback函數這是 Observer Pattern的簡單實作而且跟在網頁中使用 DOM的 addEventListener使用上很類似也很容易上手不過 NodeJS是大量使用非同步方式執行的應用所以程式邏輯幾乎都是寫在 callback函數中當邏輯比較複雜時大量的 callback會讓程式看起來很複雜也比較難單元測試舉例來說

var p client = new Db(lsquointegration tests 20rsquo new Server(ldquo127001rdquo 27017) lsquopkrsquoCustomPKFactory) p clientopen(function(err p client)

p clientdropDatabase(function(err done)

p clientcreateCollection(lsquotest custom keyrsquo function(err collection)

collectioninsert(lsquoarsquo1 function(err docs)

collection nd(lsquo idrsquonew ObjectID(ldquoaaaaaaaaaaaardquo) function(err cursor)

cursortoArray(function(err items) testassertEquals(1 itemslength) p clientclose()

)

)

)

)

)

)

這是在網路上看到的一段操作 mongodb的程式碼為了循序操作所以必須在一個 callback裡面呼叫下一個動作要使用的函數這個函數裡面還是會使用 callback最後就形成一個非常深的巢狀

這樣的程式碼會比較難進行單元測試有一個簡單的解決方式是

盡量不要使用匿名函數來當作 callback或是 event handler透過這樣的方式

14 2 JavaScript與 NodeJS

Nodejs中文電子書

就可以對各個 handler做單元測試了例如

var http = require(lsquohttprsquo) var tools =

cookieParser function(request response) if(requestheaders[rsquoCookiersquo]) do parsing

var server = httpcreateServer(function(request response)

thisemit(lsquoinitrsquo request response)

) serveron(lsquoinitrsquo toolscookieParser) serverlisten(8080 lsquo127001rsquo)

更進一步可以把 tools改成外部 module例如叫做 toolsjs

moduleexports = cookieParser function(request response) if(requestheaders[rsquoCookiersquo]) do parsing

然後把程式改成

var http = require(lsquohttprsquo)

var server = httpcreateServer(function(request response) thisemit(lsquoinitrsquo re-quest response)

) serveron(lsquoinitrsquo require(lsquo toolsrsquo)cookieParser) serverlisten(8080lsquo127001rsquo)

這樣就可以單元測試 cookieParser了例如使用 nodeunit時可以這樣寫

var testCase = require(lsquonodeunitrsquo)testCase moduleexports = testCase(

ldquosetUprdquo function(cb) thisrequest = headers Cookielsquoname1val1 name2val2rsquo thisresponse = thisresult= name1rsquoval1rsquoname2rsquoval2rsquo

cb()

ldquotearDownrdquo function(cb)

cb()

23 Callback 15

Nodejs中文電子書

ldquonormal caserdquo function(test)

testexpect(1) var obj = require(lsquotoolsrsquo)cookieParser(thisrequest thisresponse)testdeepEqual(obj thisresult) testdone()

)

善於利用模組可以讓程式更好維護與測試

24 CPS(Continuation-Passing Style)

cps 是 callback 使用上的特例形式上就是在函數最後呼叫 callback這樣就好像把函數執行後把結果交給 callback 繼續運行所以稱作continuation-passing style利用 cps可以在非同步執行的情況下透過傳給 callback的這個 cps callback來獲知 callback執行完畢或是取得執行結果例如

lthtmlgt ltbodygt ltdiv id=rdquopanelrdquo style=rdquovisibilityhiddenrdquogtlt divgtlt bodygt lt htmlgt ltscriptgt var request = new XMLHttpRequest() re-questopen(lsquoGETrsquo lsquotest749txt timestamp=rsquo+new Date()getTime() true) re-questaddEventListener(lsquoreadystatechangersquo function(next)

return function() if(thisreadyState===4ampampthisstatus===200) next(thisresponseText)lt== 傳入的 cps callback 在動作完成時執行並取得結果進一步處理

(function(str)lt==這個匿名函數就是 cps callback

documentgetElementById(lsquopanelrsquo)innerHTML=str docu-mentgetElementById(lsquopanelrsquo)stylevisibility = lsquovisiblersquo

) false) requestsend() ltscriptgt

進一步的應用也可以參考 2-6流程控制

16 2 JavaScript與 NodeJS

Nodejs中文電子書

25 函數返回函數與 Currying

前面的 cps範例裡面使用了函數返回函數這是為了把 cps callback傳遞給 onreadystatechange事件處理函數的方法(因為這個事件處理函數並沒有設計好會傳送接收這樣的參數)實際會執行的事件處理函數其實是內層返回的那個函數之外包覆的這個函數主要是為了利用 Closure把 next傳給內層的事件處理函數這個方法更常使用的地方是為了解決一些

scope問題例如

ltscriptgt var accu=0count=10 for(var i=0 iltcount i++)

setTimeout(

function() countndash accu+=i if(countlt=0)

consolelog(accu)

50)

ltscriptgt

最後得出的結果會是 100而不是想像中的 45這是因為等到 setTime-out指定的函數執行時變數 i已經變成 10而離開迴圈了要解決這個問題就需要透過 Closure來保存變數 i

ltscriptgt var accu=0count=10 for(var i=0 iltcount i++)

setTimeout(

function(i) return function() countndash

accu+=i if(countlt=0)

consolelog(accu)

(i)

50)

25 函數返回函數與 Currying 17

Nodejs中文電子書

淺藍色底色的部份是跟上面例子不一樣的地方 ltscriptgt

函數返回函數的另外一個用途是可以暫緩函數執行例如

function add(m n) return m+n

var a = add(20 10) consolelog(a)

add這個函數必須同時輸入兩個參數才有辦法執行如果我希望這個函數可以先給它一個參數等一些處理過後再給一個參數然後得到結

果就必須用函數返回函數的方式做修改

function add(m)

return function(n) return m+n

var wait another arg = add(20)先給一個參數 var a = function(arr)

var ret=0 for(var i=0iltarrlengthi++) ret+=arr[i] return ret

([1234])計算一下另一個參數 var b = wait another arg(a)然後再繼續執行 consolelog(b)

像這樣利用函數返回函數使得原本接受多個參數的函數可以一次

接受一個參數直到參數接收完成才執行得到結果的方式有一個學名就

叫做Currying

綜合以上許多奇技淫巧就可以透過用函數來處理函數的方式調整

程式流程接下來看看

26 流程控制

(以 sync方式使用 async函數避開巢狀 callback循序呼叫 async callback等奇技淫巧)

建議參考

bull httphowtonodeorgcontrol- ow

bull httphowtonodeorgcontrol- ow-part-ii

18 2 JavaScript與 NodeJS

Nodejs中文電子書

bull httphowtonodeorgcontrol- ow-part-iii

bull httpblogmixunet20110202essential-node-js-patterns-and-snippets

這幾篇都是非常經典的 NodeJSJavascript流程控制好文章(阿mixu是在介紹一些 pattern時提到這方面的主題)不過我還是用幾個簡單的程式介紹一下做法跟概念

並發與等待

下面的程式參考了 mixu文章中的做法

var wait = function(callbacks done) consolelog(lsquowait startrsquo) var counter = call-backslength var results = [] var next = function(result) 接收函數執行結果並判斷是否結束執行 resultspush(result) if(ndashcounter == 0) done(results)如果結束執行就把所有執行結果傳給指定的 callback處理 for(var i = 0 i lt callbackslength i++) 依次呼叫所有要執行的函數 callbacks[i](next) consolelog(lsquowait endrsquo)

wait( [ function(next) setTimeout(function() consolelog(lsquodone arsquo) var result =500 next(result) 500) function(next) setTimeout(function() con-solelog(lsquodone brsquo) var result = 1000 next(result) 1000) function(next)setTimeout(function() consolelog(lsquodone crsquo) var result = 1500 next(1500) 1500) ] function(results) var ret = 0 i=0 for( iltresultslength i++) ret+= results[i] consolelog(lsquodone all result lsquo+ret)

)

執行結果wait start wait end done a done b done c done all result 3000

可以看出來其實 wait並不是真的等到所有函數執行完才結束執行而是在所有傳給他的函數執行完畢後(不論同步非同步)才執行處理結

果的函數(也就是 done())

不過這樣的寫法還不夠實用因為沒辦法實際讓函數可以等待執行

完畢又能當作事件處理函數來實際使用上面參考到的 Tim Caswell的文

26 流程控制 19

Nodejs中文電子書

章裡面有一種解法不過還需要額外包裝(在他的例子中)NodeJS核心的 fs物件把一些函數(例如 readFile)用 Currying處理類似像這樣

var fs = require(lsquofsrsquo) var readFile = function(path)

return function(callback errback)

fsreadFile(path function(err data)

if(err) errback()

else callback(data)

)

其他部份可以參考 Tim Caswell的文章他的 Doparallel跟上面的 wait差不多意思這裡只提示一下他沒說到的地方

另外一種做法是去修飾一下 callback當他作為事件處理函數執行後再用 cps的方式取得結果

ltscriptgt function Wait(fns done)

var count = 0 var results = [] thisgetCallback = function(index)

count++ return (function(waitback)

return function() var i=0args=[]for(iltargumentslengthi++)

argspush(arguments[i])

argspush(waitback) fns[index]apply(thisargs)

)(function(result) resultspush(result) if(ndashcount == 0)

20 2 JavaScript與 NodeJS

Nodejs中文電子書

done(results)

)

var a = new Wait(

[ function(waitback) consolelog(lsquodone arsquo) var result = 500 wait-back(result) function(waitback) consolelog(lsquodone brsquo) var result =1000 waitback(result) function(waitback) consolelog(lsquodone crsquo) varresult = 1500 waitback(result) ] function(results) var ret = 0 i=0for( iltresultslength i++) ret += results[i] consolelog(lsquodone allresult lsquo+ret)

) var callbacks = [agetCallback(0)agetCallback(1)agetCallback(0)agetCallback(2)]一次取出要使用的 callbacks避免結果提早送出 setTimeout(callbacks[0]500) setTimeout(callbacks[1] 1000) setTimeout(callbacks[2] 1500) setTime-out(callbacks[3] 2000) 當所有取出的 callbacks執行完畢就呼叫 done()來處理結果 ltscriptgt

執行結果

done a done b done a done c done all result 3500

上面只是一些小實驗更成熟的作品是 Tim Caswell 的 stephttpsgithubcomcreationixstep

如果希望真正使用同步的方式寫非同步則需要使用 Promisejs這一類的 library來轉換非同步函數不過他結構比較複雜 XD(見仁見智不過有些人認為 Promise有點過頭了)httpblogsmsdncombrbucktonarchive20110815promise-js-2-0-promise-framework-for-javascriptaspx

如果想不透過其他 Library做轉換又能直接用同步方式執行非同步函數大概就要使用一些需要額外 compile原始程式碼的方法了例如 BrunoJouhier的 streamlinejshttpsgithubcomSagestreamlinejs

26 流程控制 21

Nodejs中文電子書

循序執行

循序執行可以協助把非常深的巢狀 callback結構攤平例如用這樣的簡單模組來做(serialjs)

moduleexports = function(funs) var c = 0 if(isArrayOfFunctions(funs))

throw(lsquoArgument type was not matched Should be array of func-tionsrsquo)

return function()

var args = Arrayprototypeslicecall(arguments 0) if((cgt=funslength))

c++ return funs[c-1]apply(this args)

function isArrayOfFunctions(f ) if(typeof f == lsquoobjectrsquo) return false if(flength)return false if(fconcat) return false if(fsplice) return false var i = 0 for(iltflength i++)

if(typeof f[i] == lsquofunctionrsquo) return false

return true

簡單的測試範例(testSerialjs)使用 fs 模組確定某個 path 是檔案然後讀取印出檔案內容這樣會用到兩層的 callback所以測試中有使用serial的版本與 nested callbacks的版本做對照

var serial = require(lsquoserialrsquo) fs = require(lsquofsrsquo) path = lsquodclientjsrsquo cb = serial([ func-tion(err data)

if(err)

if(dataisFile) fsreadFile(path cb)

22 2 JavaScript與 NodeJS

Nodejs中文電子書

else consolelog(err)

function(err data)

if(err) consolelog(lsquo[ attened by searial]rsquo) con-solelog(datatoString(lsquoutf8rsquo))

else consolelog(err)

]) fsstat(path cb)

fsstat(path function(err data) 第一層 callback if(err)

if(dataisFile)

fsreadFile(path function(err data) 第二層 callback if(err)

consolelog(lsquo[nested callbacks]rsquo) con-solelog(datatoString(lsquoutf8rsquo))

else consolelog(err)

)

else consolelog(err)

)

關鍵在於這些 callback的執行是有順序性的所以利用 serial返回的一個函數 cb來取代這些 callback然後在 cb中控制每次會循序呼叫的函數

26 流程控制 23

Nodejs中文電子書

就可以把巢狀的 callback攤平成循序的 function陣列(就是傳給 serial函數的參數)

測試中的dclientjs 是一個簡單的 dnode 測試程式放在跟 testSerialjs同一個目錄

var dnode = require(lsquodnodersquo)

dnodeconnect(8000 lsquolocalhostrsquo function(remote)

remoterestart(function(str) consolelog(str) processexit()

)

)

執行測試程式後出現結果

[ attened by searial] var dnode = require(lsquodnodersquo)

dnodeconnect(8000 lsquolocalhostrsquo function(remote)

remoterestart(function(str) consolelog(str) processexit()

)

)

[nested callbacks] var dnode = require(lsquodnodersquo)

dnodeconnect(8000 lsquolocalhostrsquo function(remote)

remoterestart(function(str) consolelog(str) processexit()

)

)

對照起來看兩種寫法的結果其實是一樣的但是利用 serialjs巢狀的 callback結構就會消失

不過這樣也只限於順序單純的狀況如果函數執行的順序比較複雜

(不只是一直線)還是需要用功能更完整的流程控制模組比較好例如

httpsgithubcomcaolanasync

24 2 JavaScript與 NodeJS

3

Nodejs安裝與設定

本篇將講解如何在各個不同 OS建立 NodeJS環境目前 NodeJS 048版本環境架設方式需依賴 Linux指令才可編譯完成當然在不同作業系統中也已經有 NodeJS package可以直接使用指令快速架設以下各不同作業系統解說如何安裝 NodeJS

31 Ubuntu Linux

更新推薦使用 nvm

git clone gitgithubcomcreationixnvmgit ~nvm

echo rdquo ~nvmnvmshrdquo gtgt ~bashrc

nvm install v0614

nvm alias default v0614

以 上 可 參 考 http dreamerslabcom blog tw how-to-setup-a-node-js-development-environment-on-ubuntu-11-04

使用 APT套件管理工具是常見的方法以下是使用社群提供的 PPA安裝方式

sudo apt-get install python-software-properties

sudo add-apt-repository ppachris-leanodejs-devel

25

Nodejs中文電子書

sudo apt-get update

sudo apt-get install nodejs

32 Other Linux

Linux很適合作為 NodeJS的伺服器作業系統及開發環境安裝前請先確認以下套件已正確安裝

bull curl (wget)用來下載檔案的工具

bull git先進的版本控制工具

bull g++ GNU C++軟體編譯工具

bull make GNU軟體專案建置工具

安裝指令如下如設有權限問題請在指令前面加上 sudo

git clone httpsgithubcomjoyentnodegit

cd node

git checkout v067

configure

make

sudo make install

接著測試 nodeJS是否正常執行

node --version

出現版本訊息即表示安裝成功

33 Windows

nodeJS 在 v060 版本之後開始正式支援 windows native直接使用nodeexe 就可以執行程式支援性完全與 linux 相同更棒的部份就是不需經過編譯經過下載之後簡單設定完成立即開發 node程式

26 3 Nodejs安裝與設定

Nodejs中文電子書

下載 nodejs安裝檔案 lthttpnodejsorgdownloadgt

如此完成 windows native nodeexe安裝接著可以進入 command line執行測試在 command line輸入rdquonoderdquo會出現 nodejs版本訊息畫面表示安裝完成

33 Windows 27

Nodejs中文電子書

28 3 Nodejs安裝與設定

4

Nodejs基礎

前篇文章已經由介紹安裝至設定都有完整介紹nodeJS 內部除了javascript常用的函式 (function)物件 (object)之外也有許多不同的自訂物件nodeJS預設建立這些物件為核心物件是為了要讓開發流程更為這些資料在官方文件已經具有許多具體說明接下來將會介紹在開發 nodeJS程式時常見的物件特性與使用方法

41 nodejs http伺服器建立

在 lsquonodejs官方網站 lthttpnodejsorggtlsquo裡面有舉一個最簡單的 HTTP伺服器建立一開始初步就是建立一個伺服器平台讓 nodejs可以與瀏覽器互相行為每種語言一開始的程式建立都是以 Hello world開始最初也從 Hello world帶各位進入 nodejs的世界

輸入以下程式碼儲存檔案為 node basic http hello worldjs

var server

ip = rdquo127001rdquo

port = 1337

http = require(rsquohttprsquo)

server = httpcreateServer(function (req res)

29

Nodejs中文電子書

reswriteHead(200 rsquoContent-Typersquo rsquotextplainrsquo)

resend(rsquoHello Worldnrsquo)

)

serverlisten(port ip)

consolelog(rdquoServer running at httprdquo + ip + rdquordquo + port)

程式碼解講一開始需要有幾個基本的變數

bull ip 機器本身的 ip位置因為使用本地端因此設定為 127001

bull port 需要開通的阜號通常設定為 http port 80因範例不希望與基本 port相衝隨意設定為 1337

在 nodejs 的程式中有許多預設的模組可以使用因此需要使用require方法將模組引入在這邊我們需要使用 http這個模組因此將 http載入Http模組裡面內建有許多方法可以使用這邊採用 createServer創建一個基本的 http伺服器再將 http伺服器給予一個 server變數裡面的回呼函式 (call back function)可以載入 http伺服器的資料與回應方法 (requestresponse)在程式裡面就可以看到我們直接回應給瀏覽器端所需的 Header回應內容

reswriteHead(200 rsquoContent-Typersquo rsquotextplainrsquo)

resend(rsquoHello Worldnrsquo)

Http伺服器需要設定 port ip在最後需要設定 Http監聽需要使用到listen事件監聽所有 Http伺服器行為

httplisten(port ip)

所有事情都完成之後需要確認伺服器正確執行因此使用 console在javascript裡就有這個原生物件console所印出的資料都會顯示於 nodejs伺服器頁面這邊印出的資料並不會傳送到使用者頁面上之後許多除壞

(debug)都會用到 console物件

consolelog(rdquoServer running at httprdquo + ip + rdquordquo + port)

30 4 Nodejs基礎

Nodejs中文電子書

42 nodejs http路徑建立

前面已經介紹如何建立一個簡單的 http伺服器接下來本章節將會介紹如何處理伺服器路徑 (rout)問題在 http的協定下所有從瀏覽器發出的要求 (request)都需要經過處理路徑上的建立也是如此

路徑就是指伺服器 ip位置或者是網域名稱之後對於伺服器給予的要求修改剛才的 hello world檔案修改如下

server = httpcreateServer(function (req res)

consolelog(requrl)

reswriteHead(200 rsquoContent-Typersquo rsquotextplainrsquo)

resend(rsquohello worldnrsquo)

)

重新啟動 nodejs程式後在瀏覽器端測試一下路徑行為結果如下圖

當在瀏覽器輸入 http1270011337test 在伺服器端會收到兩個要求一個是我們輸入的test要求另外一個則是faviconicotest的路徑要求http伺服器本身需要經過程式設定才有辦法回應給瀏覽器端所需要的回應在伺服器中所有的路徑要求都是需要被解析才有辦法取得資料從

42 nodejs http路徑建立 31

Nodejs中文電子書

上面解說可以了解到在 nodejs當中所有的路徑都需要經過設定未經過設定的路由會讓瀏覽器無法取得任何資料導致錯誤頁面的發生底下將會解

說如何設定路由同時避免發生錯誤情形先前 nodejs程式需要增加一些修改才能讓使用者透過瀏覽器在不同路徑時有不同的結果根據剛才

的程式做如下的修改

var server

ip = rdquo127001rdquo

port = 1337

http = require(rsquohttprsquo)

url = require(rsquourlrsquo)

path

server = httpcreateServer(function (req res)

path = urlparse(requrl)

reswriteHead(200 rsquoContent-Typersquo rsquotextplainrsquo)

switch (pathpathname)

case rdquoindexrdquo

resend(rsquoI am indexnrsquo)

break

case rdquotestrdquo

resend(rsquothis is test pagenrsquo)

break

default

resend(rsquodefault pagenrsquo)

break

)

serverlisten(port ip)

consolelog(rdquoServer running at httprdquo + ip + rdquordquo + port)

程式做了片段的修改首先載入 url模組另外增加一個 path變數url模組就跟如同他的命名一般專門處理 url字串處理裡面提供了許多方法來解決路徑上的問題因為從瀏覽器發出的要求路徑可能會帶有多種需求

或者 GET參數組合等因此我們需要將路徑單純化取用路徑部分的資料即可例如使用者可能會送出 http1270011337testsend=1如果直接

32 4 Nodejs基礎

Nodejs中文電子書

信任 requrl就會收到結果為testsend=1所以需要透過 url模組的方法將路徑資料過濾

在這邊使用 urlparse的方法裡面帶入網址格式資料會回傳路徑資料為了後需方便使用將回傳的資料設定到 path變數當中在回傳的路徑資料裡面包含資訊如下圖

這邊只需要使用單純的路徑要求直接取用 pathpathname就可以達到我們的目的

最後要做路徑的判別在不同的路徑可以指定不同的輸出在範例中

有三個可能結果第一個從瀏覽器輸入index就會顯示 index結果test 就會呈現出 test頁面最後如果都不符合預期的輸入會直接顯示 default的頁面最後的預防可以讓瀏覽器不會出現非預期結果讓程式的可靠性提昇

底下為測試結果

42 nodejs http路徑建立 33

Nodejs中文電子書

43 nodejs檔案讀取

前面已經介紹如何使用路由(rount)做出不同的回應實際應用只有在瀏覽器只有輸出幾個文字資料總是不夠的在本章節中將介紹如何使

用檔案讀取輸出檔案資料讓使用者在前端瀏覽器也可以讀取到完整的

html css javascript檔案輸出

檔案管理最重要的部分就是 lsquoFile system lthttpnodejsorgdocslatestapifshtmlgtlsquo這個模組此模組可以針對檔案做管理監控讀取等行為裡面有許多預設的方法底下是檔案輸出的基本範例底下會有兩個檔案

第一個是靜態 html檔案

ltDOCTYPE htmlgt

lthtml xmlns=rdquohttpwwww3org1999xhtmlrdquo xmllang=rdquozh-CNrdquo lang=rdquozh-CNrdquogt

ltheadgt

ltmeta http-equiv=rdquoContent-Typerdquo content=rdquotexthtml charset=UTF-7rdquo gt

lttitlegtnodejs index html filelttitlegt

ltheadgt

ltbodygt

34 4 Nodejs基礎

Nodejs中文電子書

lth1gtnodejs index html filelth1gt

ltbodygt

lthtmlgt

另一個為 nodejs程式

var fs = require(rdquofsrdquo)

filename = rdquostaticindexhtmlrdquo

encode = rdquoutf8rdquo

fsreadFile(filename encode function(err file)

consolelog(file)

)

一開始直接載入 le system模組 載入名稱為 fs讀取檔案主要使

用的方法為 readFile裡面以三個參數路徑 ( le path) 編碼方式 (encoding)

回應函式 (callback)路徑必須要設定為靜態 html所在位置才能指定到正確的檔案靜態檔案的編碼方式也必須正確這邊使用靜態檔案的編

碼為 utf8如果編碼設定錯誤nodejs讀取出來檔案結果會使用 byte raw格式輸出如果錯誤編碼格式會導致輸出資料為 byte raw

回應函式中裡面會使用兩個變數error為錯誤資訊如果讀取的檔案不存在或者發生錯誤error數值會是 true如果成功讀取資料 error將會是 falsecontent則是檔案內容資料讀取後將會把資料全數丟到 content這個變數當中

最後程式的輸出結果畫面如下

43 nodejs檔案讀取 35

Nodejs中文電子書

44 nodejs http靜態檔案輸出

前面已經了解如何讀取本地端檔案接下來將配合 http伺服器路由讓每個路由都能夠輸出相對應的靜態 html檔案首先新增加幾個靜態 html檔案

ltDOCTYPE htmlgt

lthtml xmlns=rdquohttpwwww3org1999xhtmlrdquo xmllang=rdquozh-CNrdquo lang=rdquozh-CNrdquogt

ltheadgt

ltmeta http-equiv=rdquoContent-Typerdquo content=rdquotexthtml charset=UTF-7rdquo gt

lttitlegtnodejs index html filelttitlegt

ltheadgt

ltbodygt

lth1gtnodejs index html filelth1gt

ltbodygt

lthtmlgt

ltDOCTYPE htmlgt

lthtml xmlns=rdquohttpwwww3org1999xhtmlrdquo xmllang=rdquozh-CNrdquo lang=rdquozh-CNrdquogt

ltheadgt

ltmeta http-equiv=rdquoContent-Typerdquo content=rdquotexthtml charset=UTF-7rdquo gt

lttitlegtnodejs test html filelttitlegt

ltheadgt

ltbodygt

lth1gtnodejs test html filelth1gt

ltbodygt

lthtmlgt

ltDOCTYPE htmlgt

lthtml xmlns=rdquohttpwwww3org1999xhtmlrdquo xmllang=rdquozh-CNrdquo lang=rdquozh-CNrdquogt

ltheadgt

ltmeta http-equiv=rdquoContent-Typerdquo content=rdquotexthtml charset=UTF-7rdquo gt

lttitlegtnodejs static html filelttitlegt

ltheadgt

ltbodygt

lth1gtnodejs static html filelth1gt

ltbodygt

lthtmlgt

36 4 Nodejs基礎

Nodejs中文電子書

準備一個包含基本路由功能的 http伺服器

var server

ip = rdquo127001rdquo

port = 1337

http = require(rsquohttprsquo)

url = require(rsquourlrsquo)

server = httpcreateServer(function (req res)

var path = urlparse(requrl)

)

serverlisten(port ip)

consolelog(rdquoServer running at httprdquo + ip + rdquordquo + port)

加入 le system 模組使用 readFile 的功能將這一段程式放置於

createServer的回應函式中

fsreadFile(filePath encode function(err file)

)

readFile的回應函式裡面加入頁面輸出讓瀏覽器可以正確讀到檔案在這邊我們設定讀取的檔案為 html 靜態檔案所以 Content-type 設定為texthtml讀取到檔案的內容將會正確輸出成 html靜態檔案

fsreadFile(filePath encode function(err file)

reswriteHead(200 rsquoContent-Typersquo rsquotexthtmlrsquo)

reswrite(file)

resend()

)

到這邊為止基本的程式內容都已經完成剩下一些細節的調整首先

路徑上必須做調整目前的靜態檔案全部都放置於 static資料夾底下設定一個變數來記住資料夾位置

接著將瀏覽器發出要求路徑與資料夾組合讀取正確 html靜態檔案使用者有可能會輸入錯誤路徑所以在讀取檔案的時候要加入錯誤處理

同時回應 404伺服器無法正確回應的 http header格式

加入這些細節的修改一個基本的 http 靜態 html輸出伺服器就完成了完整程式碼如下

44 nodejs http靜態檔案輸出 37

Nodejs中文電子書

var server

ip = rdquo127001rdquo

port = 1337

http = require(rsquohttprsquo)

fs = require(rdquofsrdquo)

folderPath = rdquostaticrdquo

url = require(rsquourlrsquo)

path

filePath

encode = rdquoutf8rdquo

server = httpcreateServer(function (req res)

path = urlparse(requrl)

filePath = folderPath + pathpathname

fsreadFile(filePath encode function(err file)

if (err)

reswriteHead(404 rsquoContent-Typersquo rsquotextplainrsquo)

resend()

return

reswriteHead(200 rsquoContent-Typersquo rsquotextapplicationrsquo)

reswrite(file)

resend()

)

)

serverlisten(port ip)

consolelog(rdquoServer running at httprdquo + ip + rdquordquo + port)

45 nodejs http GET資料擷取

http伺服器中除了路由之外另一個最常使用的方法就是擷取 GET資料本單元將會介紹如何透過基本 http伺服器擷取瀏覽器傳來的要求擷取 GET資料

在 http協定中GET參數都是藉由 URL從瀏覽器發出要求送至伺服器

38 4 Nodejs基礎

Nodejs中文電子書

端基本的傳送網址格式可能如下

http127001testsend=1amptest=2

上面這段網址裡面的 GET參數就是 send而這個變數的數值就為 1如果想要在 http伺服器取得 GET資料需要在瀏覽器給予的要求 (request)做處理

首先需要載入 query string這個模組這個模組主要是用來將字串資料

過濾後轉換成javascript物件

qs = require(rsquoquerystringrsquo)

接著在第一階段利用 url模組過濾瀏覽器發出的 URL資料後將回應的物件裡面的 query這個變數是一個字串值資料過濾後如下

send=1amptest=2

透過 query string使用 parse這個方法將資料轉換成 javascript物件就表示 GET的資料已經被伺服器端正式擷取下來

path = urlparse(requrl)

parameter = qsparse(pathquery)

整個 nodejs http GET參數完整擷取程式碼如下

var server

ip = rdquo127001rdquo

port = 1337

http = require(rsquohttprsquo)

qs = require(rsquoquerystringrsquo)

url = require(rsquourlrsquo)

server = httpcreateServer(function (req res)

var path = urlparse(requrl)

parameter = qsparse(pathquery)

consoledir(parameter)

reswriteHead(200 rsquoContent-Typersquo rsquotextplainrsquo)

reswrite(rsquoBrowser test GET parameternrsquo)

resend()

)

45 nodejs http GET資料擷取 39

Nodejs中文電子書

serverlisten(port ip)

consolelog(rdquoServer running at httprdquo + ip + rdquordquo + port)

程式運作之後由瀏覽器輸入要求網址之後nodejs伺服器端回應資料為

Server running at http1270011337

send rsquo1rsquo test rsquo1rsquo

46 本章結語

前面所解說的部份一大部分主要是處理 http伺服器基本問題雖然在某些部分有牽扯到 http 伺服器基本運作原理主要還是希望可以藉由這些基本範例練習 nodejs練習回應函式與語法串接的特點習慣編寫javascript風格程式當然直接這樣開發 nodejs是非常辛苦的接下來在模組實戰開發的部份將會介紹特定的模組一步一步帶領各位從無到有進行

nodejs應用程式開發

40 4 Nodejs基礎

5

NPM套件管理工具

npm全名為 Node Package Manager是 Nodejs的套件(package)管理工具類似 Perl的 ppm或 PHP的 PEAR等安裝 npm後使用npm install

module name指令即可安裝新套件維護管理套件的工作會更加輕鬆

npm可以讓Nodejs的開發者直接利用擴充線上的套件庫(packagesregistry)加速軟體專案的開發npm提供很友善的搜尋功能可以快速找到安裝需要的套件當這些套件發行新版本時npm也可以協助開發者自動更新這些套件

npm不僅可用於安裝新的套件它也支援搜尋列出已安裝模組及更新的功能

51 安裝 NPM

Nodejs在 063版本開始內建 npm讀者安裝的版本若是此版本或更新的版本就可以略過以下安裝說明

若要檢查 npm是否正確安裝可以使用以下的指令

npm -v

41

Nodejs中文電子書

執行結果說明

若 npm正確安裝執行 npm -v將會看到類似 110-2的版本訊息

若讀者安裝的 Nodejs版本比較舊或是有興趣嘗試自己動手安裝 npm工具則可以參考以下的說明

安裝於Windows系統

Nodejs for Windows 於 062 版開始內建 npm使用 nodejsorg 官方提供的安裝程式不需要進一步的設定就可以立即使用 npm指令對於Windows的開發者來說大幅降低環境設定的問題與門檻

除了使用 Nodejs內建的 npm讀者也可以從 npm官方提供的以下網址

httpnpmjsorgdist

這是由 npm提供的 Fancy Windows Install版本請下載壓縮檔(例如npm-110-3zip)並將壓縮檔內容解壓縮至 Nodejs的安裝路徑(例如CProgram Filesnodejs)

解壓縮後在 Nodejs的安裝路徑下應該有以下的檔案及資料夾

bull npmcmd(檔案)

bull node modules(資料夾)

安裝於 Linux系統

Ubuntu Linux的使用者可以加入 NPM Unoffcial PPA這個 repository即可使用 apt-get完成 npm安裝

42 5 NPM套件管理工具

Nodejs中文電子書

Ubuntu Linux使用 apt-get安裝 npm

sudo apt-get install python-software-properties

sudo add-apt-repository ppagias-kay-leenpm

sudo apt-get update

sudo apt-get install npm

npm官方提供的安裝程式 installsh可以適用於大多數的 Linux系統使用這個安裝程式請先確認

1 系統已安裝 curl工具(請使用 curl --version查看版本訊息)

2 已安裝 Nodejs並且 PATH正確設置

3 Nodejs的版本必須大於 04x

以下為 npm提供的安裝指令

curl httpnpmjsorginstallsh | sh

安裝成功會看到如下訊息

installsh安裝成功的訊息

npm10105 homeUSERNAMElocalnodelibnode_modulesnpm

It worked

安裝於Mac OS X

建議採用與 Nodejs相同的方式進行 npm的安裝例如使用MacPorts安裝 Nodejs就同樣使用MacPorts安裝 npm這樣對日後的維護才會更方便容易

使用 MacPorts安裝 npm是本書比較建議的方式它可以讓 npm的安裝移除及更新工作自動化將會幫助開發者節省寶貴時間

51 安裝 NPM 43

Nodejs中文電子書

安裝MacPorts的提示

在MacPorts網站可以取得 OS X系統版本對應的安裝程式(例如 106或 107)httpwwwmacportsorg安裝過程會詢問系統管理者密碼使用預設的選項完成安裝即可安

裝MacPorts之後在終端機執行 port -v將會看到MacPorts的版本訊息

安裝 npm之前先更新MacPorts的套件清單以確保安裝的 npm是最新版本

sudo port -d selfupdate

接著安裝 npm

sudo port install npm

若讀者的 Nodejs並非使用MacPorts安裝則不建議使用MacPorts安裝npm因為 MacPorts會自動檢查並安裝相依套件而 npm相依 nodejs所以MacPorts也會一併將 nodejs套件安裝造成先前讀者使用其它方式安裝的 nodejs被覆蓋

讀者可以先使用 MacPorts安裝 curl(sudo port install curl)

再參考 Linux的 installsh安裝方式即可使用 npm官方提供的安裝程式

NPM安裝後測試

npm是指令列工具(command-line tool)使用時請先打開系統的文字終端機工具

測試 npm安裝與設定是否正確請輸入指令如下

npm -v

或是

npm --version

如果 npm已經正確安裝設定就會顯示版本訊息

44 5 NPM套件管理工具

Nodejs中文電子書

執行結果(範例)

110-2

52 使用 NPM安裝套件

npm目前擁有超過 6000種套件(packages)可以在 npm registry使用關鍵字搜尋套件

httpsearchnpmjsorg

舉例來說在關鍵字欄位輸入「coffee-script」下方的清單就會自動列出包含 coffee-script關鍵字的套件

接著我們回到終端機模式的操作npm的指令工具本身就可以完成套

件搜尋的任務

例如以下的指令同樣可以找出 coffee-script相關套件

npm search coffee-script

以下是搜尋結果的參考畫面

52 使用 NPM安裝套件 45

Nodejs中文電子書

找到需要的套件後(例如 express)即可使用以下指令安裝

npm install coffee-script

值得注意的一點是使用 npm install會將指定的套件安裝在工

作目錄(Working Directory)的 node modules資料夾下

以Windows為例如果執行 npm install的目錄位於

Cproject1

那麼 npm將會自動建立一個 node modules的子目錄(如果不存在)

Cproject1node modules

並且將下載的套件放置於這個子目錄例如

Cproject1node modulescoffee-script

這個設計讓專案可以個別管理相依的套件並且可以在專案佈署或發

行時將這些套件(位於 node modules)一併打包方便其它專案的使用者不必再重新下載套件

這個 npm install的預設安裝模式為 local(本地)只會變更當前專案的資料夾不會影響系統

另一種安裝模式稱為 global(全域)這種模式會將套件安裝到系統資

料夾也就是 npm安裝路徑的 node modules資料夾例如

CProgram Filesnodejsnode modules

46 5 NPM套件管理工具

Nodejs中文電子書

是否要使用全域安裝可以依照套件是否提供新指令來判斷舉例來說express 套件提供 express 這個指令而 coffee-script 則提供 coffee

指令

在 local 安 裝 模 式 中這 些 指 令 的 程 式 檔 案會 被 安 裝 到node modules的 bin這個隱藏資料夾下除非將bin的路徑加入 PATH環境變數否則要執行這些指令將會相當不便

為了方便指令的執行我們可以在 npm install 加上 -g 或 --

global參數啟用 global安裝模式例如

npm install -g coffee-script

npm install -g express

使用 global安裝模式需要注意執行權限的問題若權限不足可能會出現類似以下的錯誤訊息

npm ERR Error EACCES permission denied rsquorsquo

npm ERR

npm ERR Please try running this command again as rootAdministrator

要獲得足夠得執行權限請參考以下說明

bull Windows 7 或 2008 以上在「命令提示字元」的捷徑按右鍵選擇「以系統管理員身分執行」執行 npm指令時就會具有 Administrator身分

bull Mac OS X或 Linux系統可以使用 sudo指令例如

sudo npm install -g express

bull Linux系統可以使用 root權限登入或是以「sudo su -」切換成 root身分(使用 root權限操作系統相當危險因此並不建議使用這種方式)

若加上 -g參數使用 npm install -g coffee-script完成安裝

後就可以在終端機執行 coffee指令例如

coffee -v

52 使用 NPM安裝套件 47

Nodejs中文電子書

執行結果(範例)

CoffeeScript version 120

53 套件的更新及維護

除了前一節說明的 search 及 install 用法npm 還提供其他許多指令(commands)

使用 npm help可以查詢可用的指令

npm help

執行結果(部分)

where ltcommandgt is one of

adduser apihelp author bin bugs c cache completion

config deprecate docs edit explore faq find get

help help-search home i info init install la link

list ll ln login ls outdated owner pack prefix

prune publish r rb rebuild remove restart rm root

run-script s se search set show star start stop

submodule tag test un uninstall unlink unpublish

unstar up update version view whoami

使用 npm help command可以查詢指令的詳細用法例如

npm help list

接下來本節要介紹開發過程常用的 npm指令

使用 list可以列出已安裝套件

npm list

48 5 NPM套件管理工具

Nodejs中文電子書

執行結果(範例)

-- coffee-script120

- express256

- connect185

| -- formidable108

-- mime124

-- mkdirp007

-- qs041

檢視某個套件的詳細資訊例如

npm show express

升級所有套件(如果該套件已發佈更新版本)

npm update

升級指定的套件

npm update express

移除指定的套件

npm uninstall express

54 使用 packagejson

對於正式的 Nodejs專案可以建立一個命名為 packagejson的設

定檔(純文字格式)檔案內容參考範例如下

54 使用 packagejson 49

Nodejs中文電子書

packagejson(範例)

rdquonamerdquo rdquoapplication-namerdquo

rdquoversionrdquo rdquo001rdquo

rdquoprivaterdquo true

rdquodependenciesrdquo

rdquoexpressrdquo rdquo255rdquo

rdquocoffee-scriptrdquo rdquolatestrdquo

rdquomongooserdquo rdquogt= 253rdquo

其中 name與 version依照專案的需求設置

需要注意的是 dependencies的設定它用於指定專案相依的套件名

稱及版本

bull rdquoexpressrdquo rdquo255rdquo

代表此專案相依版本 255的 express套件

bull rdquocoffee-scriptrdquo rdquolatestrdquo

使用最新版的 coffee-script套件(每次更新都會檢查新版)

bull rdquomongooserdquo rdquogt= 253rdquo

使用版本大於 253的 mongoose套件

假設某個套件的新版可能造成專案無法正常運作就必須指定套件的

版本避免專案的程式碼來不及更新以相容新版套件通常在開發初期的

專案需要盡可能維持新套件的相容性(以取得套件的更新或修正)可以

用「gt=」設定最低相容的版本或是使用「latest」設定永遠保持最新

套件

50 5 NPM套件管理工具

6

Express介紹

在前面的 nodejs基礎當中介紹許多許多開設 http的使用方法及介紹以及許多基本的 nodejs基本應用

接下來要介紹一個套件稱為 express [Express](httpexpressjscom)這個套件主要幫忙解決許多 nodejs http server所需要的基本服務讓開發 httpservice變得更為容易不需要像之前需要透過層層模組(module)才有辦法開始編寫自己的程式

這個套件是由 TJ Holowaychuk 製作而成的套件裡面包含基本的路由處理 (route)http資料處理(GETPOSTPUT)另外還與樣板套件(jshtml template engine)搭配同時也可以處理許多複雜化的問題

61 Express安裝

安裝方式十分簡單只要透過之前介紹的 NPM就可以使用簡單的指令安裝指令如下

這邊建議需要將此套件安裝成為全域模組方便日後使用

51

Nodejs中文電子書

62 Express基本操作

express的使用也十分簡單先來建立一個基本的 hello world

var app = require(rsquoexpressrsquo)createServer()

port = 1337

applisten(port)

appget(rsquorsquo function(req res)

ressend(rsquohello worldrsquo)

)

consolelog(rsquostart express servernrsquo)

可以從上面的程式碼發現基本操作與 nodejs http的建立方式沒有太大差異主要差在當我們設定路由時可以直接透過 appget方式設定回應與接受方式

63 Express路由處理

Express對於 http服務上有許多包裝讓開發者使用及設定上更為方便例如有幾個路由設定那我們就統一藉由 appget來處理

Create http server

appget(rsquorsquo function(req res)

ressend(rsquohello worldrsquo)

)

appget(rsquotestrsquo function(req res)

ressend(rsquotest renderrsquo)

)

appget(rsquouserrsquo function(req res)

ressend(rsquouser pagersquo)

)

52 6 Express介紹

Nodejs中文電子書

如上面的程式碼所表示appget可以帶入兩個參數第一個是路徑名稱設定第二個為回應函式 (call back function)回應函式裡面就如同之前的 createServer方法裡面包含 requestresponse兩個物件可供使用使用者就可以透過瀏覽器輸入不同的 url切換到不同的頁面顯示不同的結果

路由設定上也有基本的配對方式讓使用者從瀏覽器輸入的網址可以

是一個變數只要符合型態就可以有對應的頁面產出例如

Create http server

appget(rsquouseridrsquo function(req res)

ressend(rsquouser rsquo + reqparamsid)

)

appget(rsquonumberrsquo function(req res)

ressend(rsquonumber rsquo + reqparamsnumber)

)

裡 面 使 用 到number 從 網 址 輸 入 之 後 就 可 以 直 接 使 用reqparamsnumber 取得所輸入的資料變成 url 參數使用當然前面也是可以加上路徑的設定userid在瀏覽器上路徑必須符合userxxx透

過 reqparamsid就可以取到 xxx這個字串值

另外express參數處理也提供了路由參數配對處理也可以透過正規表示法作為參數設定

var app = require(rsquoexpressrsquo)createServer()

port = 1337

applisten(port)

appget(^ip((d23)((d23))((d23))((d23))) function(req res)

ressend(reqparams)

)

上面程式碼可以發現後面路由設定的型態是正規表示法裡面設定

格式為ip之後必須要加上 ip型態才會符合資料格式同時取得 ip資料已經由正規表示法將資料做分群因此可以取得 ip的四個數字

此程式執行之後可以透過瀏覽器測試輸入網址為 localhost3000ip25525510010可以從頁面獲得資料

63 Express路由處理 53

Nodejs中文電子書

此章節全部範例程式碼如下

overview

author Caesar Chi

blog clonnblogspotcom

version 20120226

create server

var app = require(rsquoexpressrsquo)createServer()

port = 1337

applisten(port)

normal style

appget(rsquorsquo function(req res)

ressend(rsquohello worldrsquo)

)

appget(rsquotestrsquo function(req res)

ressend(rsquotest renderrsquo)

)

parameter style

appget(rsquouseridrsquo function(req res)

ressend(rsquouser rsquo + reqparamsid)

)

appget(rsquonumberrsquo function(req res)

ressend(rsquonumber rsquo + reqparamsnumber)

)

REGX style

appget(^ip((d23)((d23))((d23))) function(req res)

ressend(reqparams)

)

appget(rsquorsquo function(req res)

ressend(rsquoPage not foundrsquo 404)

54 6 Express介紹

Nodejs中文電子書

)

consolelog(rsquostart express servernrsquo)

64 Express middleware

Express 裡面有一個十分好用的應用概念稱為 middleware可以透過middleware做出複雜的效果同時上面也有介紹 next方法參數傳遞就是靠 middleware的概念來傳遞參數讓開發者可以明確的控制程式邏輯

create http server

appuse(expressbodyParser())

appuse(expressmethodOverride())

appuse(expresssession())

上面都是一種 middleware的使用方式透過 appuse方式裡面載入函式執行方法回應函式會包含三個基本參數responserequestnext其中next表示下一個 middleware執行函式同時會自動將預設三個參數繼續帶往下個函式執行底下有個實驗

上面的片段程式執行後開啟瀏覽器連結上 localhost1337會發現伺服器回應結果順序如下

first middle ware

second middle ware

execute middle ware

end middleware function

從上面的結果可以得知剛才設定的 middleware都生效了在 appuse設定的 middleware是所有 url皆會執行方法如果有指定特定方法就可以使用 appget的 middleware設定在 appget函式的第二個參數就可以帶入函式或者是匿名函式只要函式裡面最後會接受 request response next這三個參數同時也有正確指定 next函式的執行時機最後都會執行到最後一個方法當然開發者也可以評估程式邏輯要執行到哪一個階段讓邏輯

可以更為分明

64 Express middleware 55

Nodejs中文電子書

65 Express路由應用

在實際開發上可能會遇到需要使用參數等方式混和變數一起使用

express裡面提供了一個很棒的處理方法 appall這個方式可以先採用基本路由配對再將設定為每個不同的處理方式開發者可以透過這個方式簡

化自己的程式邏輯

overview

author

version 20120226

create server

var app = require(rsquoexpressrsquo)createServer()

port = 1337

users = [

name rsquoClonnrsquo

name rsquoChirsquo

]

applisten(port)

appall(rsquouseridoprsquo function(req res next)

requser = users[reqparamsid]

if (requser)

next()

else

next(new Error(rsquocannot find user rsquo + reqparamsid))

)

appget(rsquouseridrsquo function(req res)

ressend(rsquoviewing rsquo + requsername)

)

appget(rsquouserideditrsquo function(req res)

ressend(rsquoediting rsquo + requsername)

)

56 6 Express介紹

Nodejs中文電子書

appget(rsquouseriddeletersquo function(req res)

ressend(rsquodeleting rsquo + requsername)

)

appget(rsquorsquo function(req res)

ressend(rsquoPage not foundrsquo 404)

)

consolelog(rsquostart express servernrsquo)

內部宣告一組預設的使用者分別給予名稱設定藉由 appall這個方法可以先將路由雛形建立再接下來設定 appget的路徑格式只要符合格式就會分配進入對應的方法中像上面的程式當中如果使用者輸入路徑為user0除了執行 appall程式之後執行 next方法就會對應到路徑設定為userid的這個方法當中如果使用者輸入路徑為user0edit就會執行到useridedit的對應方法

66 Express GET應用範例

我們準備一個使用 GET方法傳送資料的表單

ltDOCTYPE htmlgt

lthtmlgt

ltheadgt

ltmeta http-equiv=rdquoContent-Typerdquo content=rdquotexthtml charset=utf8rdquogt

lttitlegtNodejs 菜鳥筆記 (1)lttitlegt

ltlink rel=rdquostylesheetrdquo href=rdquocssstylecssrdquo type=rdquotextcssrdquo media=rdquoallrdquo gt

ltheadgt

ltbodygt

lth1gtNodejs 菜鳥筆記-註冊lth1gt

ltform id=rdquosignuprdquo method=rdquoGETrdquo action=rdquohttplocalhost3000Signuprdquogt

ltlabelgt 使用者名稱ltlabelgtltinput type=rdquotextrdquo id=rdquousernamerdquo name=rdquousernamerdquo gtltbrgt

ltlabelgt 電子郵件ltlabelgtltinput type=rdquotextrdquo id=rdquoemailrdquo name=rdquoemailrdquo gtltbrgt

ltinput type=rdquosubmitrdquo value=rdquo 註冊我的帳號rdquo gtltbrgt

ltformgt

ltbodygt

lthtmlgt

66 Express GET應用範例 57

Nodejs中文電子書

這個表單沒有什麼特別的地方我們只需要看第 9 行form 使用的method是 GET然後 action是rdquohttplocalhost3000Signupldquo等一下我們要來撰寫Signup這個 URL Path的處理程式

處理 Signup行為

我們知道所謂的 GET方法會透過 URL來把表單的值給帶過去以上面的表單來說到時候 URL會以這樣的形式傳遞

httplocalhost3000Signupusername=xxxampemail=xxx

所以要能處理這樣的資料必須有以下功能

bull 解析 URL

bull 辨別動作是 Signup

bull 解析出 username和 email

一旦能取得 username和 email的值程式就能加以應用了

處理 Signup的程式碼雛形

load module

var url = require(rsquourlrsquo)

urlData = urlparse(requrltrue)

action = urlDatapathname

reswriteHead(200 rdquoContent-Typerdquordquotexthtml charset=utf-8rdquo)

if (action === rdquoSignuprdquo)

user = urlDataquery

resend(rdquolth1gtrdquo + userusername + rdquo 歡迎您的加入 lth1gtltpgt 我們已經將會員啟用信寄至rdquo + useremail + rdquoltpgtrdquo)

首先需要加載 url module它是用來協助我們解析 URL的模組接著使用 urlparse方法第一個傳入 url字串變數也就是 requrl另外第二個參數的用意是設為 ture則引進 querystring模組來協助處理預設是 false它影響到的是 urlDataquery設為 true會傳回物件不然就只是一般的字串urlparse會將字串內容整理成一個物件我們把它指定給 urlData

action變數作為記錄 pathname這是我們稍後要來判斷目前網頁的動作是什麼接著先將 html表頭資訊 (Header)準備好再來判斷路徑邏輯如

58 6 Express介紹

Nodejs中文電子書

果是 Signup這個動作就把 urlDataquery裡的資料指定給 user然後輸出userusername和 useremail把使用者從表單註冊的資料顯示於頁面中

最後進行程式測試啟動 nodejs主程式之後開啟瀏覽器就會看到表單填寫完畢按下送出就可以看到結果了

完整 nodejs程式碼如下

var http = require(rsquohttprsquo)

url = require(rsquourlrsquo)

fs = require(rdquofsrdquo)

server

server = httpcreateServer(function (reqres)

var urlData

encode = rdquoutf8rdquo

filePath = rdquoviewexpress_get_example_formhtmlrdquo

action

urlData = urlparse(requrltrue)

action = urlDatapathname

reswriteHead(200 rdquoContent-Typerdquordquotexthtml charset=utf-8rdquo)

if (action === rdquoSignuprdquo)

user = urlDataquery

resend(rdquolth1gtrdquo + userusername + rdquo 歡迎您的加入 lth1gtltpgt 我們已經將會員啟用信寄至rdquo + useremail + rdquoltpgtrdquo)

else

fsreadFile(filePath encode function(err file)

reswrite(file)

resend()

)

)

serverlisten(3000)

consolelog(rsquoServer 跑起來了現在時間是rsquo + new Date())

66 Express GET應用範例 59

Nodejs中文電子書

67 Express POST應用範例

一開始準備基本的 html表單傳送內容以 POST方式form的 action屬性設定為 POST其餘 html內容與前一個範例應用相同

ltDOCTYPE htmlgt

lthtmlgt

ltheadgt

ltmeta http-equiv=rdquoContent-Typerdquo content=rdquotexthtml charset=utf8rdquogt

lttitlegtNodejs 菜鳥筆記 (1)lttitlegt

ltlink rel=rdquostylesheetrdquo href=rdquocssstylecssrdquo type=rdquotextcssrdquo media=rdquoallrdquo gt

ltheadgt

ltbodygt

lth1gtNodejs 菜鳥筆記-註冊lth1gt

ltform id=rdquosignuprdquo method=rdquoPOSTrdquo action=rdquohttplocalhost3000Signuprdquogt

ltlabelgt 使用者名稱ltlabelgtltinput type=rdquotextrdquo id=rdquousernamerdquo name=rdquousernamerdquo gtltbrgt

ltlabelgt 電子郵件ltlabelgtltinput type=rdquotextrdquo id=rdquoemailrdquo name=rdquoemailrdquo gtltbrgt

ltinput type=rdquosubmitrdquo value=rdquo 註冊我的帳號rdquo gtltbrgt

ltformgt

ltbodygt

lthtmlgt

nodejs的程式處理邏輯與前面 GET範例類似部分程式碼如下

qs = require(rsquoquerystringrsquo)

if (action === rdquoSignuprdquo)

formData = rsquorsquo

reqon(rdquodatardquo function (data)

formData += data

)

reqon(rdquoendrdquo function ()

user = qsparse(formData)

resend(rdquolth1gtrdquo + userusername + rdquo 歡迎您的加入 lth1gtltpgt 我們已經將會員啟用信寄至rdquo + useremail + rdquoltpgtrdquo)

)

60 6 Express介紹

Nodejs中文電子書

主要加入了rsquoquerystringrsquo這個moduel方便我們等一下解析由表單 POST回來的資料另外加入一個 formData的變數用來搜集待等一下表單回傳的資料前面的 GET範例我們只從 req拿出 url的資料這次要在利用req身上的事件處理

JavaScript 在訂閱事件時使用 addEventListener而 nodejs 使用的則是on這邊加上了監聽 data的事件會在瀏覽器傳送資料到Web Server時被執行參數是它所接收到的資料型態是字串

接著再增加 end的事件當瀏覽器的請求事件結束時它就會動作

由於瀏覽器使用 POST在上傳資料時會將資料一塊塊地上傳因為我們在監聽 data事件時透過 formData變數將它累加起來lt不過由於我們

上傳的資料很少一次就結束不過如果日後需要傳的是資料比較大的檔

案這個累加動作就很重要

當資料傳完就進到 end 事件中會用到 qsparse 來解析 formDataformData的內容是字串內容是

username=wordsmithampemail=wordsmith40somewhere

而 qsparse可以幫我們把這個 querystring轉成物件的格式也就是

username=wordsmithampemail=wordsmith40somewhere

一旦轉成物件並指定給 user之後其他的事情就和 GET方法時操作的一樣寫 response的表頭將內容回傳並將 userusername和 useremail代入到內容中

修改完成後接著執行 nodejs程式啟動 web server開啟瀏覽器進入表單測試看看POST的方式能否順利運作

完整程式碼如下

var http = require(rsquohttprsquo)

url = require(rsquourlrsquo)

fs = require(rdquofsrdquo)

qs = require(rsquoquerystringrsquo)

server

server = httpcreateServer(function (reqres)

var urlData

67 Express POST應用範例 61

Nodejs中文電子書

encode = rdquoutf8rdquo

filePath = rdquoviewexpress_post_example_formhtmlrdquo

formData

action

urlData = urlparse(requrltrue)

action = urlDatapathname

reswriteHead(200 rdquoContent-Typerdquordquotexthtml charset=utf-8rdquo)

if (action === rdquoSignuprdquo)

formData = rsquorsquo

reqon(rdquodatardquo function (data)

formData += data

)

reqon(rdquoendrdquo function ()

user = qsparse(formData)

resend(rdquolth1gtrdquo + userusername + rdquo 歡迎您的加入 lth1gtltpgt 我們已經將會員啟用信寄至rdquo + useremail + rdquoltpgtrdquo)

)

else

fsreadFile(filePath encode function(err file)

reswrite(file)

resend()

)

)

serverlisten(3000)

consolelog(rsquoServer 跑起來了現在時間是rsquo + new Date())

62 6 Express介紹

Nodejs中文電子書

68 Express AJAX應用範例

在 Nodejs要使用 Ajax傳送資料並且與之互動在接受資料的部份沒有太大的差別client端不是用 GET就是用 POST來傳資料重點在處理完後用 JSON格式回傳當然 Ajax不見得只傳 JSON格式有時是回傳一段 HTML碼不過後者對伺服器來說基本上就和前兩篇沒有差別了所以我們還是以回傳 JSON做為這一回的主題

這一回其實大多數的工作都會落在前端 Ajax上面前端要負責發送與接收資料並在接收資料後撤掉原先發送資料的表單並將取得的資料

改成 HTML格式之後放上頁面

首先先準備 HTML靜態頁面資料

ltDOCTYPE htmlgt

lthtmlgt

ltheadgt

lttitlegtNodejs 菜鳥筆記 (3)lttitlegt

ltlink rel=rdquostylesheetrdquo href=rdquocssstylecssrdquo type=rdquotextcssrdquo media=rdquoallrdquo gt

ltscript src=rdquohttpsajaxgoogleapiscomajaxlibsjquery171jqueryminjsrdquogtltscriptgt

ltheadgt

ltbodygt

lth1gtNodejs 菜鳥筆記-註冊 (用 Ajax)lth1gt

ltform id=rdquosignuprdquo action=rdquoSignuprdquo method=rdquoPOSTrdquogt

ltlabelgt 使用者名稱ltlabelgtltinput type=rdquotextrdquo id=rdquousernamerdquo name=rdquousernamerdquo gtltbrgt

ltlabelgt 電子郵件ltlabelgtltinput type=rdquotextrdquo id=rdquoemailrdquo name=rdquoemailrdquo gtltbrgt

ltinput type=rdquosubmitrdquo value=rdquo 註冊我的帳號rdquo gtltbrgt

ltformgt

ltdiv id=rdquoresultrdquogt

ltdivgt

ltscript type=rdquotextjavascriptrdquogt

$(function()$(rsquosignup input[type=rdquosubmitrdquo]rsquo)click(function(e)

var user =

userusername = $(rdquousernamerdquo)val()useremail = $(rdquoemailrdquo)val()

$post(rdquoSignuprdquouserfunction(data)greet(data)

)

68 Express AJAX應用範例 63

Nodejs中文電子書

epreventDefault()

)

var greet = function(msg)

var resultNode = $(rsquoresultrsquo)greeting = $(rsquolth2gtHirsquo+msgusername+rsquo 你的會員 id 是rsquo+ msgid +rsquo 我們會將會員啟動信寄至rsquo+msgemail+rsquolth2gtrsquo)

$(rsquosignuprsquo)hide()resultNodehtml(rdquordquo)

resultNodeappend(greeting)

)

ltscriptgt

ltbodygt

lthtmlgt

HTML頁面上準備了一個表單用來傳送註冊資料接著直接引用了Google CDN來載入 jQuery用來幫我們處理 Ajax的工作這次要傳送和接收的工作很大的變動都在 HTML頁面上的 JavaScript當中我們要做的事有 (相關 jQuery處理這邊不多做贅述指提起主要功能解說)

bull 用 jQUery取得 submit按鈕綁定它的 click動作

bull 取得表單 username和 email的值存放在 user這個物件中

bull 用 jQuery的 $post方法將 user的資料傳到 Server

bull 一旦成功取得資料後透過 greet這個 function組成回報給 user的訊息

bull 清空原本給使用者填資料的表單

bull 將 Server回傳的 usernameemail和 id這 3個資料組成回應的訊息

bull 將訊息放到原本表單的位置

經過以上的處理後一個 Ajax的表單的基本功能已經完成

接著進行 nodejs主要程式的編輯部分程式碼如下

var fs = require(rdquofsrdquo)

64 6 Express介紹

Nodejs中文電子書

qs = require(rsquoquerystringrsquo)

if (action === rdquoSignuprdquo)

formData = rsquorsquo

reqon(rdquodatardquo function (data)

formData += data

)

reqon(rdquoendrdquo function ()

var msg

user = qsparse(formData)

userid = rdquo123456rdquo

msg = JSONstringify(user)

reswriteHead(200 rdquoContent-Typerdquordquoapplicationjson charset=utf-8rdquordquoContent-Lengthrdquomsglength)

resend(msg)

)

這裡的程式和前面 POST範例基本上大同小異差別在

bull 幫 user的資料加上 id隨意存放一些文字進去讓 Server回傳的資料多於 Client端傳上來的不然會覺得 Server都沒做事

bull 增加了 msg 這個變數存放將 user 物件 JSON 文字化的結果JSONstringify這個轉換函式是 V8引擎所提供的如果你好奇的話

bull 大重點來了我們要告訴 Client端這次回傳的資料格式是 JSON所在 Content-type和 Content-Length要提供給 Client

Server很輕鬆就完成任務了最後進行程式測試啟動 nodejs主程式之後開啟瀏覽器就會看到表單填寫完畢按下送出就可以看到結果了

最後 nodejs本篇範例程式碼如下

var http = require(rsquohttprsquo)

url = require(rsquourlrsquo)

fs = require(rdquofsrdquo)

qs = require(rsquoquerystringrsquo)

server

68 Express AJAX應用範例 65

Nodejs中文電子書

server = httpcreateServer(function (reqres)

var urlData

encode = rdquoutf8rdquo

filePath = rdquoviewexpress_ajax_example_formhtmlrdquo

formData

action

urlData = urlparse(requrltrue)

action = urlDatapathname

if (action === rdquoSignuprdquo)

formData = rsquorsquo

reqon(rdquodatardquo function (data)

formData += data

)

reqon(rdquoendrdquo function ()

var msg

user = qsparse(formData)

userid = rdquo123456rdquo

msg = JSONstringify(user)

reswriteHead(200 rdquoContent-TyperdquordquoapplicationjsonrdquordquoContent-Lengthrdquomsglength)

resend(msg)

)

else

fsreadFile(filePath encode function(err file)

reswriteHead(200 rdquoContent-Typerdquordquotexthtml charset=utf-8rdquo)

reswrite(file)

resend()

)

)

serverlisten(3000)

66 6 Express介紹

Nodejs中文電子書

consolelog(rsquoServer 跑起來了現在時間是rsquo + new Date())

69 原始資料提供

bull [NodeJS初學者筆記 (1)-用 GET傳送資料] (httpithelpithomecomtwquestion10087402)

bull [NodeJS初學者筆記 (2)-用 POST傳送資料] (httpithelpithomecomtwquestion10087489)

bull [NodeJS 初學者筆記 (3)-用 Ajax 傳送資料] (httpithelpithomecomtwquestion10087627)

69 原始資料提供 67

Nodejs中文電子書

68 6 Express介紹

7

CoffeeScript

bull Spine

bull Hem

bull Spineapp

Letrsquos Have a Cup of CoffeeScript

bull es5-shim ECMAScript 5 compatibility shims for legacy JavaScript engines

ESnext

node-iform - A middleware for node-validator httpsgithubcomguileennode-iform

69

Nodejs中文電子書

70 7 CoffeeScript

8

製作一個 Hubot的 Plurk Adapter

81 應用事項提醒

此應用範例以CoffeeScript編寫主要程式架構採用Github釋放的Hubot專案以下有幾個主要注意事項請讀者注意

bull Hubot 一開始的架構除了內建的兩個 Adapter 之外其餘都要以Nodejs的Module方式才能運作

bull Hubot的 binhubot裡面寫著 npm install所以不管你怎麼改原始碼也不能改變第一點的狀況

其實上述都在討論同一件事情NodeJS的模組而且模組不能設定相對路徑之類的來安裝一定要包裝成 npm相符和格式才有辦法正確執行

82 建立 Adapter

首先需要準備兩個檔案

bull packagejson

bull plurkcoffee

71

Nodejs中文電子書

有這兩個就足以變成 NodeJS的模組了在開始 Coding之前先來設定一下 packagejson相依性

rdquonamerdquo rdquohubot-plurkrdquo

rdquoversionrdquo rdquo011rdquo

rdquomainrdquo rdquoplurkrdquo

rdquodependenciesrdquo

rdquohubotrdquo rdquogt=205rdquo

rdquooauthrdquo rdquordquo

rdquocronrdquordquordquo

這邊會使用到NodeJS的 cron模組(Module)而登入 Plurk需要OAuth標準授權才行所以也需要加載 OAuth模組

注意Hubot在讀取非內建模組時會自動在前面加上 hubot-的前置

83 建立 Robot跟 API

本篇應用基本上程式碼大部分參考 Hubot - Twitter Adapter來製作主要差異只有在使用 OAuth這個模組Twitter有 Streaming可用而 Plurk則得用 Comet方式來達到即時讀取

plurkcoffee程式碼

Robot = require(rdquohubotrdquo)robot()

Adapter = require(rdquohubtordquo)adapter()

EventEmitter = require(rdquoeventsrdquo)EventEmitter

oauth = require(rdquooauthrdquo)

cronJob = require(rdquocronrdquo)CronJob

class Plurk exntends Adapter

class PlurkStreaming exnteds EventEmitter

72 8 製作一個 Hubot的 Plurk Adapter

Nodejs中文電子書

先弄個基本架構主要 Class皆參考 Twitter Adapter命名方式接著再將 Plurk這個 Class增加幾個Method基本上只要有 run send reply就夠了而 run用來做初始化的部分

class Plurk entends Adapter

send (plurk id strings⋯) -gt

reply (plurk id strings⋯) -gt

run -gt

看起來有點東西接著來處理主要的 Plurk API結合部分

class PlurkStreaming extends EventEmitter

consuctor (options) -gt

plurk (callback) -gt

觀察河道

getChannel -gt

取得 Comet 網址

reply (plurk id message) -gt

回噗

acceptFriends -gt

接受好友

get (path callback) -gt

GET 請求

post (path body callback)-gt

POST 請求(其實是裝飾)

request (method path body callback)-gt

主要的 OAuth 請求

comet (server callback)-gt

噗浪的 Comet 傳回是 JavaScript Callback 要另外處理後才會變成 JSON

然後把注意力集中到 constructor上先把建構子弄好

constructor (options) -gt

super()

if optionskey and optionssecret and optionstoken and optionstoken secret

key = optionskey

secret = optionssecret

token = optionstoken

token secret = optionstoken secret

83 建立 Robot跟 API 73

Nodejs中文電子書

建立 OAuth 連接

consumer = new oauthOAuth(

rdquohttpwwwplurkcomOAuthrequest tokenrdquo

rdquohttpwwwplurkcomOAuthaccess tokenrdquo

key

secret

rdquo10rdquo

rdquohttpwwwplurkcomOAuthauthorizerdquo

rdquoHMAC-SHA1rdquo

)

domain = rdquowwwplurkcomrdquo

初始化取得 Comet 網址

do getChannel

else

throw new Error(rdquo 參數不足需要 Key Secret Token Token Secretrdquo)

接著來處理 request這個 method

request (method path body callback) -gt

記錄一下這次的 Request

consolelog(rdquohttpdomainpathrdquo)

Callback 這邊先不丟進去要用另一種方式處理

request = consumerget(rdquohttpdomainpathrdquo token token secret null)

requeston rdquoresponserdquo (res) -gt

reson rdquodatardquo (chunk) -gt

parseResponse(chunk+rsquorsquo callback)

reson rdquoendrdquo (data) -gt

consolelog rdquoEnd Request pathrdquo

reson rdquoerrorrdquo (data) -gt

consolelog rdquoError rdquo + data

requestend()

處理資料

parseResponse = (data callback) -gt

if datalength gt 0

用 TryCatch 避免處理 JSON 出錯導致整個中斷

try

callback null JSONparse(data)

catch err

74 8 製作一個 Hubot的 Plurk Adapter

Nodejs中文電子書

consolelog(rdquoError Parse JSONrdquo + data err)

繼續執行

callback null data ||

大致上就是這樣上面程式的架構已經將整個 Hubot Plurk Adapter完成因為在測試時竟然因為噗浪 Lag而沒讀到完整的 Comet資料然後造成程式異常為了避免這個問題發生因此需要加上為了完美呈現需要再

加上 Comet的處理所以要使用到 EventEmitter的功能

comet (server callback) -gt

在 Callback 裡面會找不到自身所以設定區域變數

self =

記錄一下這次的 Request

consolelog(rdquo[Comet] serverrdquo)

Callback 這邊先不丟進去要用另一種方式處理

request = consumerget(rdquohttpdomainpathrdquo token token secret null)

requeston rdquoresponserdquo (res) -gt

reson rdquodatardquo (chunk) -gt

parseResponse(chunk+rsquorsquo callback)

reson rdquoendrdquo (data) -gt

consolelog rdquoEnd Request pathrdquo

請求結束發出事件通知可以進行下一次請求

selfemit rdquonextPlurkrdquo

reson rdquoerrorrdquo (data) -gt

consolelog rdquoError rdquo + data

requestend()

處理資料

parseResponse = (data callback) -gt

if datalength gt 0

用 trycatch 避免失敗中斷

try

去掉 JavaScript 的 Callback

data = datamatch(CometChannelscriptCallback((+))s)

jsonData = rdquordquo

if data

83 建立 Robot跟 API 75

Nodejs中文電子書

jsonData = JSONparse(data[1])

else

如果沒有任何 Match 嘗試直接 parse

jsonData = JSONparse(data)

catch err

consolelog(rdquo[Comet] Errorrdquo data err)

用 TryCatch 避免處理 JSON 出錯導致整個中斷

try

只傳入 json 的 data 部分

callback null jsonDatadata

catch err

consolelog(rdquo[Comet]Error Parse JSONrdquo + data err)

繼續執行

callback null data ||

後面的 get跟 post就簡單多了

get (path callback) -gt

request(rdquoGETrdquo path null callback)

post (path body callback) -gt

request(rdquoPOSTrdquo path body callback)

接著處理取的 Comet網址的 getChannel

getChannel -gt

self =

get rdquoAPPRealtimegetUserChannelrdquo (error data) -gt

if error

檢查是否有 comet server

if datacomet server

selfchannel = datacomet server

如果沒有 Channel Ready 就嘗試連接會失敗

selfemit(rsquochannel readyrsquo)

那麼先來處理 Plurk Adaper好處理的部份

send (plruk id strings⋯)-gt

跟 Reply 一樣直接交給 reply 做

reply plurk id strings⋯

76 8 製作一個 Hubot的 Plurk Adapter

Nodejs中文電子書

reply (plurk id strings⋯) -gt

stringsforEach (message) =gt

botreply(plruk id message)

接著把 run處理好就可以上線運作摟

run -gt

self =

options =

key processenvHUBOT PLURK KEY

secret processenvHUBOT PLURK SECRET

token processenvHUBOT PLURK TOKEN

token secret processenvHUBOT PLURK TOKEN SECRET

創建剛剛的 API

bot = new PlurkStreaming(options)

依照 Twitter 的 new RobotTextMessage 會沒有反應所以參考 hubot-minecraft 的方式

r = robotconstructor

處理噗浪河道訊息

doPlurk = (data)-gt

檢查是否為回噗

if dataresponse

datacontent raw = dataresponsecontent raw

datauser id = dataresponseuser id

確定有噗浪 ID 跟訊息

if dataplurk id and datacontent raw

selfreceive new rTextMessage(dataplurk id datacontent raw)

取得 Comet Server 完成開始第一次 Comet 連接

boton rdquochannel readyrdquo () -gt

botplurk selfdoPlurk

上一次 Comet 完成繼續 Polling

boton rdquonextPlurkrdquo ()-gt

botplurk selfdoPlurk

定時接受好友邀請

do botacceptFriends

bot = bot

83 建立 Robot跟 API 77

Nodejs中文電子書

終於完成 AdapterHubot專案裡面的 scripts資料夾內是互動部分不需要像 Adapter如此大費周章處理只新增檔案並且設計好對白之後就會回噗了我開發用的機器人在此大家可以去跟他玩玩[httpplurkcomelct9620 bot](httpplurkcomelct9620 bot)

84 原始資料提供

bull [製 作 一 個 Hubot 的 噗 浪 Adapter](http revo-skillfrosttw blog20120318create-a-hubot-plurk-adapter)

78 8 製作一個 Hubot的 Plurk Adapter

9

Nodejs好用工具介紹

91 logio

Real-time log monitoring in your browser

httplogioorg

nodejs (gt=0412 lt=0611)

npm config set unsafe-perm true

npm install -g --prefix=usrlocal logio

logio server start

httplocalhost8998

79

Nodejs中文電子書

80 9 Nodejs好用工具介紹

10

精選文章

101 Express

bull 使用 NodeJS + Express從 GETPOST Request取值 [邀稿中]

bull link2 [邀稿中]

bull link3 [邀稿中]

81

Nodejs中文電子書

82 10 精選文章

11

參考資源

111 Nodejs書籍

bull Programming Nodejs April 2012 (est) Travis Swicegood Pragmatic Bookshelf

bull e Little Book on CoffeeScript January 2012 Alex MacCaw OrsquoReilly Media

bull Node for Front-End Developers January 2012 Garann Means OrsquoReilly Media

bull Building Hypermedia APIs with HTML5 and Node November 2011 MikeAmundsen OrsquoReilly Media

bull Node Web Development August 2011 David Herron PacktPub

bull CoffeeScript Accelerated JavaScript Development July 2011 Trevor BurnhamPragmatic Bookshelf

bull Getting Started with GEO CouchDB and Nodejs July 2011 Mick omp-son OrsquoReilly Media

bull What Is Node July 2011 Brett McLaughlin OrsquoReilly Media

bull Node Up and Running May 2011 Tom Hughes-Croucher Mike WilsonOrsquoReilly Media

83

Nodejs中文電子書

bull Hands-on Nodejs e Nodejs introduction and API reference May 2011 Pe-dro Teixeira LeanPub

bull e Node Beginner Book Manuel Kiessling

bull Mastering Nodejs visionmedia

112 Nodejs影音教學

bull e Node Sessions e Best of OSCON 2011 August 2011 OrsquoReilly Media(Video)

bull Tom Hughes-Croucher on Node March 2011 OrsquoReilly Media (Video)

113 Nodejs教學網站

bull Node Tuts

114 Nodejs課程

bull Introduction To Nodejs Van Nguyen CodeLesson

84 11 參考資源

Page 15: Nodejs Wiki

Nodejs中文電子書

NodeJS對於資源的調校有所不同當程式接收到一筆連線 (connection)會通知作業系統透過 epoll kqueue devpoll或 select將連線保留並且放入heap中配置先讓連線進入休眠 (Sleep)狀態當系統通知時才會觸發連線的 callback這種處理連線方式只會佔用掉記憶體並不會使用到 CPU資源另外因為採用 Javascript語言的特性每個 request都會有一個 callback如此可以避免發生 Block的狀況發生

基於 Callback特性目前NodeJS大多應用於 Comet(long pulling) RequestServer或者是高連線數量的網路服務上目前也有許多公司將 NodeJS設為內部核心網路服務之一在 NodeJS也提供了外掛管理 (Node packagemanagement)讓愛好 NodeJS輕易開發更多有趣的服務外掛並且提供到 npm讓全世界使用者快速安裝使用

本書最後執行測試版本為 nodejs v068相關 API文件可查詢 lsquohttpnodejsorg lthttpnodejsorggtlsquo本書所有範例均可於 linux windows上執行如遇到任何問題歡迎至 lsquohttpnodejstw lthttpnodejstwgtlsquo詢問對於nodejs相關問題

9

Nodejs中文電子書

10 1 Nodejs簡介

2

JavaScript與 NodeJS

其實使用 JavaScript 在網頁端與伺服器端的差距並不大但是為了使NodeJS可以發揮他最強大的能力有一些知識還是必要的所以還是針對這些主題介紹一下其中 Event LoopScope以及 Callback其實是比較需要了解的基本知識cpscurrying ow control是更進階的技巧與應用

21 Event Loop

可能很多人在寫 Javascript 時並不知道他是怎麼被執行的這個時候可以參考一下 jQuery作者 John Resig一篇好文章介紹事件及 timer怎麼在瀏覽器中執行How JavaScript Timers Work通常在網頁中所有的Javascript執行完畢後(這部份全部都在 global scope跑除非執行函數)接下來就是如 John Resig解釋的這樣所有的事件處理函數以及 timer執行的函數會排在一個 queue結構中利用一個無窮迴圈不斷從 queue中取出函數來執行這個就是 event loop

(除了 John Resig的那篇文章Nicholas C Zakas的 ldquoProfessional Javascriptfor Web Developer 2nd editionrdquo 有一個試閱本httpyuiblogcomassetspdfzakas-projs-2ed-ch18pdf598頁剛好也有簡短的說明)

所以在 Javascript中雖然有非同步但是他並不是使用執行緒所有

11

Nodejs中文電子書

的事件或是非同步執行的函數都是在同一個執行緒中利用 event loop的方式在執行至於一些比較慢的動作例如 IO網頁 render re ow等實際動作會在其他執行緒跑等到有結果時才利用事件來觸發處理函數來處理

這樣的模型有幾個好處沒有執行緒的額外成本所以反應速度很快不會

有任何程式同時用到同一個變數不必考慮 lock也不會產生 dead lock所以程式撰寫很簡單但是也有一些潛在問題任一個函數執行時間較長都

會讓其他函數更慢執行(因為一個跑完才會跑另一個)在多核心硬體普遍

的現在無法用單一的應用程式 instance發揮所有的硬體能力用 NodeJS撰寫伺服器程式碰到的也是一樣的狀況要讓系統發揮 event loop的效能就要盡量利用事件的方式來組織程式架構另外對於一些有可能較為耗

時的操作可以考慮使用 processnextTick函數來讓他以非同步的方式執行避免在同一個函數中執行太久擋住所有函數的執行

如果想要測試 event loop怎樣在「瀏覽器」中運行可以在函數中呼叫alert()這樣會讓所有 Javascript的執行停下來尤其會干擾所有使用 timer的函數執行有一個簡單的例子這是一個會依照設定的時間間隔嚴格執

行動作的動畫如果時間過了就會跳過要執行的動作點按圖片以後人

物會快速旋轉但是在旋轉執行完畢前按下「delay」按鈕讓 alert訊息等久一點接下來的動畫就完全不會出現了

22 Scope與 Closure

要快速理解 JavaScript的 Scope(變數作用範圍)原理只要記住他是Lexical Scope就差不多了簡單地說變數作用範圍是依照程式定義時(或者叫做程式文本)的上下文決定而不是執行時的上下文決定

為了維護程式執行時所依賴的變數即使執行時程式運行在原本的

scope之外他的變數作用範圍仍然維持不變這時程式依賴的自由變數(定義時不是 local的而是在上一層 scope定義的變數)一樣可以使用就好像被關閉起來所以叫做 Closure用程式看比較好懂

function outter(arg1)

arg1 及 free_variable1 對 inner 函數來說都是自由變數

var free_variable1 = 3

return function inner(arg2)

12 2 JavaScript與 NodeJS

Nodejs中文電子書

var local_variable1 =2arg2 及 local_variable1 對 inner 函數來說都是本地變數

return arg1 + arg2 + free_variable + local_variable1

var a = outter(1)變數 a就是 outter函數執行後返回的 inner函數 var b =a(4)執行 inner函數執行時上下文已經在 outter函數之外但是仍然能正常執行而且可以使用定義在 outter函數裡面的 arg1及 free variable1變數 consolelog(b)結果 10

在 Javascript中scope最主要的單位是函數(另外有 global及 eval)所以有可能製造出 closure的狀況通常在形式上都是有巢狀的函數定義而且內側的函數使用到定義在外側函數裡面的變數

Closure有可能會造成記憶體洩漏主要是因為被參考的變數無法被垃圾收集機制處理造成佔用的資源無法釋放所以使用上必須考慮清楚

不要造成意外的記憶體洩漏(在上面的例子中如果 a一直未執行使用到的記憶體就不會被釋放)

跟透過函數的參數把變數傳給函數比較起來Javascript Engine會比較難對 Closure進行最佳化如果有效能上的考量這一點也需要注意

23 Callback

要介紹 Callback之前要先提到 JavaScript的特色

JavaScript是一種函數式語言(functional language)所有 Javascript語言內的函數都是高階函數 (higher order function這是數學名詞計算機用語好像是 rst class function意指函數使用沒有任何限制與其他物件一樣)也就是說函數可以作為函數的參數傳給函數也可以當作函數的返回值這個特性讓 Javascript的函數使用上非常有彈性而且功能強大

callback在形式上其實就是把函數傳給函數然後在適當的時機呼叫傳入的函數Javascript使用的事件系統通常就是使用這種形式NodeJS中有一個物件叫做 EventEmitter這是 NodeJS事件處理的核心物件所有會使用事件處理的函數都會「繼承」這個物件(這裡說的繼承實

作上應該像是 mixin)他的使用很簡單可以使用物件on(事件名稱callback

23 Callback 13

Nodejs中文電子書

函數) 或是物件addListener(事件名稱callback 函數) 把你想要處理事件的函數傳入在物件中可以使用物件emit(事件名稱 參數) 呼叫傳入的callback函數這是 Observer Pattern的簡單實作而且跟在網頁中使用 DOM的 addEventListener使用上很類似也很容易上手不過 NodeJS是大量使用非同步方式執行的應用所以程式邏輯幾乎都是寫在 callback函數中當邏輯比較複雜時大量的 callback會讓程式看起來很複雜也比較難單元測試舉例來說

var p client = new Db(lsquointegration tests 20rsquo new Server(ldquo127001rdquo 27017) lsquopkrsquoCustomPKFactory) p clientopen(function(err p client)

p clientdropDatabase(function(err done)

p clientcreateCollection(lsquotest custom keyrsquo function(err collection)

collectioninsert(lsquoarsquo1 function(err docs)

collection nd(lsquo idrsquonew ObjectID(ldquoaaaaaaaaaaaardquo) function(err cursor)

cursortoArray(function(err items) testassertEquals(1 itemslength) p clientclose()

)

)

)

)

)

)

這是在網路上看到的一段操作 mongodb的程式碼為了循序操作所以必須在一個 callback裡面呼叫下一個動作要使用的函數這個函數裡面還是會使用 callback最後就形成一個非常深的巢狀

這樣的程式碼會比較難進行單元測試有一個簡單的解決方式是

盡量不要使用匿名函數來當作 callback或是 event handler透過這樣的方式

14 2 JavaScript與 NodeJS

Nodejs中文電子書

就可以對各個 handler做單元測試了例如

var http = require(lsquohttprsquo) var tools =

cookieParser function(request response) if(requestheaders[rsquoCookiersquo]) do parsing

var server = httpcreateServer(function(request response)

thisemit(lsquoinitrsquo request response)

) serveron(lsquoinitrsquo toolscookieParser) serverlisten(8080 lsquo127001rsquo)

更進一步可以把 tools改成外部 module例如叫做 toolsjs

moduleexports = cookieParser function(request response) if(requestheaders[rsquoCookiersquo]) do parsing

然後把程式改成

var http = require(lsquohttprsquo)

var server = httpcreateServer(function(request response) thisemit(lsquoinitrsquo re-quest response)

) serveron(lsquoinitrsquo require(lsquo toolsrsquo)cookieParser) serverlisten(8080lsquo127001rsquo)

這樣就可以單元測試 cookieParser了例如使用 nodeunit時可以這樣寫

var testCase = require(lsquonodeunitrsquo)testCase moduleexports = testCase(

ldquosetUprdquo function(cb) thisrequest = headers Cookielsquoname1val1 name2val2rsquo thisresponse = thisresult= name1rsquoval1rsquoname2rsquoval2rsquo

cb()

ldquotearDownrdquo function(cb)

cb()

23 Callback 15

Nodejs中文電子書

ldquonormal caserdquo function(test)

testexpect(1) var obj = require(lsquotoolsrsquo)cookieParser(thisrequest thisresponse)testdeepEqual(obj thisresult) testdone()

)

善於利用模組可以讓程式更好維護與測試

24 CPS(Continuation-Passing Style)

cps 是 callback 使用上的特例形式上就是在函數最後呼叫 callback這樣就好像把函數執行後把結果交給 callback 繼續運行所以稱作continuation-passing style利用 cps可以在非同步執行的情況下透過傳給 callback的這個 cps callback來獲知 callback執行完畢或是取得執行結果例如

lthtmlgt ltbodygt ltdiv id=rdquopanelrdquo style=rdquovisibilityhiddenrdquogtlt divgtlt bodygt lt htmlgt ltscriptgt var request = new XMLHttpRequest() re-questopen(lsquoGETrsquo lsquotest749txt timestamp=rsquo+new Date()getTime() true) re-questaddEventListener(lsquoreadystatechangersquo function(next)

return function() if(thisreadyState===4ampampthisstatus===200) next(thisresponseText)lt== 傳入的 cps callback 在動作完成時執行並取得結果進一步處理

(function(str)lt==這個匿名函數就是 cps callback

documentgetElementById(lsquopanelrsquo)innerHTML=str docu-mentgetElementById(lsquopanelrsquo)stylevisibility = lsquovisiblersquo

) false) requestsend() ltscriptgt

進一步的應用也可以參考 2-6流程控制

16 2 JavaScript與 NodeJS

Nodejs中文電子書

25 函數返回函數與 Currying

前面的 cps範例裡面使用了函數返回函數這是為了把 cps callback傳遞給 onreadystatechange事件處理函數的方法(因為這個事件處理函數並沒有設計好會傳送接收這樣的參數)實際會執行的事件處理函數其實是內層返回的那個函數之外包覆的這個函數主要是為了利用 Closure把 next傳給內層的事件處理函數這個方法更常使用的地方是為了解決一些

scope問題例如

ltscriptgt var accu=0count=10 for(var i=0 iltcount i++)

setTimeout(

function() countndash accu+=i if(countlt=0)

consolelog(accu)

50)

ltscriptgt

最後得出的結果會是 100而不是想像中的 45這是因為等到 setTime-out指定的函數執行時變數 i已經變成 10而離開迴圈了要解決這個問題就需要透過 Closure來保存變數 i

ltscriptgt var accu=0count=10 for(var i=0 iltcount i++)

setTimeout(

function(i) return function() countndash

accu+=i if(countlt=0)

consolelog(accu)

(i)

50)

25 函數返回函數與 Currying 17

Nodejs中文電子書

淺藍色底色的部份是跟上面例子不一樣的地方 ltscriptgt

函數返回函數的另外一個用途是可以暫緩函數執行例如

function add(m n) return m+n

var a = add(20 10) consolelog(a)

add這個函數必須同時輸入兩個參數才有辦法執行如果我希望這個函數可以先給它一個參數等一些處理過後再給一個參數然後得到結

果就必須用函數返回函數的方式做修改

function add(m)

return function(n) return m+n

var wait another arg = add(20)先給一個參數 var a = function(arr)

var ret=0 for(var i=0iltarrlengthi++) ret+=arr[i] return ret

([1234])計算一下另一個參數 var b = wait another arg(a)然後再繼續執行 consolelog(b)

像這樣利用函數返回函數使得原本接受多個參數的函數可以一次

接受一個參數直到參數接收完成才執行得到結果的方式有一個學名就

叫做Currying

綜合以上許多奇技淫巧就可以透過用函數來處理函數的方式調整

程式流程接下來看看

26 流程控制

(以 sync方式使用 async函數避開巢狀 callback循序呼叫 async callback等奇技淫巧)

建議參考

bull httphowtonodeorgcontrol- ow

bull httphowtonodeorgcontrol- ow-part-ii

18 2 JavaScript與 NodeJS

Nodejs中文電子書

bull httphowtonodeorgcontrol- ow-part-iii

bull httpblogmixunet20110202essential-node-js-patterns-and-snippets

這幾篇都是非常經典的 NodeJSJavascript流程控制好文章(阿mixu是在介紹一些 pattern時提到這方面的主題)不過我還是用幾個簡單的程式介紹一下做法跟概念

並發與等待

下面的程式參考了 mixu文章中的做法

var wait = function(callbacks done) consolelog(lsquowait startrsquo) var counter = call-backslength var results = [] var next = function(result) 接收函數執行結果並判斷是否結束執行 resultspush(result) if(ndashcounter == 0) done(results)如果結束執行就把所有執行結果傳給指定的 callback處理 for(var i = 0 i lt callbackslength i++) 依次呼叫所有要執行的函數 callbacks[i](next) consolelog(lsquowait endrsquo)

wait( [ function(next) setTimeout(function() consolelog(lsquodone arsquo) var result =500 next(result) 500) function(next) setTimeout(function() con-solelog(lsquodone brsquo) var result = 1000 next(result) 1000) function(next)setTimeout(function() consolelog(lsquodone crsquo) var result = 1500 next(1500) 1500) ] function(results) var ret = 0 i=0 for( iltresultslength i++) ret+= results[i] consolelog(lsquodone all result lsquo+ret)

)

執行結果wait start wait end done a done b done c done all result 3000

可以看出來其實 wait並不是真的等到所有函數執行完才結束執行而是在所有傳給他的函數執行完畢後(不論同步非同步)才執行處理結

果的函數(也就是 done())

不過這樣的寫法還不夠實用因為沒辦法實際讓函數可以等待執行

完畢又能當作事件處理函數來實際使用上面參考到的 Tim Caswell的文

26 流程控制 19

Nodejs中文電子書

章裡面有一種解法不過還需要額外包裝(在他的例子中)NodeJS核心的 fs物件把一些函數(例如 readFile)用 Currying處理類似像這樣

var fs = require(lsquofsrsquo) var readFile = function(path)

return function(callback errback)

fsreadFile(path function(err data)

if(err) errback()

else callback(data)

)

其他部份可以參考 Tim Caswell的文章他的 Doparallel跟上面的 wait差不多意思這裡只提示一下他沒說到的地方

另外一種做法是去修飾一下 callback當他作為事件處理函數執行後再用 cps的方式取得結果

ltscriptgt function Wait(fns done)

var count = 0 var results = [] thisgetCallback = function(index)

count++ return (function(waitback)

return function() var i=0args=[]for(iltargumentslengthi++)

argspush(arguments[i])

argspush(waitback) fns[index]apply(thisargs)

)(function(result) resultspush(result) if(ndashcount == 0)

20 2 JavaScript與 NodeJS

Nodejs中文電子書

done(results)

)

var a = new Wait(

[ function(waitback) consolelog(lsquodone arsquo) var result = 500 wait-back(result) function(waitback) consolelog(lsquodone brsquo) var result =1000 waitback(result) function(waitback) consolelog(lsquodone crsquo) varresult = 1500 waitback(result) ] function(results) var ret = 0 i=0for( iltresultslength i++) ret += results[i] consolelog(lsquodone allresult lsquo+ret)

) var callbacks = [agetCallback(0)agetCallback(1)agetCallback(0)agetCallback(2)]一次取出要使用的 callbacks避免結果提早送出 setTimeout(callbacks[0]500) setTimeout(callbacks[1] 1000) setTimeout(callbacks[2] 1500) setTime-out(callbacks[3] 2000) 當所有取出的 callbacks執行完畢就呼叫 done()來處理結果 ltscriptgt

執行結果

done a done b done a done c done all result 3500

上面只是一些小實驗更成熟的作品是 Tim Caswell 的 stephttpsgithubcomcreationixstep

如果希望真正使用同步的方式寫非同步則需要使用 Promisejs這一類的 library來轉換非同步函數不過他結構比較複雜 XD(見仁見智不過有些人認為 Promise有點過頭了)httpblogsmsdncombrbucktonarchive20110815promise-js-2-0-promise-framework-for-javascriptaspx

如果想不透過其他 Library做轉換又能直接用同步方式執行非同步函數大概就要使用一些需要額外 compile原始程式碼的方法了例如 BrunoJouhier的 streamlinejshttpsgithubcomSagestreamlinejs

26 流程控制 21

Nodejs中文電子書

循序執行

循序執行可以協助把非常深的巢狀 callback結構攤平例如用這樣的簡單模組來做(serialjs)

moduleexports = function(funs) var c = 0 if(isArrayOfFunctions(funs))

throw(lsquoArgument type was not matched Should be array of func-tionsrsquo)

return function()

var args = Arrayprototypeslicecall(arguments 0) if((cgt=funslength))

c++ return funs[c-1]apply(this args)

function isArrayOfFunctions(f ) if(typeof f == lsquoobjectrsquo) return false if(flength)return false if(fconcat) return false if(fsplice) return false var i = 0 for(iltflength i++)

if(typeof f[i] == lsquofunctionrsquo) return false

return true

簡單的測試範例(testSerialjs)使用 fs 模組確定某個 path 是檔案然後讀取印出檔案內容這樣會用到兩層的 callback所以測試中有使用serial的版本與 nested callbacks的版本做對照

var serial = require(lsquoserialrsquo) fs = require(lsquofsrsquo) path = lsquodclientjsrsquo cb = serial([ func-tion(err data)

if(err)

if(dataisFile) fsreadFile(path cb)

22 2 JavaScript與 NodeJS

Nodejs中文電子書

else consolelog(err)

function(err data)

if(err) consolelog(lsquo[ attened by searial]rsquo) con-solelog(datatoString(lsquoutf8rsquo))

else consolelog(err)

]) fsstat(path cb)

fsstat(path function(err data) 第一層 callback if(err)

if(dataisFile)

fsreadFile(path function(err data) 第二層 callback if(err)

consolelog(lsquo[nested callbacks]rsquo) con-solelog(datatoString(lsquoutf8rsquo))

else consolelog(err)

)

else consolelog(err)

)

關鍵在於這些 callback的執行是有順序性的所以利用 serial返回的一個函數 cb來取代這些 callback然後在 cb中控制每次會循序呼叫的函數

26 流程控制 23

Nodejs中文電子書

就可以把巢狀的 callback攤平成循序的 function陣列(就是傳給 serial函數的參數)

測試中的dclientjs 是一個簡單的 dnode 測試程式放在跟 testSerialjs同一個目錄

var dnode = require(lsquodnodersquo)

dnodeconnect(8000 lsquolocalhostrsquo function(remote)

remoterestart(function(str) consolelog(str) processexit()

)

)

執行測試程式後出現結果

[ attened by searial] var dnode = require(lsquodnodersquo)

dnodeconnect(8000 lsquolocalhostrsquo function(remote)

remoterestart(function(str) consolelog(str) processexit()

)

)

[nested callbacks] var dnode = require(lsquodnodersquo)

dnodeconnect(8000 lsquolocalhostrsquo function(remote)

remoterestart(function(str) consolelog(str) processexit()

)

)

對照起來看兩種寫法的結果其實是一樣的但是利用 serialjs巢狀的 callback結構就會消失

不過這樣也只限於順序單純的狀況如果函數執行的順序比較複雜

(不只是一直線)還是需要用功能更完整的流程控制模組比較好例如

httpsgithubcomcaolanasync

24 2 JavaScript與 NodeJS

3

Nodejs安裝與設定

本篇將講解如何在各個不同 OS建立 NodeJS環境目前 NodeJS 048版本環境架設方式需依賴 Linux指令才可編譯完成當然在不同作業系統中也已經有 NodeJS package可以直接使用指令快速架設以下各不同作業系統解說如何安裝 NodeJS

31 Ubuntu Linux

更新推薦使用 nvm

git clone gitgithubcomcreationixnvmgit ~nvm

echo rdquo ~nvmnvmshrdquo gtgt ~bashrc

nvm install v0614

nvm alias default v0614

以 上 可 參 考 http dreamerslabcom blog tw how-to-setup-a-node-js-development-environment-on-ubuntu-11-04

使用 APT套件管理工具是常見的方法以下是使用社群提供的 PPA安裝方式

sudo apt-get install python-software-properties

sudo add-apt-repository ppachris-leanodejs-devel

25

Nodejs中文電子書

sudo apt-get update

sudo apt-get install nodejs

32 Other Linux

Linux很適合作為 NodeJS的伺服器作業系統及開發環境安裝前請先確認以下套件已正確安裝

bull curl (wget)用來下載檔案的工具

bull git先進的版本控制工具

bull g++ GNU C++軟體編譯工具

bull make GNU軟體專案建置工具

安裝指令如下如設有權限問題請在指令前面加上 sudo

git clone httpsgithubcomjoyentnodegit

cd node

git checkout v067

configure

make

sudo make install

接著測試 nodeJS是否正常執行

node --version

出現版本訊息即表示安裝成功

33 Windows

nodeJS 在 v060 版本之後開始正式支援 windows native直接使用nodeexe 就可以執行程式支援性完全與 linux 相同更棒的部份就是不需經過編譯經過下載之後簡單設定完成立即開發 node程式

26 3 Nodejs安裝與設定

Nodejs中文電子書

下載 nodejs安裝檔案 lthttpnodejsorgdownloadgt

如此完成 windows native nodeexe安裝接著可以進入 command line執行測試在 command line輸入rdquonoderdquo會出現 nodejs版本訊息畫面表示安裝完成

33 Windows 27

Nodejs中文電子書

28 3 Nodejs安裝與設定

4

Nodejs基礎

前篇文章已經由介紹安裝至設定都有完整介紹nodeJS 內部除了javascript常用的函式 (function)物件 (object)之外也有許多不同的自訂物件nodeJS預設建立這些物件為核心物件是為了要讓開發流程更為這些資料在官方文件已經具有許多具體說明接下來將會介紹在開發 nodeJS程式時常見的物件特性與使用方法

41 nodejs http伺服器建立

在 lsquonodejs官方網站 lthttpnodejsorggtlsquo裡面有舉一個最簡單的 HTTP伺服器建立一開始初步就是建立一個伺服器平台讓 nodejs可以與瀏覽器互相行為每種語言一開始的程式建立都是以 Hello world開始最初也從 Hello world帶各位進入 nodejs的世界

輸入以下程式碼儲存檔案為 node basic http hello worldjs

var server

ip = rdquo127001rdquo

port = 1337

http = require(rsquohttprsquo)

server = httpcreateServer(function (req res)

29

Nodejs中文電子書

reswriteHead(200 rsquoContent-Typersquo rsquotextplainrsquo)

resend(rsquoHello Worldnrsquo)

)

serverlisten(port ip)

consolelog(rdquoServer running at httprdquo + ip + rdquordquo + port)

程式碼解講一開始需要有幾個基本的變數

bull ip 機器本身的 ip位置因為使用本地端因此設定為 127001

bull port 需要開通的阜號通常設定為 http port 80因範例不希望與基本 port相衝隨意設定為 1337

在 nodejs 的程式中有許多預設的模組可以使用因此需要使用require方法將模組引入在這邊我們需要使用 http這個模組因此將 http載入Http模組裡面內建有許多方法可以使用這邊採用 createServer創建一個基本的 http伺服器再將 http伺服器給予一個 server變數裡面的回呼函式 (call back function)可以載入 http伺服器的資料與回應方法 (requestresponse)在程式裡面就可以看到我們直接回應給瀏覽器端所需的 Header回應內容

reswriteHead(200 rsquoContent-Typersquo rsquotextplainrsquo)

resend(rsquoHello Worldnrsquo)

Http伺服器需要設定 port ip在最後需要設定 Http監聽需要使用到listen事件監聽所有 Http伺服器行為

httplisten(port ip)

所有事情都完成之後需要確認伺服器正確執行因此使用 console在javascript裡就有這個原生物件console所印出的資料都會顯示於 nodejs伺服器頁面這邊印出的資料並不會傳送到使用者頁面上之後許多除壞

(debug)都會用到 console物件

consolelog(rdquoServer running at httprdquo + ip + rdquordquo + port)

30 4 Nodejs基礎

Nodejs中文電子書

42 nodejs http路徑建立

前面已經介紹如何建立一個簡單的 http伺服器接下來本章節將會介紹如何處理伺服器路徑 (rout)問題在 http的協定下所有從瀏覽器發出的要求 (request)都需要經過處理路徑上的建立也是如此

路徑就是指伺服器 ip位置或者是網域名稱之後對於伺服器給予的要求修改剛才的 hello world檔案修改如下

server = httpcreateServer(function (req res)

consolelog(requrl)

reswriteHead(200 rsquoContent-Typersquo rsquotextplainrsquo)

resend(rsquohello worldnrsquo)

)

重新啟動 nodejs程式後在瀏覽器端測試一下路徑行為結果如下圖

當在瀏覽器輸入 http1270011337test 在伺服器端會收到兩個要求一個是我們輸入的test要求另外一個則是faviconicotest的路徑要求http伺服器本身需要經過程式設定才有辦法回應給瀏覽器端所需要的回應在伺服器中所有的路徑要求都是需要被解析才有辦法取得資料從

42 nodejs http路徑建立 31

Nodejs中文電子書

上面解說可以了解到在 nodejs當中所有的路徑都需要經過設定未經過設定的路由會讓瀏覽器無法取得任何資料導致錯誤頁面的發生底下將會解

說如何設定路由同時避免發生錯誤情形先前 nodejs程式需要增加一些修改才能讓使用者透過瀏覽器在不同路徑時有不同的結果根據剛才

的程式做如下的修改

var server

ip = rdquo127001rdquo

port = 1337

http = require(rsquohttprsquo)

url = require(rsquourlrsquo)

path

server = httpcreateServer(function (req res)

path = urlparse(requrl)

reswriteHead(200 rsquoContent-Typersquo rsquotextplainrsquo)

switch (pathpathname)

case rdquoindexrdquo

resend(rsquoI am indexnrsquo)

break

case rdquotestrdquo

resend(rsquothis is test pagenrsquo)

break

default

resend(rsquodefault pagenrsquo)

break

)

serverlisten(port ip)

consolelog(rdquoServer running at httprdquo + ip + rdquordquo + port)

程式做了片段的修改首先載入 url模組另外增加一個 path變數url模組就跟如同他的命名一般專門處理 url字串處理裡面提供了許多方法來解決路徑上的問題因為從瀏覽器發出的要求路徑可能會帶有多種需求

或者 GET參數組合等因此我們需要將路徑單純化取用路徑部分的資料即可例如使用者可能會送出 http1270011337testsend=1如果直接

32 4 Nodejs基礎

Nodejs中文電子書

信任 requrl就會收到結果為testsend=1所以需要透過 url模組的方法將路徑資料過濾

在這邊使用 urlparse的方法裡面帶入網址格式資料會回傳路徑資料為了後需方便使用將回傳的資料設定到 path變數當中在回傳的路徑資料裡面包含資訊如下圖

這邊只需要使用單純的路徑要求直接取用 pathpathname就可以達到我們的目的

最後要做路徑的判別在不同的路徑可以指定不同的輸出在範例中

有三個可能結果第一個從瀏覽器輸入index就會顯示 index結果test 就會呈現出 test頁面最後如果都不符合預期的輸入會直接顯示 default的頁面最後的預防可以讓瀏覽器不會出現非預期結果讓程式的可靠性提昇

底下為測試結果

42 nodejs http路徑建立 33

Nodejs中文電子書

43 nodejs檔案讀取

前面已經介紹如何使用路由(rount)做出不同的回應實際應用只有在瀏覽器只有輸出幾個文字資料總是不夠的在本章節中將介紹如何使

用檔案讀取輸出檔案資料讓使用者在前端瀏覽器也可以讀取到完整的

html css javascript檔案輸出

檔案管理最重要的部分就是 lsquoFile system lthttpnodejsorgdocslatestapifshtmlgtlsquo這個模組此模組可以針對檔案做管理監控讀取等行為裡面有許多預設的方法底下是檔案輸出的基本範例底下會有兩個檔案

第一個是靜態 html檔案

ltDOCTYPE htmlgt

lthtml xmlns=rdquohttpwwww3org1999xhtmlrdquo xmllang=rdquozh-CNrdquo lang=rdquozh-CNrdquogt

ltheadgt

ltmeta http-equiv=rdquoContent-Typerdquo content=rdquotexthtml charset=UTF-7rdquo gt

lttitlegtnodejs index html filelttitlegt

ltheadgt

ltbodygt

34 4 Nodejs基礎

Nodejs中文電子書

lth1gtnodejs index html filelth1gt

ltbodygt

lthtmlgt

另一個為 nodejs程式

var fs = require(rdquofsrdquo)

filename = rdquostaticindexhtmlrdquo

encode = rdquoutf8rdquo

fsreadFile(filename encode function(err file)

consolelog(file)

)

一開始直接載入 le system模組 載入名稱為 fs讀取檔案主要使

用的方法為 readFile裡面以三個參數路徑 ( le path) 編碼方式 (encoding)

回應函式 (callback)路徑必須要設定為靜態 html所在位置才能指定到正確的檔案靜態檔案的編碼方式也必須正確這邊使用靜態檔案的編

碼為 utf8如果編碼設定錯誤nodejs讀取出來檔案結果會使用 byte raw格式輸出如果錯誤編碼格式會導致輸出資料為 byte raw

回應函式中裡面會使用兩個變數error為錯誤資訊如果讀取的檔案不存在或者發生錯誤error數值會是 true如果成功讀取資料 error將會是 falsecontent則是檔案內容資料讀取後將會把資料全數丟到 content這個變數當中

最後程式的輸出結果畫面如下

43 nodejs檔案讀取 35

Nodejs中文電子書

44 nodejs http靜態檔案輸出

前面已經了解如何讀取本地端檔案接下來將配合 http伺服器路由讓每個路由都能夠輸出相對應的靜態 html檔案首先新增加幾個靜態 html檔案

ltDOCTYPE htmlgt

lthtml xmlns=rdquohttpwwww3org1999xhtmlrdquo xmllang=rdquozh-CNrdquo lang=rdquozh-CNrdquogt

ltheadgt

ltmeta http-equiv=rdquoContent-Typerdquo content=rdquotexthtml charset=UTF-7rdquo gt

lttitlegtnodejs index html filelttitlegt

ltheadgt

ltbodygt

lth1gtnodejs index html filelth1gt

ltbodygt

lthtmlgt

ltDOCTYPE htmlgt

lthtml xmlns=rdquohttpwwww3org1999xhtmlrdquo xmllang=rdquozh-CNrdquo lang=rdquozh-CNrdquogt

ltheadgt

ltmeta http-equiv=rdquoContent-Typerdquo content=rdquotexthtml charset=UTF-7rdquo gt

lttitlegtnodejs test html filelttitlegt

ltheadgt

ltbodygt

lth1gtnodejs test html filelth1gt

ltbodygt

lthtmlgt

ltDOCTYPE htmlgt

lthtml xmlns=rdquohttpwwww3org1999xhtmlrdquo xmllang=rdquozh-CNrdquo lang=rdquozh-CNrdquogt

ltheadgt

ltmeta http-equiv=rdquoContent-Typerdquo content=rdquotexthtml charset=UTF-7rdquo gt

lttitlegtnodejs static html filelttitlegt

ltheadgt

ltbodygt

lth1gtnodejs static html filelth1gt

ltbodygt

lthtmlgt

36 4 Nodejs基礎

Nodejs中文電子書

準備一個包含基本路由功能的 http伺服器

var server

ip = rdquo127001rdquo

port = 1337

http = require(rsquohttprsquo)

url = require(rsquourlrsquo)

server = httpcreateServer(function (req res)

var path = urlparse(requrl)

)

serverlisten(port ip)

consolelog(rdquoServer running at httprdquo + ip + rdquordquo + port)

加入 le system 模組使用 readFile 的功能將這一段程式放置於

createServer的回應函式中

fsreadFile(filePath encode function(err file)

)

readFile的回應函式裡面加入頁面輸出讓瀏覽器可以正確讀到檔案在這邊我們設定讀取的檔案為 html 靜態檔案所以 Content-type 設定為texthtml讀取到檔案的內容將會正確輸出成 html靜態檔案

fsreadFile(filePath encode function(err file)

reswriteHead(200 rsquoContent-Typersquo rsquotexthtmlrsquo)

reswrite(file)

resend()

)

到這邊為止基本的程式內容都已經完成剩下一些細節的調整首先

路徑上必須做調整目前的靜態檔案全部都放置於 static資料夾底下設定一個變數來記住資料夾位置

接著將瀏覽器發出要求路徑與資料夾組合讀取正確 html靜態檔案使用者有可能會輸入錯誤路徑所以在讀取檔案的時候要加入錯誤處理

同時回應 404伺服器無法正確回應的 http header格式

加入這些細節的修改一個基本的 http 靜態 html輸出伺服器就完成了完整程式碼如下

44 nodejs http靜態檔案輸出 37

Nodejs中文電子書

var server

ip = rdquo127001rdquo

port = 1337

http = require(rsquohttprsquo)

fs = require(rdquofsrdquo)

folderPath = rdquostaticrdquo

url = require(rsquourlrsquo)

path

filePath

encode = rdquoutf8rdquo

server = httpcreateServer(function (req res)

path = urlparse(requrl)

filePath = folderPath + pathpathname

fsreadFile(filePath encode function(err file)

if (err)

reswriteHead(404 rsquoContent-Typersquo rsquotextplainrsquo)

resend()

return

reswriteHead(200 rsquoContent-Typersquo rsquotextapplicationrsquo)

reswrite(file)

resend()

)

)

serverlisten(port ip)

consolelog(rdquoServer running at httprdquo + ip + rdquordquo + port)

45 nodejs http GET資料擷取

http伺服器中除了路由之外另一個最常使用的方法就是擷取 GET資料本單元將會介紹如何透過基本 http伺服器擷取瀏覽器傳來的要求擷取 GET資料

在 http協定中GET參數都是藉由 URL從瀏覽器發出要求送至伺服器

38 4 Nodejs基礎

Nodejs中文電子書

端基本的傳送網址格式可能如下

http127001testsend=1amptest=2

上面這段網址裡面的 GET參數就是 send而這個變數的數值就為 1如果想要在 http伺服器取得 GET資料需要在瀏覽器給予的要求 (request)做處理

首先需要載入 query string這個模組這個模組主要是用來將字串資料

過濾後轉換成javascript物件

qs = require(rsquoquerystringrsquo)

接著在第一階段利用 url模組過濾瀏覽器發出的 URL資料後將回應的物件裡面的 query這個變數是一個字串值資料過濾後如下

send=1amptest=2

透過 query string使用 parse這個方法將資料轉換成 javascript物件就表示 GET的資料已經被伺服器端正式擷取下來

path = urlparse(requrl)

parameter = qsparse(pathquery)

整個 nodejs http GET參數完整擷取程式碼如下

var server

ip = rdquo127001rdquo

port = 1337

http = require(rsquohttprsquo)

qs = require(rsquoquerystringrsquo)

url = require(rsquourlrsquo)

server = httpcreateServer(function (req res)

var path = urlparse(requrl)

parameter = qsparse(pathquery)

consoledir(parameter)

reswriteHead(200 rsquoContent-Typersquo rsquotextplainrsquo)

reswrite(rsquoBrowser test GET parameternrsquo)

resend()

)

45 nodejs http GET資料擷取 39

Nodejs中文電子書

serverlisten(port ip)

consolelog(rdquoServer running at httprdquo + ip + rdquordquo + port)

程式運作之後由瀏覽器輸入要求網址之後nodejs伺服器端回應資料為

Server running at http1270011337

send rsquo1rsquo test rsquo1rsquo

46 本章結語

前面所解說的部份一大部分主要是處理 http伺服器基本問題雖然在某些部分有牽扯到 http 伺服器基本運作原理主要還是希望可以藉由這些基本範例練習 nodejs練習回應函式與語法串接的特點習慣編寫javascript風格程式當然直接這樣開發 nodejs是非常辛苦的接下來在模組實戰開發的部份將會介紹特定的模組一步一步帶領各位從無到有進行

nodejs應用程式開發

40 4 Nodejs基礎

5

NPM套件管理工具

npm全名為 Node Package Manager是 Nodejs的套件(package)管理工具類似 Perl的 ppm或 PHP的 PEAR等安裝 npm後使用npm install

module name指令即可安裝新套件維護管理套件的工作會更加輕鬆

npm可以讓Nodejs的開發者直接利用擴充線上的套件庫(packagesregistry)加速軟體專案的開發npm提供很友善的搜尋功能可以快速找到安裝需要的套件當這些套件發行新版本時npm也可以協助開發者自動更新這些套件

npm不僅可用於安裝新的套件它也支援搜尋列出已安裝模組及更新的功能

51 安裝 NPM

Nodejs在 063版本開始內建 npm讀者安裝的版本若是此版本或更新的版本就可以略過以下安裝說明

若要檢查 npm是否正確安裝可以使用以下的指令

npm -v

41

Nodejs中文電子書

執行結果說明

若 npm正確安裝執行 npm -v將會看到類似 110-2的版本訊息

若讀者安裝的 Nodejs版本比較舊或是有興趣嘗試自己動手安裝 npm工具則可以參考以下的說明

安裝於Windows系統

Nodejs for Windows 於 062 版開始內建 npm使用 nodejsorg 官方提供的安裝程式不需要進一步的設定就可以立即使用 npm指令對於Windows的開發者來說大幅降低環境設定的問題與門檻

除了使用 Nodejs內建的 npm讀者也可以從 npm官方提供的以下網址

httpnpmjsorgdist

這是由 npm提供的 Fancy Windows Install版本請下載壓縮檔(例如npm-110-3zip)並將壓縮檔內容解壓縮至 Nodejs的安裝路徑(例如CProgram Filesnodejs)

解壓縮後在 Nodejs的安裝路徑下應該有以下的檔案及資料夾

bull npmcmd(檔案)

bull node modules(資料夾)

安裝於 Linux系統

Ubuntu Linux的使用者可以加入 NPM Unoffcial PPA這個 repository即可使用 apt-get完成 npm安裝

42 5 NPM套件管理工具

Nodejs中文電子書

Ubuntu Linux使用 apt-get安裝 npm

sudo apt-get install python-software-properties

sudo add-apt-repository ppagias-kay-leenpm

sudo apt-get update

sudo apt-get install npm

npm官方提供的安裝程式 installsh可以適用於大多數的 Linux系統使用這個安裝程式請先確認

1 系統已安裝 curl工具(請使用 curl --version查看版本訊息)

2 已安裝 Nodejs並且 PATH正確設置

3 Nodejs的版本必須大於 04x

以下為 npm提供的安裝指令

curl httpnpmjsorginstallsh | sh

安裝成功會看到如下訊息

installsh安裝成功的訊息

npm10105 homeUSERNAMElocalnodelibnode_modulesnpm

It worked

安裝於Mac OS X

建議採用與 Nodejs相同的方式進行 npm的安裝例如使用MacPorts安裝 Nodejs就同樣使用MacPorts安裝 npm這樣對日後的維護才會更方便容易

使用 MacPorts安裝 npm是本書比較建議的方式它可以讓 npm的安裝移除及更新工作自動化將會幫助開發者節省寶貴時間

51 安裝 NPM 43

Nodejs中文電子書

安裝MacPorts的提示

在MacPorts網站可以取得 OS X系統版本對應的安裝程式(例如 106或 107)httpwwwmacportsorg安裝過程會詢問系統管理者密碼使用預設的選項完成安裝即可安

裝MacPorts之後在終端機執行 port -v將會看到MacPorts的版本訊息

安裝 npm之前先更新MacPorts的套件清單以確保安裝的 npm是最新版本

sudo port -d selfupdate

接著安裝 npm

sudo port install npm

若讀者的 Nodejs並非使用MacPorts安裝則不建議使用MacPorts安裝npm因為 MacPorts會自動檢查並安裝相依套件而 npm相依 nodejs所以MacPorts也會一併將 nodejs套件安裝造成先前讀者使用其它方式安裝的 nodejs被覆蓋

讀者可以先使用 MacPorts安裝 curl(sudo port install curl)

再參考 Linux的 installsh安裝方式即可使用 npm官方提供的安裝程式

NPM安裝後測試

npm是指令列工具(command-line tool)使用時請先打開系統的文字終端機工具

測試 npm安裝與設定是否正確請輸入指令如下

npm -v

或是

npm --version

如果 npm已經正確安裝設定就會顯示版本訊息

44 5 NPM套件管理工具

Nodejs中文電子書

執行結果(範例)

110-2

52 使用 NPM安裝套件

npm目前擁有超過 6000種套件(packages)可以在 npm registry使用關鍵字搜尋套件

httpsearchnpmjsorg

舉例來說在關鍵字欄位輸入「coffee-script」下方的清單就會自動列出包含 coffee-script關鍵字的套件

接著我們回到終端機模式的操作npm的指令工具本身就可以完成套

件搜尋的任務

例如以下的指令同樣可以找出 coffee-script相關套件

npm search coffee-script

以下是搜尋結果的參考畫面

52 使用 NPM安裝套件 45

Nodejs中文電子書

找到需要的套件後(例如 express)即可使用以下指令安裝

npm install coffee-script

值得注意的一點是使用 npm install會將指定的套件安裝在工

作目錄(Working Directory)的 node modules資料夾下

以Windows為例如果執行 npm install的目錄位於

Cproject1

那麼 npm將會自動建立一個 node modules的子目錄(如果不存在)

Cproject1node modules

並且將下載的套件放置於這個子目錄例如

Cproject1node modulescoffee-script

這個設計讓專案可以個別管理相依的套件並且可以在專案佈署或發

行時將這些套件(位於 node modules)一併打包方便其它專案的使用者不必再重新下載套件

這個 npm install的預設安裝模式為 local(本地)只會變更當前專案的資料夾不會影響系統

另一種安裝模式稱為 global(全域)這種模式會將套件安裝到系統資

料夾也就是 npm安裝路徑的 node modules資料夾例如

CProgram Filesnodejsnode modules

46 5 NPM套件管理工具

Nodejs中文電子書

是否要使用全域安裝可以依照套件是否提供新指令來判斷舉例來說express 套件提供 express 這個指令而 coffee-script 則提供 coffee

指令

在 local 安 裝 模 式 中這 些 指 令 的 程 式 檔 案會 被 安 裝 到node modules的 bin這個隱藏資料夾下除非將bin的路徑加入 PATH環境變數否則要執行這些指令將會相當不便

為了方便指令的執行我們可以在 npm install 加上 -g 或 --

global參數啟用 global安裝模式例如

npm install -g coffee-script

npm install -g express

使用 global安裝模式需要注意執行權限的問題若權限不足可能會出現類似以下的錯誤訊息

npm ERR Error EACCES permission denied rsquorsquo

npm ERR

npm ERR Please try running this command again as rootAdministrator

要獲得足夠得執行權限請參考以下說明

bull Windows 7 或 2008 以上在「命令提示字元」的捷徑按右鍵選擇「以系統管理員身分執行」執行 npm指令時就會具有 Administrator身分

bull Mac OS X或 Linux系統可以使用 sudo指令例如

sudo npm install -g express

bull Linux系統可以使用 root權限登入或是以「sudo su -」切換成 root身分(使用 root權限操作系統相當危險因此並不建議使用這種方式)

若加上 -g參數使用 npm install -g coffee-script完成安裝

後就可以在終端機執行 coffee指令例如

coffee -v

52 使用 NPM安裝套件 47

Nodejs中文電子書

執行結果(範例)

CoffeeScript version 120

53 套件的更新及維護

除了前一節說明的 search 及 install 用法npm 還提供其他許多指令(commands)

使用 npm help可以查詢可用的指令

npm help

執行結果(部分)

where ltcommandgt is one of

adduser apihelp author bin bugs c cache completion

config deprecate docs edit explore faq find get

help help-search home i info init install la link

list ll ln login ls outdated owner pack prefix

prune publish r rb rebuild remove restart rm root

run-script s se search set show star start stop

submodule tag test un uninstall unlink unpublish

unstar up update version view whoami

使用 npm help command可以查詢指令的詳細用法例如

npm help list

接下來本節要介紹開發過程常用的 npm指令

使用 list可以列出已安裝套件

npm list

48 5 NPM套件管理工具

Nodejs中文電子書

執行結果(範例)

-- coffee-script120

- express256

- connect185

| -- formidable108

-- mime124

-- mkdirp007

-- qs041

檢視某個套件的詳細資訊例如

npm show express

升級所有套件(如果該套件已發佈更新版本)

npm update

升級指定的套件

npm update express

移除指定的套件

npm uninstall express

54 使用 packagejson

對於正式的 Nodejs專案可以建立一個命名為 packagejson的設

定檔(純文字格式)檔案內容參考範例如下

54 使用 packagejson 49

Nodejs中文電子書

packagejson(範例)

rdquonamerdquo rdquoapplication-namerdquo

rdquoversionrdquo rdquo001rdquo

rdquoprivaterdquo true

rdquodependenciesrdquo

rdquoexpressrdquo rdquo255rdquo

rdquocoffee-scriptrdquo rdquolatestrdquo

rdquomongooserdquo rdquogt= 253rdquo

其中 name與 version依照專案的需求設置

需要注意的是 dependencies的設定它用於指定專案相依的套件名

稱及版本

bull rdquoexpressrdquo rdquo255rdquo

代表此專案相依版本 255的 express套件

bull rdquocoffee-scriptrdquo rdquolatestrdquo

使用最新版的 coffee-script套件(每次更新都會檢查新版)

bull rdquomongooserdquo rdquogt= 253rdquo

使用版本大於 253的 mongoose套件

假設某個套件的新版可能造成專案無法正常運作就必須指定套件的

版本避免專案的程式碼來不及更新以相容新版套件通常在開發初期的

專案需要盡可能維持新套件的相容性(以取得套件的更新或修正)可以

用「gt=」設定最低相容的版本或是使用「latest」設定永遠保持最新

套件

50 5 NPM套件管理工具

6

Express介紹

在前面的 nodejs基礎當中介紹許多許多開設 http的使用方法及介紹以及許多基本的 nodejs基本應用

接下來要介紹一個套件稱為 express [Express](httpexpressjscom)這個套件主要幫忙解決許多 nodejs http server所需要的基本服務讓開發 httpservice變得更為容易不需要像之前需要透過層層模組(module)才有辦法開始編寫自己的程式

這個套件是由 TJ Holowaychuk 製作而成的套件裡面包含基本的路由處理 (route)http資料處理(GETPOSTPUT)另外還與樣板套件(jshtml template engine)搭配同時也可以處理許多複雜化的問題

61 Express安裝

安裝方式十分簡單只要透過之前介紹的 NPM就可以使用簡單的指令安裝指令如下

這邊建議需要將此套件安裝成為全域模組方便日後使用

51

Nodejs中文電子書

62 Express基本操作

express的使用也十分簡單先來建立一個基本的 hello world

var app = require(rsquoexpressrsquo)createServer()

port = 1337

applisten(port)

appget(rsquorsquo function(req res)

ressend(rsquohello worldrsquo)

)

consolelog(rsquostart express servernrsquo)

可以從上面的程式碼發現基本操作與 nodejs http的建立方式沒有太大差異主要差在當我們設定路由時可以直接透過 appget方式設定回應與接受方式

63 Express路由處理

Express對於 http服務上有許多包裝讓開發者使用及設定上更為方便例如有幾個路由設定那我們就統一藉由 appget來處理

Create http server

appget(rsquorsquo function(req res)

ressend(rsquohello worldrsquo)

)

appget(rsquotestrsquo function(req res)

ressend(rsquotest renderrsquo)

)

appget(rsquouserrsquo function(req res)

ressend(rsquouser pagersquo)

)

52 6 Express介紹

Nodejs中文電子書

如上面的程式碼所表示appget可以帶入兩個參數第一個是路徑名稱設定第二個為回應函式 (call back function)回應函式裡面就如同之前的 createServer方法裡面包含 requestresponse兩個物件可供使用使用者就可以透過瀏覽器輸入不同的 url切換到不同的頁面顯示不同的結果

路由設定上也有基本的配對方式讓使用者從瀏覽器輸入的網址可以

是一個變數只要符合型態就可以有對應的頁面產出例如

Create http server

appget(rsquouseridrsquo function(req res)

ressend(rsquouser rsquo + reqparamsid)

)

appget(rsquonumberrsquo function(req res)

ressend(rsquonumber rsquo + reqparamsnumber)

)

裡 面 使 用 到number 從 網 址 輸 入 之 後 就 可 以 直 接 使 用reqparamsnumber 取得所輸入的資料變成 url 參數使用當然前面也是可以加上路徑的設定userid在瀏覽器上路徑必須符合userxxx透

過 reqparamsid就可以取到 xxx這個字串值

另外express參數處理也提供了路由參數配對處理也可以透過正規表示法作為參數設定

var app = require(rsquoexpressrsquo)createServer()

port = 1337

applisten(port)

appget(^ip((d23)((d23))((d23))((d23))) function(req res)

ressend(reqparams)

)

上面程式碼可以發現後面路由設定的型態是正規表示法裡面設定

格式為ip之後必須要加上 ip型態才會符合資料格式同時取得 ip資料已經由正規表示法將資料做分群因此可以取得 ip的四個數字

此程式執行之後可以透過瀏覽器測試輸入網址為 localhost3000ip25525510010可以從頁面獲得資料

63 Express路由處理 53

Nodejs中文電子書

此章節全部範例程式碼如下

overview

author Caesar Chi

blog clonnblogspotcom

version 20120226

create server

var app = require(rsquoexpressrsquo)createServer()

port = 1337

applisten(port)

normal style

appget(rsquorsquo function(req res)

ressend(rsquohello worldrsquo)

)

appget(rsquotestrsquo function(req res)

ressend(rsquotest renderrsquo)

)

parameter style

appget(rsquouseridrsquo function(req res)

ressend(rsquouser rsquo + reqparamsid)

)

appget(rsquonumberrsquo function(req res)

ressend(rsquonumber rsquo + reqparamsnumber)

)

REGX style

appget(^ip((d23)((d23))((d23))) function(req res)

ressend(reqparams)

)

appget(rsquorsquo function(req res)

ressend(rsquoPage not foundrsquo 404)

54 6 Express介紹

Nodejs中文電子書

)

consolelog(rsquostart express servernrsquo)

64 Express middleware

Express 裡面有一個十分好用的應用概念稱為 middleware可以透過middleware做出複雜的效果同時上面也有介紹 next方法參數傳遞就是靠 middleware的概念來傳遞參數讓開發者可以明確的控制程式邏輯

create http server

appuse(expressbodyParser())

appuse(expressmethodOverride())

appuse(expresssession())

上面都是一種 middleware的使用方式透過 appuse方式裡面載入函式執行方法回應函式會包含三個基本參數responserequestnext其中next表示下一個 middleware執行函式同時會自動將預設三個參數繼續帶往下個函式執行底下有個實驗

上面的片段程式執行後開啟瀏覽器連結上 localhost1337會發現伺服器回應結果順序如下

first middle ware

second middle ware

execute middle ware

end middleware function

從上面的結果可以得知剛才設定的 middleware都生效了在 appuse設定的 middleware是所有 url皆會執行方法如果有指定特定方法就可以使用 appget的 middleware設定在 appget函式的第二個參數就可以帶入函式或者是匿名函式只要函式裡面最後會接受 request response next這三個參數同時也有正確指定 next函式的執行時機最後都會執行到最後一個方法當然開發者也可以評估程式邏輯要執行到哪一個階段讓邏輯

可以更為分明

64 Express middleware 55

Nodejs中文電子書

65 Express路由應用

在實際開發上可能會遇到需要使用參數等方式混和變數一起使用

express裡面提供了一個很棒的處理方法 appall這個方式可以先採用基本路由配對再將設定為每個不同的處理方式開發者可以透過這個方式簡

化自己的程式邏輯

overview

author

version 20120226

create server

var app = require(rsquoexpressrsquo)createServer()

port = 1337

users = [

name rsquoClonnrsquo

name rsquoChirsquo

]

applisten(port)

appall(rsquouseridoprsquo function(req res next)

requser = users[reqparamsid]

if (requser)

next()

else

next(new Error(rsquocannot find user rsquo + reqparamsid))

)

appget(rsquouseridrsquo function(req res)

ressend(rsquoviewing rsquo + requsername)

)

appget(rsquouserideditrsquo function(req res)

ressend(rsquoediting rsquo + requsername)

)

56 6 Express介紹

Nodejs中文電子書

appget(rsquouseriddeletersquo function(req res)

ressend(rsquodeleting rsquo + requsername)

)

appget(rsquorsquo function(req res)

ressend(rsquoPage not foundrsquo 404)

)

consolelog(rsquostart express servernrsquo)

內部宣告一組預設的使用者分別給予名稱設定藉由 appall這個方法可以先將路由雛形建立再接下來設定 appget的路徑格式只要符合格式就會分配進入對應的方法中像上面的程式當中如果使用者輸入路徑為user0除了執行 appall程式之後執行 next方法就會對應到路徑設定為userid的這個方法當中如果使用者輸入路徑為user0edit就會執行到useridedit的對應方法

66 Express GET應用範例

我們準備一個使用 GET方法傳送資料的表單

ltDOCTYPE htmlgt

lthtmlgt

ltheadgt

ltmeta http-equiv=rdquoContent-Typerdquo content=rdquotexthtml charset=utf8rdquogt

lttitlegtNodejs 菜鳥筆記 (1)lttitlegt

ltlink rel=rdquostylesheetrdquo href=rdquocssstylecssrdquo type=rdquotextcssrdquo media=rdquoallrdquo gt

ltheadgt

ltbodygt

lth1gtNodejs 菜鳥筆記-註冊lth1gt

ltform id=rdquosignuprdquo method=rdquoGETrdquo action=rdquohttplocalhost3000Signuprdquogt

ltlabelgt 使用者名稱ltlabelgtltinput type=rdquotextrdquo id=rdquousernamerdquo name=rdquousernamerdquo gtltbrgt

ltlabelgt 電子郵件ltlabelgtltinput type=rdquotextrdquo id=rdquoemailrdquo name=rdquoemailrdquo gtltbrgt

ltinput type=rdquosubmitrdquo value=rdquo 註冊我的帳號rdquo gtltbrgt

ltformgt

ltbodygt

lthtmlgt

66 Express GET應用範例 57

Nodejs中文電子書

這個表單沒有什麼特別的地方我們只需要看第 9 行form 使用的method是 GET然後 action是rdquohttplocalhost3000Signupldquo等一下我們要來撰寫Signup這個 URL Path的處理程式

處理 Signup行為

我們知道所謂的 GET方法會透過 URL來把表單的值給帶過去以上面的表單來說到時候 URL會以這樣的形式傳遞

httplocalhost3000Signupusername=xxxampemail=xxx

所以要能處理這樣的資料必須有以下功能

bull 解析 URL

bull 辨別動作是 Signup

bull 解析出 username和 email

一旦能取得 username和 email的值程式就能加以應用了

處理 Signup的程式碼雛形

load module

var url = require(rsquourlrsquo)

urlData = urlparse(requrltrue)

action = urlDatapathname

reswriteHead(200 rdquoContent-Typerdquordquotexthtml charset=utf-8rdquo)

if (action === rdquoSignuprdquo)

user = urlDataquery

resend(rdquolth1gtrdquo + userusername + rdquo 歡迎您的加入 lth1gtltpgt 我們已經將會員啟用信寄至rdquo + useremail + rdquoltpgtrdquo)

首先需要加載 url module它是用來協助我們解析 URL的模組接著使用 urlparse方法第一個傳入 url字串變數也就是 requrl另外第二個參數的用意是設為 ture則引進 querystring模組來協助處理預設是 false它影響到的是 urlDataquery設為 true會傳回物件不然就只是一般的字串urlparse會將字串內容整理成一個物件我們把它指定給 urlData

action變數作為記錄 pathname這是我們稍後要來判斷目前網頁的動作是什麼接著先將 html表頭資訊 (Header)準備好再來判斷路徑邏輯如

58 6 Express介紹

Nodejs中文電子書

果是 Signup這個動作就把 urlDataquery裡的資料指定給 user然後輸出userusername和 useremail把使用者從表單註冊的資料顯示於頁面中

最後進行程式測試啟動 nodejs主程式之後開啟瀏覽器就會看到表單填寫完畢按下送出就可以看到結果了

完整 nodejs程式碼如下

var http = require(rsquohttprsquo)

url = require(rsquourlrsquo)

fs = require(rdquofsrdquo)

server

server = httpcreateServer(function (reqres)

var urlData

encode = rdquoutf8rdquo

filePath = rdquoviewexpress_get_example_formhtmlrdquo

action

urlData = urlparse(requrltrue)

action = urlDatapathname

reswriteHead(200 rdquoContent-Typerdquordquotexthtml charset=utf-8rdquo)

if (action === rdquoSignuprdquo)

user = urlDataquery

resend(rdquolth1gtrdquo + userusername + rdquo 歡迎您的加入 lth1gtltpgt 我們已經將會員啟用信寄至rdquo + useremail + rdquoltpgtrdquo)

else

fsreadFile(filePath encode function(err file)

reswrite(file)

resend()

)

)

serverlisten(3000)

consolelog(rsquoServer 跑起來了現在時間是rsquo + new Date())

66 Express GET應用範例 59

Nodejs中文電子書

67 Express POST應用範例

一開始準備基本的 html表單傳送內容以 POST方式form的 action屬性設定為 POST其餘 html內容與前一個範例應用相同

ltDOCTYPE htmlgt

lthtmlgt

ltheadgt

ltmeta http-equiv=rdquoContent-Typerdquo content=rdquotexthtml charset=utf8rdquogt

lttitlegtNodejs 菜鳥筆記 (1)lttitlegt

ltlink rel=rdquostylesheetrdquo href=rdquocssstylecssrdquo type=rdquotextcssrdquo media=rdquoallrdquo gt

ltheadgt

ltbodygt

lth1gtNodejs 菜鳥筆記-註冊lth1gt

ltform id=rdquosignuprdquo method=rdquoPOSTrdquo action=rdquohttplocalhost3000Signuprdquogt

ltlabelgt 使用者名稱ltlabelgtltinput type=rdquotextrdquo id=rdquousernamerdquo name=rdquousernamerdquo gtltbrgt

ltlabelgt 電子郵件ltlabelgtltinput type=rdquotextrdquo id=rdquoemailrdquo name=rdquoemailrdquo gtltbrgt

ltinput type=rdquosubmitrdquo value=rdquo 註冊我的帳號rdquo gtltbrgt

ltformgt

ltbodygt

lthtmlgt

nodejs的程式處理邏輯與前面 GET範例類似部分程式碼如下

qs = require(rsquoquerystringrsquo)

if (action === rdquoSignuprdquo)

formData = rsquorsquo

reqon(rdquodatardquo function (data)

formData += data

)

reqon(rdquoendrdquo function ()

user = qsparse(formData)

resend(rdquolth1gtrdquo + userusername + rdquo 歡迎您的加入 lth1gtltpgt 我們已經將會員啟用信寄至rdquo + useremail + rdquoltpgtrdquo)

)

60 6 Express介紹

Nodejs中文電子書

主要加入了rsquoquerystringrsquo這個moduel方便我們等一下解析由表單 POST回來的資料另外加入一個 formData的變數用來搜集待等一下表單回傳的資料前面的 GET範例我們只從 req拿出 url的資料這次要在利用req身上的事件處理

JavaScript 在訂閱事件時使用 addEventListener而 nodejs 使用的則是on這邊加上了監聽 data的事件會在瀏覽器傳送資料到Web Server時被執行參數是它所接收到的資料型態是字串

接著再增加 end的事件當瀏覽器的請求事件結束時它就會動作

由於瀏覽器使用 POST在上傳資料時會將資料一塊塊地上傳因為我們在監聽 data事件時透過 formData變數將它累加起來lt不過由於我們

上傳的資料很少一次就結束不過如果日後需要傳的是資料比較大的檔

案這個累加動作就很重要

當資料傳完就進到 end 事件中會用到 qsparse 來解析 formDataformData的內容是字串內容是

username=wordsmithampemail=wordsmith40somewhere

而 qsparse可以幫我們把這個 querystring轉成物件的格式也就是

username=wordsmithampemail=wordsmith40somewhere

一旦轉成物件並指定給 user之後其他的事情就和 GET方法時操作的一樣寫 response的表頭將內容回傳並將 userusername和 useremail代入到內容中

修改完成後接著執行 nodejs程式啟動 web server開啟瀏覽器進入表單測試看看POST的方式能否順利運作

完整程式碼如下

var http = require(rsquohttprsquo)

url = require(rsquourlrsquo)

fs = require(rdquofsrdquo)

qs = require(rsquoquerystringrsquo)

server

server = httpcreateServer(function (reqres)

var urlData

67 Express POST應用範例 61

Nodejs中文電子書

encode = rdquoutf8rdquo

filePath = rdquoviewexpress_post_example_formhtmlrdquo

formData

action

urlData = urlparse(requrltrue)

action = urlDatapathname

reswriteHead(200 rdquoContent-Typerdquordquotexthtml charset=utf-8rdquo)

if (action === rdquoSignuprdquo)

formData = rsquorsquo

reqon(rdquodatardquo function (data)

formData += data

)

reqon(rdquoendrdquo function ()

user = qsparse(formData)

resend(rdquolth1gtrdquo + userusername + rdquo 歡迎您的加入 lth1gtltpgt 我們已經將會員啟用信寄至rdquo + useremail + rdquoltpgtrdquo)

)

else

fsreadFile(filePath encode function(err file)

reswrite(file)

resend()

)

)

serverlisten(3000)

consolelog(rsquoServer 跑起來了現在時間是rsquo + new Date())

62 6 Express介紹

Nodejs中文電子書

68 Express AJAX應用範例

在 Nodejs要使用 Ajax傳送資料並且與之互動在接受資料的部份沒有太大的差別client端不是用 GET就是用 POST來傳資料重點在處理完後用 JSON格式回傳當然 Ajax不見得只傳 JSON格式有時是回傳一段 HTML碼不過後者對伺服器來說基本上就和前兩篇沒有差別了所以我們還是以回傳 JSON做為這一回的主題

這一回其實大多數的工作都會落在前端 Ajax上面前端要負責發送與接收資料並在接收資料後撤掉原先發送資料的表單並將取得的資料

改成 HTML格式之後放上頁面

首先先準備 HTML靜態頁面資料

ltDOCTYPE htmlgt

lthtmlgt

ltheadgt

lttitlegtNodejs 菜鳥筆記 (3)lttitlegt

ltlink rel=rdquostylesheetrdquo href=rdquocssstylecssrdquo type=rdquotextcssrdquo media=rdquoallrdquo gt

ltscript src=rdquohttpsajaxgoogleapiscomajaxlibsjquery171jqueryminjsrdquogtltscriptgt

ltheadgt

ltbodygt

lth1gtNodejs 菜鳥筆記-註冊 (用 Ajax)lth1gt

ltform id=rdquosignuprdquo action=rdquoSignuprdquo method=rdquoPOSTrdquogt

ltlabelgt 使用者名稱ltlabelgtltinput type=rdquotextrdquo id=rdquousernamerdquo name=rdquousernamerdquo gtltbrgt

ltlabelgt 電子郵件ltlabelgtltinput type=rdquotextrdquo id=rdquoemailrdquo name=rdquoemailrdquo gtltbrgt

ltinput type=rdquosubmitrdquo value=rdquo 註冊我的帳號rdquo gtltbrgt

ltformgt

ltdiv id=rdquoresultrdquogt

ltdivgt

ltscript type=rdquotextjavascriptrdquogt

$(function()$(rsquosignup input[type=rdquosubmitrdquo]rsquo)click(function(e)

var user =

userusername = $(rdquousernamerdquo)val()useremail = $(rdquoemailrdquo)val()

$post(rdquoSignuprdquouserfunction(data)greet(data)

)

68 Express AJAX應用範例 63

Nodejs中文電子書

epreventDefault()

)

var greet = function(msg)

var resultNode = $(rsquoresultrsquo)greeting = $(rsquolth2gtHirsquo+msgusername+rsquo 你的會員 id 是rsquo+ msgid +rsquo 我們會將會員啟動信寄至rsquo+msgemail+rsquolth2gtrsquo)

$(rsquosignuprsquo)hide()resultNodehtml(rdquordquo)

resultNodeappend(greeting)

)

ltscriptgt

ltbodygt

lthtmlgt

HTML頁面上準備了一個表單用來傳送註冊資料接著直接引用了Google CDN來載入 jQuery用來幫我們處理 Ajax的工作這次要傳送和接收的工作很大的變動都在 HTML頁面上的 JavaScript當中我們要做的事有 (相關 jQuery處理這邊不多做贅述指提起主要功能解說)

bull 用 jQUery取得 submit按鈕綁定它的 click動作

bull 取得表單 username和 email的值存放在 user這個物件中

bull 用 jQuery的 $post方法將 user的資料傳到 Server

bull 一旦成功取得資料後透過 greet這個 function組成回報給 user的訊息

bull 清空原本給使用者填資料的表單

bull 將 Server回傳的 usernameemail和 id這 3個資料組成回應的訊息

bull 將訊息放到原本表單的位置

經過以上的處理後一個 Ajax的表單的基本功能已經完成

接著進行 nodejs主要程式的編輯部分程式碼如下

var fs = require(rdquofsrdquo)

64 6 Express介紹

Nodejs中文電子書

qs = require(rsquoquerystringrsquo)

if (action === rdquoSignuprdquo)

formData = rsquorsquo

reqon(rdquodatardquo function (data)

formData += data

)

reqon(rdquoendrdquo function ()

var msg

user = qsparse(formData)

userid = rdquo123456rdquo

msg = JSONstringify(user)

reswriteHead(200 rdquoContent-Typerdquordquoapplicationjson charset=utf-8rdquordquoContent-Lengthrdquomsglength)

resend(msg)

)

這裡的程式和前面 POST範例基本上大同小異差別在

bull 幫 user的資料加上 id隨意存放一些文字進去讓 Server回傳的資料多於 Client端傳上來的不然會覺得 Server都沒做事

bull 增加了 msg 這個變數存放將 user 物件 JSON 文字化的結果JSONstringify這個轉換函式是 V8引擎所提供的如果你好奇的話

bull 大重點來了我們要告訴 Client端這次回傳的資料格式是 JSON所在 Content-type和 Content-Length要提供給 Client

Server很輕鬆就完成任務了最後進行程式測試啟動 nodejs主程式之後開啟瀏覽器就會看到表單填寫完畢按下送出就可以看到結果了

最後 nodejs本篇範例程式碼如下

var http = require(rsquohttprsquo)

url = require(rsquourlrsquo)

fs = require(rdquofsrdquo)

qs = require(rsquoquerystringrsquo)

server

68 Express AJAX應用範例 65

Nodejs中文電子書

server = httpcreateServer(function (reqres)

var urlData

encode = rdquoutf8rdquo

filePath = rdquoviewexpress_ajax_example_formhtmlrdquo

formData

action

urlData = urlparse(requrltrue)

action = urlDatapathname

if (action === rdquoSignuprdquo)

formData = rsquorsquo

reqon(rdquodatardquo function (data)

formData += data

)

reqon(rdquoendrdquo function ()

var msg

user = qsparse(formData)

userid = rdquo123456rdquo

msg = JSONstringify(user)

reswriteHead(200 rdquoContent-TyperdquordquoapplicationjsonrdquordquoContent-Lengthrdquomsglength)

resend(msg)

)

else

fsreadFile(filePath encode function(err file)

reswriteHead(200 rdquoContent-Typerdquordquotexthtml charset=utf-8rdquo)

reswrite(file)

resend()

)

)

serverlisten(3000)

66 6 Express介紹

Nodejs中文電子書

consolelog(rsquoServer 跑起來了現在時間是rsquo + new Date())

69 原始資料提供

bull [NodeJS初學者筆記 (1)-用 GET傳送資料] (httpithelpithomecomtwquestion10087402)

bull [NodeJS初學者筆記 (2)-用 POST傳送資料] (httpithelpithomecomtwquestion10087489)

bull [NodeJS 初學者筆記 (3)-用 Ajax 傳送資料] (httpithelpithomecomtwquestion10087627)

69 原始資料提供 67

Nodejs中文電子書

68 6 Express介紹

7

CoffeeScript

bull Spine

bull Hem

bull Spineapp

Letrsquos Have a Cup of CoffeeScript

bull es5-shim ECMAScript 5 compatibility shims for legacy JavaScript engines

ESnext

node-iform - A middleware for node-validator httpsgithubcomguileennode-iform

69

Nodejs中文電子書

70 7 CoffeeScript

8

製作一個 Hubot的 Plurk Adapter

81 應用事項提醒

此應用範例以CoffeeScript編寫主要程式架構採用Github釋放的Hubot專案以下有幾個主要注意事項請讀者注意

bull Hubot 一開始的架構除了內建的兩個 Adapter 之外其餘都要以Nodejs的Module方式才能運作

bull Hubot的 binhubot裡面寫著 npm install所以不管你怎麼改原始碼也不能改變第一點的狀況

其實上述都在討論同一件事情NodeJS的模組而且模組不能設定相對路徑之類的來安裝一定要包裝成 npm相符和格式才有辦法正確執行

82 建立 Adapter

首先需要準備兩個檔案

bull packagejson

bull plurkcoffee

71

Nodejs中文電子書

有這兩個就足以變成 NodeJS的模組了在開始 Coding之前先來設定一下 packagejson相依性

rdquonamerdquo rdquohubot-plurkrdquo

rdquoversionrdquo rdquo011rdquo

rdquomainrdquo rdquoplurkrdquo

rdquodependenciesrdquo

rdquohubotrdquo rdquogt=205rdquo

rdquooauthrdquo rdquordquo

rdquocronrdquordquordquo

這邊會使用到NodeJS的 cron模組(Module)而登入 Plurk需要OAuth標準授權才行所以也需要加載 OAuth模組

注意Hubot在讀取非內建模組時會自動在前面加上 hubot-的前置

83 建立 Robot跟 API

本篇應用基本上程式碼大部分參考 Hubot - Twitter Adapter來製作主要差異只有在使用 OAuth這個模組Twitter有 Streaming可用而 Plurk則得用 Comet方式來達到即時讀取

plurkcoffee程式碼

Robot = require(rdquohubotrdquo)robot()

Adapter = require(rdquohubtordquo)adapter()

EventEmitter = require(rdquoeventsrdquo)EventEmitter

oauth = require(rdquooauthrdquo)

cronJob = require(rdquocronrdquo)CronJob

class Plurk exntends Adapter

class PlurkStreaming exnteds EventEmitter

72 8 製作一個 Hubot的 Plurk Adapter

Nodejs中文電子書

先弄個基本架構主要 Class皆參考 Twitter Adapter命名方式接著再將 Plurk這個 Class增加幾個Method基本上只要有 run send reply就夠了而 run用來做初始化的部分

class Plurk entends Adapter

send (plurk id strings⋯) -gt

reply (plurk id strings⋯) -gt

run -gt

看起來有點東西接著來處理主要的 Plurk API結合部分

class PlurkStreaming extends EventEmitter

consuctor (options) -gt

plurk (callback) -gt

觀察河道

getChannel -gt

取得 Comet 網址

reply (plurk id message) -gt

回噗

acceptFriends -gt

接受好友

get (path callback) -gt

GET 請求

post (path body callback)-gt

POST 請求(其實是裝飾)

request (method path body callback)-gt

主要的 OAuth 請求

comet (server callback)-gt

噗浪的 Comet 傳回是 JavaScript Callback 要另外處理後才會變成 JSON

然後把注意力集中到 constructor上先把建構子弄好

constructor (options) -gt

super()

if optionskey and optionssecret and optionstoken and optionstoken secret

key = optionskey

secret = optionssecret

token = optionstoken

token secret = optionstoken secret

83 建立 Robot跟 API 73

Nodejs中文電子書

建立 OAuth 連接

consumer = new oauthOAuth(

rdquohttpwwwplurkcomOAuthrequest tokenrdquo

rdquohttpwwwplurkcomOAuthaccess tokenrdquo

key

secret

rdquo10rdquo

rdquohttpwwwplurkcomOAuthauthorizerdquo

rdquoHMAC-SHA1rdquo

)

domain = rdquowwwplurkcomrdquo

初始化取得 Comet 網址

do getChannel

else

throw new Error(rdquo 參數不足需要 Key Secret Token Token Secretrdquo)

接著來處理 request這個 method

request (method path body callback) -gt

記錄一下這次的 Request

consolelog(rdquohttpdomainpathrdquo)

Callback 這邊先不丟進去要用另一種方式處理

request = consumerget(rdquohttpdomainpathrdquo token token secret null)

requeston rdquoresponserdquo (res) -gt

reson rdquodatardquo (chunk) -gt

parseResponse(chunk+rsquorsquo callback)

reson rdquoendrdquo (data) -gt

consolelog rdquoEnd Request pathrdquo

reson rdquoerrorrdquo (data) -gt

consolelog rdquoError rdquo + data

requestend()

處理資料

parseResponse = (data callback) -gt

if datalength gt 0

用 TryCatch 避免處理 JSON 出錯導致整個中斷

try

callback null JSONparse(data)

catch err

74 8 製作一個 Hubot的 Plurk Adapter

Nodejs中文電子書

consolelog(rdquoError Parse JSONrdquo + data err)

繼續執行

callback null data ||

大致上就是這樣上面程式的架構已經將整個 Hubot Plurk Adapter完成因為在測試時竟然因為噗浪 Lag而沒讀到完整的 Comet資料然後造成程式異常為了避免這個問題發生因此需要加上為了完美呈現需要再

加上 Comet的處理所以要使用到 EventEmitter的功能

comet (server callback) -gt

在 Callback 裡面會找不到自身所以設定區域變數

self =

記錄一下這次的 Request

consolelog(rdquo[Comet] serverrdquo)

Callback 這邊先不丟進去要用另一種方式處理

request = consumerget(rdquohttpdomainpathrdquo token token secret null)

requeston rdquoresponserdquo (res) -gt

reson rdquodatardquo (chunk) -gt

parseResponse(chunk+rsquorsquo callback)

reson rdquoendrdquo (data) -gt

consolelog rdquoEnd Request pathrdquo

請求結束發出事件通知可以進行下一次請求

selfemit rdquonextPlurkrdquo

reson rdquoerrorrdquo (data) -gt

consolelog rdquoError rdquo + data

requestend()

處理資料

parseResponse = (data callback) -gt

if datalength gt 0

用 trycatch 避免失敗中斷

try

去掉 JavaScript 的 Callback

data = datamatch(CometChannelscriptCallback((+))s)

jsonData = rdquordquo

if data

83 建立 Robot跟 API 75

Nodejs中文電子書

jsonData = JSONparse(data[1])

else

如果沒有任何 Match 嘗試直接 parse

jsonData = JSONparse(data)

catch err

consolelog(rdquo[Comet] Errorrdquo data err)

用 TryCatch 避免處理 JSON 出錯導致整個中斷

try

只傳入 json 的 data 部分

callback null jsonDatadata

catch err

consolelog(rdquo[Comet]Error Parse JSONrdquo + data err)

繼續執行

callback null data ||

後面的 get跟 post就簡單多了

get (path callback) -gt

request(rdquoGETrdquo path null callback)

post (path body callback) -gt

request(rdquoPOSTrdquo path body callback)

接著處理取的 Comet網址的 getChannel

getChannel -gt

self =

get rdquoAPPRealtimegetUserChannelrdquo (error data) -gt

if error

檢查是否有 comet server

if datacomet server

selfchannel = datacomet server

如果沒有 Channel Ready 就嘗試連接會失敗

selfemit(rsquochannel readyrsquo)

那麼先來處理 Plurk Adaper好處理的部份

send (plruk id strings⋯)-gt

跟 Reply 一樣直接交給 reply 做

reply plurk id strings⋯

76 8 製作一個 Hubot的 Plurk Adapter

Nodejs中文電子書

reply (plurk id strings⋯) -gt

stringsforEach (message) =gt

botreply(plruk id message)

接著把 run處理好就可以上線運作摟

run -gt

self =

options =

key processenvHUBOT PLURK KEY

secret processenvHUBOT PLURK SECRET

token processenvHUBOT PLURK TOKEN

token secret processenvHUBOT PLURK TOKEN SECRET

創建剛剛的 API

bot = new PlurkStreaming(options)

依照 Twitter 的 new RobotTextMessage 會沒有反應所以參考 hubot-minecraft 的方式

r = robotconstructor

處理噗浪河道訊息

doPlurk = (data)-gt

檢查是否為回噗

if dataresponse

datacontent raw = dataresponsecontent raw

datauser id = dataresponseuser id

確定有噗浪 ID 跟訊息

if dataplurk id and datacontent raw

selfreceive new rTextMessage(dataplurk id datacontent raw)

取得 Comet Server 完成開始第一次 Comet 連接

boton rdquochannel readyrdquo () -gt

botplurk selfdoPlurk

上一次 Comet 完成繼續 Polling

boton rdquonextPlurkrdquo ()-gt

botplurk selfdoPlurk

定時接受好友邀請

do botacceptFriends

bot = bot

83 建立 Robot跟 API 77

Nodejs中文電子書

終於完成 AdapterHubot專案裡面的 scripts資料夾內是互動部分不需要像 Adapter如此大費周章處理只新增檔案並且設計好對白之後就會回噗了我開發用的機器人在此大家可以去跟他玩玩[httpplurkcomelct9620 bot](httpplurkcomelct9620 bot)

84 原始資料提供

bull [製 作 一 個 Hubot 的 噗 浪 Adapter](http revo-skillfrosttw blog20120318create-a-hubot-plurk-adapter)

78 8 製作一個 Hubot的 Plurk Adapter

9

Nodejs好用工具介紹

91 logio

Real-time log monitoring in your browser

httplogioorg

nodejs (gt=0412 lt=0611)

npm config set unsafe-perm true

npm install -g --prefix=usrlocal logio

logio server start

httplocalhost8998

79

Nodejs中文電子書

80 9 Nodejs好用工具介紹

10

精選文章

101 Express

bull 使用 NodeJS + Express從 GETPOST Request取值 [邀稿中]

bull link2 [邀稿中]

bull link3 [邀稿中]

81

Nodejs中文電子書

82 10 精選文章

11

參考資源

111 Nodejs書籍

bull Programming Nodejs April 2012 (est) Travis Swicegood Pragmatic Bookshelf

bull e Little Book on CoffeeScript January 2012 Alex MacCaw OrsquoReilly Media

bull Node for Front-End Developers January 2012 Garann Means OrsquoReilly Media

bull Building Hypermedia APIs with HTML5 and Node November 2011 MikeAmundsen OrsquoReilly Media

bull Node Web Development August 2011 David Herron PacktPub

bull CoffeeScript Accelerated JavaScript Development July 2011 Trevor BurnhamPragmatic Bookshelf

bull Getting Started with GEO CouchDB and Nodejs July 2011 Mick omp-son OrsquoReilly Media

bull What Is Node July 2011 Brett McLaughlin OrsquoReilly Media

bull Node Up and Running May 2011 Tom Hughes-Croucher Mike WilsonOrsquoReilly Media

83

Nodejs中文電子書

bull Hands-on Nodejs e Nodejs introduction and API reference May 2011 Pe-dro Teixeira LeanPub

bull e Node Beginner Book Manuel Kiessling

bull Mastering Nodejs visionmedia

112 Nodejs影音教學

bull e Node Sessions e Best of OSCON 2011 August 2011 OrsquoReilly Media(Video)

bull Tom Hughes-Croucher on Node March 2011 OrsquoReilly Media (Video)

113 Nodejs教學網站

bull Node Tuts

114 Nodejs課程

bull Introduction To Nodejs Van Nguyen CodeLesson

84 11 參考資源

Page 16: Nodejs Wiki

Nodejs中文電子書

10 1 Nodejs簡介

2

JavaScript與 NodeJS

其實使用 JavaScript 在網頁端與伺服器端的差距並不大但是為了使NodeJS可以發揮他最強大的能力有一些知識還是必要的所以還是針對這些主題介紹一下其中 Event LoopScope以及 Callback其實是比較需要了解的基本知識cpscurrying ow control是更進階的技巧與應用

21 Event Loop

可能很多人在寫 Javascript 時並不知道他是怎麼被執行的這個時候可以參考一下 jQuery作者 John Resig一篇好文章介紹事件及 timer怎麼在瀏覽器中執行How JavaScript Timers Work通常在網頁中所有的Javascript執行完畢後(這部份全部都在 global scope跑除非執行函數)接下來就是如 John Resig解釋的這樣所有的事件處理函數以及 timer執行的函數會排在一個 queue結構中利用一個無窮迴圈不斷從 queue中取出函數來執行這個就是 event loop

(除了 John Resig的那篇文章Nicholas C Zakas的 ldquoProfessional Javascriptfor Web Developer 2nd editionrdquo 有一個試閱本httpyuiblogcomassetspdfzakas-projs-2ed-ch18pdf598頁剛好也有簡短的說明)

所以在 Javascript中雖然有非同步但是他並不是使用執行緒所有

11

Nodejs中文電子書

的事件或是非同步執行的函數都是在同一個執行緒中利用 event loop的方式在執行至於一些比較慢的動作例如 IO網頁 render re ow等實際動作會在其他執行緒跑等到有結果時才利用事件來觸發處理函數來處理

這樣的模型有幾個好處沒有執行緒的額外成本所以反應速度很快不會

有任何程式同時用到同一個變數不必考慮 lock也不會產生 dead lock所以程式撰寫很簡單但是也有一些潛在問題任一個函數執行時間較長都

會讓其他函數更慢執行(因為一個跑完才會跑另一個)在多核心硬體普遍

的現在無法用單一的應用程式 instance發揮所有的硬體能力用 NodeJS撰寫伺服器程式碰到的也是一樣的狀況要讓系統發揮 event loop的效能就要盡量利用事件的方式來組織程式架構另外對於一些有可能較為耗

時的操作可以考慮使用 processnextTick函數來讓他以非同步的方式執行避免在同一個函數中執行太久擋住所有函數的執行

如果想要測試 event loop怎樣在「瀏覽器」中運行可以在函數中呼叫alert()這樣會讓所有 Javascript的執行停下來尤其會干擾所有使用 timer的函數執行有一個簡單的例子這是一個會依照設定的時間間隔嚴格執

行動作的動畫如果時間過了就會跳過要執行的動作點按圖片以後人

物會快速旋轉但是在旋轉執行完畢前按下「delay」按鈕讓 alert訊息等久一點接下來的動畫就完全不會出現了

22 Scope與 Closure

要快速理解 JavaScript的 Scope(變數作用範圍)原理只要記住他是Lexical Scope就差不多了簡單地說變數作用範圍是依照程式定義時(或者叫做程式文本)的上下文決定而不是執行時的上下文決定

為了維護程式執行時所依賴的變數即使執行時程式運行在原本的

scope之外他的變數作用範圍仍然維持不變這時程式依賴的自由變數(定義時不是 local的而是在上一層 scope定義的變數)一樣可以使用就好像被關閉起來所以叫做 Closure用程式看比較好懂

function outter(arg1)

arg1 及 free_variable1 對 inner 函數來說都是自由變數

var free_variable1 = 3

return function inner(arg2)

12 2 JavaScript與 NodeJS

Nodejs中文電子書

var local_variable1 =2arg2 及 local_variable1 對 inner 函數來說都是本地變數

return arg1 + arg2 + free_variable + local_variable1

var a = outter(1)變數 a就是 outter函數執行後返回的 inner函數 var b =a(4)執行 inner函數執行時上下文已經在 outter函數之外但是仍然能正常執行而且可以使用定義在 outter函數裡面的 arg1及 free variable1變數 consolelog(b)結果 10

在 Javascript中scope最主要的單位是函數(另外有 global及 eval)所以有可能製造出 closure的狀況通常在形式上都是有巢狀的函數定義而且內側的函數使用到定義在外側函數裡面的變數

Closure有可能會造成記憶體洩漏主要是因為被參考的變數無法被垃圾收集機制處理造成佔用的資源無法釋放所以使用上必須考慮清楚

不要造成意外的記憶體洩漏(在上面的例子中如果 a一直未執行使用到的記憶體就不會被釋放)

跟透過函數的參數把變數傳給函數比較起來Javascript Engine會比較難對 Closure進行最佳化如果有效能上的考量這一點也需要注意

23 Callback

要介紹 Callback之前要先提到 JavaScript的特色

JavaScript是一種函數式語言(functional language)所有 Javascript語言內的函數都是高階函數 (higher order function這是數學名詞計算機用語好像是 rst class function意指函數使用沒有任何限制與其他物件一樣)也就是說函數可以作為函數的參數傳給函數也可以當作函數的返回值這個特性讓 Javascript的函數使用上非常有彈性而且功能強大

callback在形式上其實就是把函數傳給函數然後在適當的時機呼叫傳入的函數Javascript使用的事件系統通常就是使用這種形式NodeJS中有一個物件叫做 EventEmitter這是 NodeJS事件處理的核心物件所有會使用事件處理的函數都會「繼承」這個物件(這裡說的繼承實

作上應該像是 mixin)他的使用很簡單可以使用物件on(事件名稱callback

23 Callback 13

Nodejs中文電子書

函數) 或是物件addListener(事件名稱callback 函數) 把你想要處理事件的函數傳入在物件中可以使用物件emit(事件名稱 參數) 呼叫傳入的callback函數這是 Observer Pattern的簡單實作而且跟在網頁中使用 DOM的 addEventListener使用上很類似也很容易上手不過 NodeJS是大量使用非同步方式執行的應用所以程式邏輯幾乎都是寫在 callback函數中當邏輯比較複雜時大量的 callback會讓程式看起來很複雜也比較難單元測試舉例來說

var p client = new Db(lsquointegration tests 20rsquo new Server(ldquo127001rdquo 27017) lsquopkrsquoCustomPKFactory) p clientopen(function(err p client)

p clientdropDatabase(function(err done)

p clientcreateCollection(lsquotest custom keyrsquo function(err collection)

collectioninsert(lsquoarsquo1 function(err docs)

collection nd(lsquo idrsquonew ObjectID(ldquoaaaaaaaaaaaardquo) function(err cursor)

cursortoArray(function(err items) testassertEquals(1 itemslength) p clientclose()

)

)

)

)

)

)

這是在網路上看到的一段操作 mongodb的程式碼為了循序操作所以必須在一個 callback裡面呼叫下一個動作要使用的函數這個函數裡面還是會使用 callback最後就形成一個非常深的巢狀

這樣的程式碼會比較難進行單元測試有一個簡單的解決方式是

盡量不要使用匿名函數來當作 callback或是 event handler透過這樣的方式

14 2 JavaScript與 NodeJS

Nodejs中文電子書

就可以對各個 handler做單元測試了例如

var http = require(lsquohttprsquo) var tools =

cookieParser function(request response) if(requestheaders[rsquoCookiersquo]) do parsing

var server = httpcreateServer(function(request response)

thisemit(lsquoinitrsquo request response)

) serveron(lsquoinitrsquo toolscookieParser) serverlisten(8080 lsquo127001rsquo)

更進一步可以把 tools改成外部 module例如叫做 toolsjs

moduleexports = cookieParser function(request response) if(requestheaders[rsquoCookiersquo]) do parsing

然後把程式改成

var http = require(lsquohttprsquo)

var server = httpcreateServer(function(request response) thisemit(lsquoinitrsquo re-quest response)

) serveron(lsquoinitrsquo require(lsquo toolsrsquo)cookieParser) serverlisten(8080lsquo127001rsquo)

這樣就可以單元測試 cookieParser了例如使用 nodeunit時可以這樣寫

var testCase = require(lsquonodeunitrsquo)testCase moduleexports = testCase(

ldquosetUprdquo function(cb) thisrequest = headers Cookielsquoname1val1 name2val2rsquo thisresponse = thisresult= name1rsquoval1rsquoname2rsquoval2rsquo

cb()

ldquotearDownrdquo function(cb)

cb()

23 Callback 15

Nodejs中文電子書

ldquonormal caserdquo function(test)

testexpect(1) var obj = require(lsquotoolsrsquo)cookieParser(thisrequest thisresponse)testdeepEqual(obj thisresult) testdone()

)

善於利用模組可以讓程式更好維護與測試

24 CPS(Continuation-Passing Style)

cps 是 callback 使用上的特例形式上就是在函數最後呼叫 callback這樣就好像把函數執行後把結果交給 callback 繼續運行所以稱作continuation-passing style利用 cps可以在非同步執行的情況下透過傳給 callback的這個 cps callback來獲知 callback執行完畢或是取得執行結果例如

lthtmlgt ltbodygt ltdiv id=rdquopanelrdquo style=rdquovisibilityhiddenrdquogtlt divgtlt bodygt lt htmlgt ltscriptgt var request = new XMLHttpRequest() re-questopen(lsquoGETrsquo lsquotest749txt timestamp=rsquo+new Date()getTime() true) re-questaddEventListener(lsquoreadystatechangersquo function(next)

return function() if(thisreadyState===4ampampthisstatus===200) next(thisresponseText)lt== 傳入的 cps callback 在動作完成時執行並取得結果進一步處理

(function(str)lt==這個匿名函數就是 cps callback

documentgetElementById(lsquopanelrsquo)innerHTML=str docu-mentgetElementById(lsquopanelrsquo)stylevisibility = lsquovisiblersquo

) false) requestsend() ltscriptgt

進一步的應用也可以參考 2-6流程控制

16 2 JavaScript與 NodeJS

Nodejs中文電子書

25 函數返回函數與 Currying

前面的 cps範例裡面使用了函數返回函數這是為了把 cps callback傳遞給 onreadystatechange事件處理函數的方法(因為這個事件處理函數並沒有設計好會傳送接收這樣的參數)實際會執行的事件處理函數其實是內層返回的那個函數之外包覆的這個函數主要是為了利用 Closure把 next傳給內層的事件處理函數這個方法更常使用的地方是為了解決一些

scope問題例如

ltscriptgt var accu=0count=10 for(var i=0 iltcount i++)

setTimeout(

function() countndash accu+=i if(countlt=0)

consolelog(accu)

50)

ltscriptgt

最後得出的結果會是 100而不是想像中的 45這是因為等到 setTime-out指定的函數執行時變數 i已經變成 10而離開迴圈了要解決這個問題就需要透過 Closure來保存變數 i

ltscriptgt var accu=0count=10 for(var i=0 iltcount i++)

setTimeout(

function(i) return function() countndash

accu+=i if(countlt=0)

consolelog(accu)

(i)

50)

25 函數返回函數與 Currying 17

Nodejs中文電子書

淺藍色底色的部份是跟上面例子不一樣的地方 ltscriptgt

函數返回函數的另外一個用途是可以暫緩函數執行例如

function add(m n) return m+n

var a = add(20 10) consolelog(a)

add這個函數必須同時輸入兩個參數才有辦法執行如果我希望這個函數可以先給它一個參數等一些處理過後再給一個參數然後得到結

果就必須用函數返回函數的方式做修改

function add(m)

return function(n) return m+n

var wait another arg = add(20)先給一個參數 var a = function(arr)

var ret=0 for(var i=0iltarrlengthi++) ret+=arr[i] return ret

([1234])計算一下另一個參數 var b = wait another arg(a)然後再繼續執行 consolelog(b)

像這樣利用函數返回函數使得原本接受多個參數的函數可以一次

接受一個參數直到參數接收完成才執行得到結果的方式有一個學名就

叫做Currying

綜合以上許多奇技淫巧就可以透過用函數來處理函數的方式調整

程式流程接下來看看

26 流程控制

(以 sync方式使用 async函數避開巢狀 callback循序呼叫 async callback等奇技淫巧)

建議參考

bull httphowtonodeorgcontrol- ow

bull httphowtonodeorgcontrol- ow-part-ii

18 2 JavaScript與 NodeJS

Nodejs中文電子書

bull httphowtonodeorgcontrol- ow-part-iii

bull httpblogmixunet20110202essential-node-js-patterns-and-snippets

這幾篇都是非常經典的 NodeJSJavascript流程控制好文章(阿mixu是在介紹一些 pattern時提到這方面的主題)不過我還是用幾個簡單的程式介紹一下做法跟概念

並發與等待

下面的程式參考了 mixu文章中的做法

var wait = function(callbacks done) consolelog(lsquowait startrsquo) var counter = call-backslength var results = [] var next = function(result) 接收函數執行結果並判斷是否結束執行 resultspush(result) if(ndashcounter == 0) done(results)如果結束執行就把所有執行結果傳給指定的 callback處理 for(var i = 0 i lt callbackslength i++) 依次呼叫所有要執行的函數 callbacks[i](next) consolelog(lsquowait endrsquo)

wait( [ function(next) setTimeout(function() consolelog(lsquodone arsquo) var result =500 next(result) 500) function(next) setTimeout(function() con-solelog(lsquodone brsquo) var result = 1000 next(result) 1000) function(next)setTimeout(function() consolelog(lsquodone crsquo) var result = 1500 next(1500) 1500) ] function(results) var ret = 0 i=0 for( iltresultslength i++) ret+= results[i] consolelog(lsquodone all result lsquo+ret)

)

執行結果wait start wait end done a done b done c done all result 3000

可以看出來其實 wait並不是真的等到所有函數執行完才結束執行而是在所有傳給他的函數執行完畢後(不論同步非同步)才執行處理結

果的函數(也就是 done())

不過這樣的寫法還不夠實用因為沒辦法實際讓函數可以等待執行

完畢又能當作事件處理函數來實際使用上面參考到的 Tim Caswell的文

26 流程控制 19

Nodejs中文電子書

章裡面有一種解法不過還需要額外包裝(在他的例子中)NodeJS核心的 fs物件把一些函數(例如 readFile)用 Currying處理類似像這樣

var fs = require(lsquofsrsquo) var readFile = function(path)

return function(callback errback)

fsreadFile(path function(err data)

if(err) errback()

else callback(data)

)

其他部份可以參考 Tim Caswell的文章他的 Doparallel跟上面的 wait差不多意思這裡只提示一下他沒說到的地方

另外一種做法是去修飾一下 callback當他作為事件處理函數執行後再用 cps的方式取得結果

ltscriptgt function Wait(fns done)

var count = 0 var results = [] thisgetCallback = function(index)

count++ return (function(waitback)

return function() var i=0args=[]for(iltargumentslengthi++)

argspush(arguments[i])

argspush(waitback) fns[index]apply(thisargs)

)(function(result) resultspush(result) if(ndashcount == 0)

20 2 JavaScript與 NodeJS

Nodejs中文電子書

done(results)

)

var a = new Wait(

[ function(waitback) consolelog(lsquodone arsquo) var result = 500 wait-back(result) function(waitback) consolelog(lsquodone brsquo) var result =1000 waitback(result) function(waitback) consolelog(lsquodone crsquo) varresult = 1500 waitback(result) ] function(results) var ret = 0 i=0for( iltresultslength i++) ret += results[i] consolelog(lsquodone allresult lsquo+ret)

) var callbacks = [agetCallback(0)agetCallback(1)agetCallback(0)agetCallback(2)]一次取出要使用的 callbacks避免結果提早送出 setTimeout(callbacks[0]500) setTimeout(callbacks[1] 1000) setTimeout(callbacks[2] 1500) setTime-out(callbacks[3] 2000) 當所有取出的 callbacks執行完畢就呼叫 done()來處理結果 ltscriptgt

執行結果

done a done b done a done c done all result 3500

上面只是一些小實驗更成熟的作品是 Tim Caswell 的 stephttpsgithubcomcreationixstep

如果希望真正使用同步的方式寫非同步則需要使用 Promisejs這一類的 library來轉換非同步函數不過他結構比較複雜 XD(見仁見智不過有些人認為 Promise有點過頭了)httpblogsmsdncombrbucktonarchive20110815promise-js-2-0-promise-framework-for-javascriptaspx

如果想不透過其他 Library做轉換又能直接用同步方式執行非同步函數大概就要使用一些需要額外 compile原始程式碼的方法了例如 BrunoJouhier的 streamlinejshttpsgithubcomSagestreamlinejs

26 流程控制 21

Nodejs中文電子書

循序執行

循序執行可以協助把非常深的巢狀 callback結構攤平例如用這樣的簡單模組來做(serialjs)

moduleexports = function(funs) var c = 0 if(isArrayOfFunctions(funs))

throw(lsquoArgument type was not matched Should be array of func-tionsrsquo)

return function()

var args = Arrayprototypeslicecall(arguments 0) if((cgt=funslength))

c++ return funs[c-1]apply(this args)

function isArrayOfFunctions(f ) if(typeof f == lsquoobjectrsquo) return false if(flength)return false if(fconcat) return false if(fsplice) return false var i = 0 for(iltflength i++)

if(typeof f[i] == lsquofunctionrsquo) return false

return true

簡單的測試範例(testSerialjs)使用 fs 模組確定某個 path 是檔案然後讀取印出檔案內容這樣會用到兩層的 callback所以測試中有使用serial的版本與 nested callbacks的版本做對照

var serial = require(lsquoserialrsquo) fs = require(lsquofsrsquo) path = lsquodclientjsrsquo cb = serial([ func-tion(err data)

if(err)

if(dataisFile) fsreadFile(path cb)

22 2 JavaScript與 NodeJS

Nodejs中文電子書

else consolelog(err)

function(err data)

if(err) consolelog(lsquo[ attened by searial]rsquo) con-solelog(datatoString(lsquoutf8rsquo))

else consolelog(err)

]) fsstat(path cb)

fsstat(path function(err data) 第一層 callback if(err)

if(dataisFile)

fsreadFile(path function(err data) 第二層 callback if(err)

consolelog(lsquo[nested callbacks]rsquo) con-solelog(datatoString(lsquoutf8rsquo))

else consolelog(err)

)

else consolelog(err)

)

關鍵在於這些 callback的執行是有順序性的所以利用 serial返回的一個函數 cb來取代這些 callback然後在 cb中控制每次會循序呼叫的函數

26 流程控制 23

Nodejs中文電子書

就可以把巢狀的 callback攤平成循序的 function陣列(就是傳給 serial函數的參數)

測試中的dclientjs 是一個簡單的 dnode 測試程式放在跟 testSerialjs同一個目錄

var dnode = require(lsquodnodersquo)

dnodeconnect(8000 lsquolocalhostrsquo function(remote)

remoterestart(function(str) consolelog(str) processexit()

)

)

執行測試程式後出現結果

[ attened by searial] var dnode = require(lsquodnodersquo)

dnodeconnect(8000 lsquolocalhostrsquo function(remote)

remoterestart(function(str) consolelog(str) processexit()

)

)

[nested callbacks] var dnode = require(lsquodnodersquo)

dnodeconnect(8000 lsquolocalhostrsquo function(remote)

remoterestart(function(str) consolelog(str) processexit()

)

)

對照起來看兩種寫法的結果其實是一樣的但是利用 serialjs巢狀的 callback結構就會消失

不過這樣也只限於順序單純的狀況如果函數執行的順序比較複雜

(不只是一直線)還是需要用功能更完整的流程控制模組比較好例如

httpsgithubcomcaolanasync

24 2 JavaScript與 NodeJS

3

Nodejs安裝與設定

本篇將講解如何在各個不同 OS建立 NodeJS環境目前 NodeJS 048版本環境架設方式需依賴 Linux指令才可編譯完成當然在不同作業系統中也已經有 NodeJS package可以直接使用指令快速架設以下各不同作業系統解說如何安裝 NodeJS

31 Ubuntu Linux

更新推薦使用 nvm

git clone gitgithubcomcreationixnvmgit ~nvm

echo rdquo ~nvmnvmshrdquo gtgt ~bashrc

nvm install v0614

nvm alias default v0614

以 上 可 參 考 http dreamerslabcom blog tw how-to-setup-a-node-js-development-environment-on-ubuntu-11-04

使用 APT套件管理工具是常見的方法以下是使用社群提供的 PPA安裝方式

sudo apt-get install python-software-properties

sudo add-apt-repository ppachris-leanodejs-devel

25

Nodejs中文電子書

sudo apt-get update

sudo apt-get install nodejs

32 Other Linux

Linux很適合作為 NodeJS的伺服器作業系統及開發環境安裝前請先確認以下套件已正確安裝

bull curl (wget)用來下載檔案的工具

bull git先進的版本控制工具

bull g++ GNU C++軟體編譯工具

bull make GNU軟體專案建置工具

安裝指令如下如設有權限問題請在指令前面加上 sudo

git clone httpsgithubcomjoyentnodegit

cd node

git checkout v067

configure

make

sudo make install

接著測試 nodeJS是否正常執行

node --version

出現版本訊息即表示安裝成功

33 Windows

nodeJS 在 v060 版本之後開始正式支援 windows native直接使用nodeexe 就可以執行程式支援性完全與 linux 相同更棒的部份就是不需經過編譯經過下載之後簡單設定完成立即開發 node程式

26 3 Nodejs安裝與設定

Nodejs中文電子書

下載 nodejs安裝檔案 lthttpnodejsorgdownloadgt

如此完成 windows native nodeexe安裝接著可以進入 command line執行測試在 command line輸入rdquonoderdquo會出現 nodejs版本訊息畫面表示安裝完成

33 Windows 27

Nodejs中文電子書

28 3 Nodejs安裝與設定

4

Nodejs基礎

前篇文章已經由介紹安裝至設定都有完整介紹nodeJS 內部除了javascript常用的函式 (function)物件 (object)之外也有許多不同的自訂物件nodeJS預設建立這些物件為核心物件是為了要讓開發流程更為這些資料在官方文件已經具有許多具體說明接下來將會介紹在開發 nodeJS程式時常見的物件特性與使用方法

41 nodejs http伺服器建立

在 lsquonodejs官方網站 lthttpnodejsorggtlsquo裡面有舉一個最簡單的 HTTP伺服器建立一開始初步就是建立一個伺服器平台讓 nodejs可以與瀏覽器互相行為每種語言一開始的程式建立都是以 Hello world開始最初也從 Hello world帶各位進入 nodejs的世界

輸入以下程式碼儲存檔案為 node basic http hello worldjs

var server

ip = rdquo127001rdquo

port = 1337

http = require(rsquohttprsquo)

server = httpcreateServer(function (req res)

29

Nodejs中文電子書

reswriteHead(200 rsquoContent-Typersquo rsquotextplainrsquo)

resend(rsquoHello Worldnrsquo)

)

serverlisten(port ip)

consolelog(rdquoServer running at httprdquo + ip + rdquordquo + port)

程式碼解講一開始需要有幾個基本的變數

bull ip 機器本身的 ip位置因為使用本地端因此設定為 127001

bull port 需要開通的阜號通常設定為 http port 80因範例不希望與基本 port相衝隨意設定為 1337

在 nodejs 的程式中有許多預設的模組可以使用因此需要使用require方法將模組引入在這邊我們需要使用 http這個模組因此將 http載入Http模組裡面內建有許多方法可以使用這邊採用 createServer創建一個基本的 http伺服器再將 http伺服器給予一個 server變數裡面的回呼函式 (call back function)可以載入 http伺服器的資料與回應方法 (requestresponse)在程式裡面就可以看到我們直接回應給瀏覽器端所需的 Header回應內容

reswriteHead(200 rsquoContent-Typersquo rsquotextplainrsquo)

resend(rsquoHello Worldnrsquo)

Http伺服器需要設定 port ip在最後需要設定 Http監聽需要使用到listen事件監聽所有 Http伺服器行為

httplisten(port ip)

所有事情都完成之後需要確認伺服器正確執行因此使用 console在javascript裡就有這個原生物件console所印出的資料都會顯示於 nodejs伺服器頁面這邊印出的資料並不會傳送到使用者頁面上之後許多除壞

(debug)都會用到 console物件

consolelog(rdquoServer running at httprdquo + ip + rdquordquo + port)

30 4 Nodejs基礎

Nodejs中文電子書

42 nodejs http路徑建立

前面已經介紹如何建立一個簡單的 http伺服器接下來本章節將會介紹如何處理伺服器路徑 (rout)問題在 http的協定下所有從瀏覽器發出的要求 (request)都需要經過處理路徑上的建立也是如此

路徑就是指伺服器 ip位置或者是網域名稱之後對於伺服器給予的要求修改剛才的 hello world檔案修改如下

server = httpcreateServer(function (req res)

consolelog(requrl)

reswriteHead(200 rsquoContent-Typersquo rsquotextplainrsquo)

resend(rsquohello worldnrsquo)

)

重新啟動 nodejs程式後在瀏覽器端測試一下路徑行為結果如下圖

當在瀏覽器輸入 http1270011337test 在伺服器端會收到兩個要求一個是我們輸入的test要求另外一個則是faviconicotest的路徑要求http伺服器本身需要經過程式設定才有辦法回應給瀏覽器端所需要的回應在伺服器中所有的路徑要求都是需要被解析才有辦法取得資料從

42 nodejs http路徑建立 31

Nodejs中文電子書

上面解說可以了解到在 nodejs當中所有的路徑都需要經過設定未經過設定的路由會讓瀏覽器無法取得任何資料導致錯誤頁面的發生底下將會解

說如何設定路由同時避免發生錯誤情形先前 nodejs程式需要增加一些修改才能讓使用者透過瀏覽器在不同路徑時有不同的結果根據剛才

的程式做如下的修改

var server

ip = rdquo127001rdquo

port = 1337

http = require(rsquohttprsquo)

url = require(rsquourlrsquo)

path

server = httpcreateServer(function (req res)

path = urlparse(requrl)

reswriteHead(200 rsquoContent-Typersquo rsquotextplainrsquo)

switch (pathpathname)

case rdquoindexrdquo

resend(rsquoI am indexnrsquo)

break

case rdquotestrdquo

resend(rsquothis is test pagenrsquo)

break

default

resend(rsquodefault pagenrsquo)

break

)

serverlisten(port ip)

consolelog(rdquoServer running at httprdquo + ip + rdquordquo + port)

程式做了片段的修改首先載入 url模組另外增加一個 path變數url模組就跟如同他的命名一般專門處理 url字串處理裡面提供了許多方法來解決路徑上的問題因為從瀏覽器發出的要求路徑可能會帶有多種需求

或者 GET參數組合等因此我們需要將路徑單純化取用路徑部分的資料即可例如使用者可能會送出 http1270011337testsend=1如果直接

32 4 Nodejs基礎

Nodejs中文電子書

信任 requrl就會收到結果為testsend=1所以需要透過 url模組的方法將路徑資料過濾

在這邊使用 urlparse的方法裡面帶入網址格式資料會回傳路徑資料為了後需方便使用將回傳的資料設定到 path變數當中在回傳的路徑資料裡面包含資訊如下圖

這邊只需要使用單純的路徑要求直接取用 pathpathname就可以達到我們的目的

最後要做路徑的判別在不同的路徑可以指定不同的輸出在範例中

有三個可能結果第一個從瀏覽器輸入index就會顯示 index結果test 就會呈現出 test頁面最後如果都不符合預期的輸入會直接顯示 default的頁面最後的預防可以讓瀏覽器不會出現非預期結果讓程式的可靠性提昇

底下為測試結果

42 nodejs http路徑建立 33

Nodejs中文電子書

43 nodejs檔案讀取

前面已經介紹如何使用路由(rount)做出不同的回應實際應用只有在瀏覽器只有輸出幾個文字資料總是不夠的在本章節中將介紹如何使

用檔案讀取輸出檔案資料讓使用者在前端瀏覽器也可以讀取到完整的

html css javascript檔案輸出

檔案管理最重要的部分就是 lsquoFile system lthttpnodejsorgdocslatestapifshtmlgtlsquo這個模組此模組可以針對檔案做管理監控讀取等行為裡面有許多預設的方法底下是檔案輸出的基本範例底下會有兩個檔案

第一個是靜態 html檔案

ltDOCTYPE htmlgt

lthtml xmlns=rdquohttpwwww3org1999xhtmlrdquo xmllang=rdquozh-CNrdquo lang=rdquozh-CNrdquogt

ltheadgt

ltmeta http-equiv=rdquoContent-Typerdquo content=rdquotexthtml charset=UTF-7rdquo gt

lttitlegtnodejs index html filelttitlegt

ltheadgt

ltbodygt

34 4 Nodejs基礎

Nodejs中文電子書

lth1gtnodejs index html filelth1gt

ltbodygt

lthtmlgt

另一個為 nodejs程式

var fs = require(rdquofsrdquo)

filename = rdquostaticindexhtmlrdquo

encode = rdquoutf8rdquo

fsreadFile(filename encode function(err file)

consolelog(file)

)

一開始直接載入 le system模組 載入名稱為 fs讀取檔案主要使

用的方法為 readFile裡面以三個參數路徑 ( le path) 編碼方式 (encoding)

回應函式 (callback)路徑必須要設定為靜態 html所在位置才能指定到正確的檔案靜態檔案的編碼方式也必須正確這邊使用靜態檔案的編

碼為 utf8如果編碼設定錯誤nodejs讀取出來檔案結果會使用 byte raw格式輸出如果錯誤編碼格式會導致輸出資料為 byte raw

回應函式中裡面會使用兩個變數error為錯誤資訊如果讀取的檔案不存在或者發生錯誤error數值會是 true如果成功讀取資料 error將會是 falsecontent則是檔案內容資料讀取後將會把資料全數丟到 content這個變數當中

最後程式的輸出結果畫面如下

43 nodejs檔案讀取 35

Nodejs中文電子書

44 nodejs http靜態檔案輸出

前面已經了解如何讀取本地端檔案接下來將配合 http伺服器路由讓每個路由都能夠輸出相對應的靜態 html檔案首先新增加幾個靜態 html檔案

ltDOCTYPE htmlgt

lthtml xmlns=rdquohttpwwww3org1999xhtmlrdquo xmllang=rdquozh-CNrdquo lang=rdquozh-CNrdquogt

ltheadgt

ltmeta http-equiv=rdquoContent-Typerdquo content=rdquotexthtml charset=UTF-7rdquo gt

lttitlegtnodejs index html filelttitlegt

ltheadgt

ltbodygt

lth1gtnodejs index html filelth1gt

ltbodygt

lthtmlgt

ltDOCTYPE htmlgt

lthtml xmlns=rdquohttpwwww3org1999xhtmlrdquo xmllang=rdquozh-CNrdquo lang=rdquozh-CNrdquogt

ltheadgt

ltmeta http-equiv=rdquoContent-Typerdquo content=rdquotexthtml charset=UTF-7rdquo gt

lttitlegtnodejs test html filelttitlegt

ltheadgt

ltbodygt

lth1gtnodejs test html filelth1gt

ltbodygt

lthtmlgt

ltDOCTYPE htmlgt

lthtml xmlns=rdquohttpwwww3org1999xhtmlrdquo xmllang=rdquozh-CNrdquo lang=rdquozh-CNrdquogt

ltheadgt

ltmeta http-equiv=rdquoContent-Typerdquo content=rdquotexthtml charset=UTF-7rdquo gt

lttitlegtnodejs static html filelttitlegt

ltheadgt

ltbodygt

lth1gtnodejs static html filelth1gt

ltbodygt

lthtmlgt

36 4 Nodejs基礎

Nodejs中文電子書

準備一個包含基本路由功能的 http伺服器

var server

ip = rdquo127001rdquo

port = 1337

http = require(rsquohttprsquo)

url = require(rsquourlrsquo)

server = httpcreateServer(function (req res)

var path = urlparse(requrl)

)

serverlisten(port ip)

consolelog(rdquoServer running at httprdquo + ip + rdquordquo + port)

加入 le system 模組使用 readFile 的功能將這一段程式放置於

createServer的回應函式中

fsreadFile(filePath encode function(err file)

)

readFile的回應函式裡面加入頁面輸出讓瀏覽器可以正確讀到檔案在這邊我們設定讀取的檔案為 html 靜態檔案所以 Content-type 設定為texthtml讀取到檔案的內容將會正確輸出成 html靜態檔案

fsreadFile(filePath encode function(err file)

reswriteHead(200 rsquoContent-Typersquo rsquotexthtmlrsquo)

reswrite(file)

resend()

)

到這邊為止基本的程式內容都已經完成剩下一些細節的調整首先

路徑上必須做調整目前的靜態檔案全部都放置於 static資料夾底下設定一個變數來記住資料夾位置

接著將瀏覽器發出要求路徑與資料夾組合讀取正確 html靜態檔案使用者有可能會輸入錯誤路徑所以在讀取檔案的時候要加入錯誤處理

同時回應 404伺服器無法正確回應的 http header格式

加入這些細節的修改一個基本的 http 靜態 html輸出伺服器就完成了完整程式碼如下

44 nodejs http靜態檔案輸出 37

Nodejs中文電子書

var server

ip = rdquo127001rdquo

port = 1337

http = require(rsquohttprsquo)

fs = require(rdquofsrdquo)

folderPath = rdquostaticrdquo

url = require(rsquourlrsquo)

path

filePath

encode = rdquoutf8rdquo

server = httpcreateServer(function (req res)

path = urlparse(requrl)

filePath = folderPath + pathpathname

fsreadFile(filePath encode function(err file)

if (err)

reswriteHead(404 rsquoContent-Typersquo rsquotextplainrsquo)

resend()

return

reswriteHead(200 rsquoContent-Typersquo rsquotextapplicationrsquo)

reswrite(file)

resend()

)

)

serverlisten(port ip)

consolelog(rdquoServer running at httprdquo + ip + rdquordquo + port)

45 nodejs http GET資料擷取

http伺服器中除了路由之外另一個最常使用的方法就是擷取 GET資料本單元將會介紹如何透過基本 http伺服器擷取瀏覽器傳來的要求擷取 GET資料

在 http協定中GET參數都是藉由 URL從瀏覽器發出要求送至伺服器

38 4 Nodejs基礎

Nodejs中文電子書

端基本的傳送網址格式可能如下

http127001testsend=1amptest=2

上面這段網址裡面的 GET參數就是 send而這個變數的數值就為 1如果想要在 http伺服器取得 GET資料需要在瀏覽器給予的要求 (request)做處理

首先需要載入 query string這個模組這個模組主要是用來將字串資料

過濾後轉換成javascript物件

qs = require(rsquoquerystringrsquo)

接著在第一階段利用 url模組過濾瀏覽器發出的 URL資料後將回應的物件裡面的 query這個變數是一個字串值資料過濾後如下

send=1amptest=2

透過 query string使用 parse這個方法將資料轉換成 javascript物件就表示 GET的資料已經被伺服器端正式擷取下來

path = urlparse(requrl)

parameter = qsparse(pathquery)

整個 nodejs http GET參數完整擷取程式碼如下

var server

ip = rdquo127001rdquo

port = 1337

http = require(rsquohttprsquo)

qs = require(rsquoquerystringrsquo)

url = require(rsquourlrsquo)

server = httpcreateServer(function (req res)

var path = urlparse(requrl)

parameter = qsparse(pathquery)

consoledir(parameter)

reswriteHead(200 rsquoContent-Typersquo rsquotextplainrsquo)

reswrite(rsquoBrowser test GET parameternrsquo)

resend()

)

45 nodejs http GET資料擷取 39

Nodejs中文電子書

serverlisten(port ip)

consolelog(rdquoServer running at httprdquo + ip + rdquordquo + port)

程式運作之後由瀏覽器輸入要求網址之後nodejs伺服器端回應資料為

Server running at http1270011337

send rsquo1rsquo test rsquo1rsquo

46 本章結語

前面所解說的部份一大部分主要是處理 http伺服器基本問題雖然在某些部分有牽扯到 http 伺服器基本運作原理主要還是希望可以藉由這些基本範例練習 nodejs練習回應函式與語法串接的特點習慣編寫javascript風格程式當然直接這樣開發 nodejs是非常辛苦的接下來在模組實戰開發的部份將會介紹特定的模組一步一步帶領各位從無到有進行

nodejs應用程式開發

40 4 Nodejs基礎

5

NPM套件管理工具

npm全名為 Node Package Manager是 Nodejs的套件(package)管理工具類似 Perl的 ppm或 PHP的 PEAR等安裝 npm後使用npm install

module name指令即可安裝新套件維護管理套件的工作會更加輕鬆

npm可以讓Nodejs的開發者直接利用擴充線上的套件庫(packagesregistry)加速軟體專案的開發npm提供很友善的搜尋功能可以快速找到安裝需要的套件當這些套件發行新版本時npm也可以協助開發者自動更新這些套件

npm不僅可用於安裝新的套件它也支援搜尋列出已安裝模組及更新的功能

51 安裝 NPM

Nodejs在 063版本開始內建 npm讀者安裝的版本若是此版本或更新的版本就可以略過以下安裝說明

若要檢查 npm是否正確安裝可以使用以下的指令

npm -v

41

Nodejs中文電子書

執行結果說明

若 npm正確安裝執行 npm -v將會看到類似 110-2的版本訊息

若讀者安裝的 Nodejs版本比較舊或是有興趣嘗試自己動手安裝 npm工具則可以參考以下的說明

安裝於Windows系統

Nodejs for Windows 於 062 版開始內建 npm使用 nodejsorg 官方提供的安裝程式不需要進一步的設定就可以立即使用 npm指令對於Windows的開發者來說大幅降低環境設定的問題與門檻

除了使用 Nodejs內建的 npm讀者也可以從 npm官方提供的以下網址

httpnpmjsorgdist

這是由 npm提供的 Fancy Windows Install版本請下載壓縮檔(例如npm-110-3zip)並將壓縮檔內容解壓縮至 Nodejs的安裝路徑(例如CProgram Filesnodejs)

解壓縮後在 Nodejs的安裝路徑下應該有以下的檔案及資料夾

bull npmcmd(檔案)

bull node modules(資料夾)

安裝於 Linux系統

Ubuntu Linux的使用者可以加入 NPM Unoffcial PPA這個 repository即可使用 apt-get完成 npm安裝

42 5 NPM套件管理工具

Nodejs中文電子書

Ubuntu Linux使用 apt-get安裝 npm

sudo apt-get install python-software-properties

sudo add-apt-repository ppagias-kay-leenpm

sudo apt-get update

sudo apt-get install npm

npm官方提供的安裝程式 installsh可以適用於大多數的 Linux系統使用這個安裝程式請先確認

1 系統已安裝 curl工具(請使用 curl --version查看版本訊息)

2 已安裝 Nodejs並且 PATH正確設置

3 Nodejs的版本必須大於 04x

以下為 npm提供的安裝指令

curl httpnpmjsorginstallsh | sh

安裝成功會看到如下訊息

installsh安裝成功的訊息

npm10105 homeUSERNAMElocalnodelibnode_modulesnpm

It worked

安裝於Mac OS X

建議採用與 Nodejs相同的方式進行 npm的安裝例如使用MacPorts安裝 Nodejs就同樣使用MacPorts安裝 npm這樣對日後的維護才會更方便容易

使用 MacPorts安裝 npm是本書比較建議的方式它可以讓 npm的安裝移除及更新工作自動化將會幫助開發者節省寶貴時間

51 安裝 NPM 43

Nodejs中文電子書

安裝MacPorts的提示

在MacPorts網站可以取得 OS X系統版本對應的安裝程式(例如 106或 107)httpwwwmacportsorg安裝過程會詢問系統管理者密碼使用預設的選項完成安裝即可安

裝MacPorts之後在終端機執行 port -v將會看到MacPorts的版本訊息

安裝 npm之前先更新MacPorts的套件清單以確保安裝的 npm是最新版本

sudo port -d selfupdate

接著安裝 npm

sudo port install npm

若讀者的 Nodejs並非使用MacPorts安裝則不建議使用MacPorts安裝npm因為 MacPorts會自動檢查並安裝相依套件而 npm相依 nodejs所以MacPorts也會一併將 nodejs套件安裝造成先前讀者使用其它方式安裝的 nodejs被覆蓋

讀者可以先使用 MacPorts安裝 curl(sudo port install curl)

再參考 Linux的 installsh安裝方式即可使用 npm官方提供的安裝程式

NPM安裝後測試

npm是指令列工具(command-line tool)使用時請先打開系統的文字終端機工具

測試 npm安裝與設定是否正確請輸入指令如下

npm -v

或是

npm --version

如果 npm已經正確安裝設定就會顯示版本訊息

44 5 NPM套件管理工具

Nodejs中文電子書

執行結果(範例)

110-2

52 使用 NPM安裝套件

npm目前擁有超過 6000種套件(packages)可以在 npm registry使用關鍵字搜尋套件

httpsearchnpmjsorg

舉例來說在關鍵字欄位輸入「coffee-script」下方的清單就會自動列出包含 coffee-script關鍵字的套件

接著我們回到終端機模式的操作npm的指令工具本身就可以完成套

件搜尋的任務

例如以下的指令同樣可以找出 coffee-script相關套件

npm search coffee-script

以下是搜尋結果的參考畫面

52 使用 NPM安裝套件 45

Nodejs中文電子書

找到需要的套件後(例如 express)即可使用以下指令安裝

npm install coffee-script

值得注意的一點是使用 npm install會將指定的套件安裝在工

作目錄(Working Directory)的 node modules資料夾下

以Windows為例如果執行 npm install的目錄位於

Cproject1

那麼 npm將會自動建立一個 node modules的子目錄(如果不存在)

Cproject1node modules

並且將下載的套件放置於這個子目錄例如

Cproject1node modulescoffee-script

這個設計讓專案可以個別管理相依的套件並且可以在專案佈署或發

行時將這些套件(位於 node modules)一併打包方便其它專案的使用者不必再重新下載套件

這個 npm install的預設安裝模式為 local(本地)只會變更當前專案的資料夾不會影響系統

另一種安裝模式稱為 global(全域)這種模式會將套件安裝到系統資

料夾也就是 npm安裝路徑的 node modules資料夾例如

CProgram Filesnodejsnode modules

46 5 NPM套件管理工具

Nodejs中文電子書

是否要使用全域安裝可以依照套件是否提供新指令來判斷舉例來說express 套件提供 express 這個指令而 coffee-script 則提供 coffee

指令

在 local 安 裝 模 式 中這 些 指 令 的 程 式 檔 案會 被 安 裝 到node modules的 bin這個隱藏資料夾下除非將bin的路徑加入 PATH環境變數否則要執行這些指令將會相當不便

為了方便指令的執行我們可以在 npm install 加上 -g 或 --

global參數啟用 global安裝模式例如

npm install -g coffee-script

npm install -g express

使用 global安裝模式需要注意執行權限的問題若權限不足可能會出現類似以下的錯誤訊息

npm ERR Error EACCES permission denied rsquorsquo

npm ERR

npm ERR Please try running this command again as rootAdministrator

要獲得足夠得執行權限請參考以下說明

bull Windows 7 或 2008 以上在「命令提示字元」的捷徑按右鍵選擇「以系統管理員身分執行」執行 npm指令時就會具有 Administrator身分

bull Mac OS X或 Linux系統可以使用 sudo指令例如

sudo npm install -g express

bull Linux系統可以使用 root權限登入或是以「sudo su -」切換成 root身分(使用 root權限操作系統相當危險因此並不建議使用這種方式)

若加上 -g參數使用 npm install -g coffee-script完成安裝

後就可以在終端機執行 coffee指令例如

coffee -v

52 使用 NPM安裝套件 47

Nodejs中文電子書

執行結果(範例)

CoffeeScript version 120

53 套件的更新及維護

除了前一節說明的 search 及 install 用法npm 還提供其他許多指令(commands)

使用 npm help可以查詢可用的指令

npm help

執行結果(部分)

where ltcommandgt is one of

adduser apihelp author bin bugs c cache completion

config deprecate docs edit explore faq find get

help help-search home i info init install la link

list ll ln login ls outdated owner pack prefix

prune publish r rb rebuild remove restart rm root

run-script s se search set show star start stop

submodule tag test un uninstall unlink unpublish

unstar up update version view whoami

使用 npm help command可以查詢指令的詳細用法例如

npm help list

接下來本節要介紹開發過程常用的 npm指令

使用 list可以列出已安裝套件

npm list

48 5 NPM套件管理工具

Nodejs中文電子書

執行結果(範例)

-- coffee-script120

- express256

- connect185

| -- formidable108

-- mime124

-- mkdirp007

-- qs041

檢視某個套件的詳細資訊例如

npm show express

升級所有套件(如果該套件已發佈更新版本)

npm update

升級指定的套件

npm update express

移除指定的套件

npm uninstall express

54 使用 packagejson

對於正式的 Nodejs專案可以建立一個命名為 packagejson的設

定檔(純文字格式)檔案內容參考範例如下

54 使用 packagejson 49

Nodejs中文電子書

packagejson(範例)

rdquonamerdquo rdquoapplication-namerdquo

rdquoversionrdquo rdquo001rdquo

rdquoprivaterdquo true

rdquodependenciesrdquo

rdquoexpressrdquo rdquo255rdquo

rdquocoffee-scriptrdquo rdquolatestrdquo

rdquomongooserdquo rdquogt= 253rdquo

其中 name與 version依照專案的需求設置

需要注意的是 dependencies的設定它用於指定專案相依的套件名

稱及版本

bull rdquoexpressrdquo rdquo255rdquo

代表此專案相依版本 255的 express套件

bull rdquocoffee-scriptrdquo rdquolatestrdquo

使用最新版的 coffee-script套件(每次更新都會檢查新版)

bull rdquomongooserdquo rdquogt= 253rdquo

使用版本大於 253的 mongoose套件

假設某個套件的新版可能造成專案無法正常運作就必須指定套件的

版本避免專案的程式碼來不及更新以相容新版套件通常在開發初期的

專案需要盡可能維持新套件的相容性(以取得套件的更新或修正)可以

用「gt=」設定最低相容的版本或是使用「latest」設定永遠保持最新

套件

50 5 NPM套件管理工具

6

Express介紹

在前面的 nodejs基礎當中介紹許多許多開設 http的使用方法及介紹以及許多基本的 nodejs基本應用

接下來要介紹一個套件稱為 express [Express](httpexpressjscom)這個套件主要幫忙解決許多 nodejs http server所需要的基本服務讓開發 httpservice變得更為容易不需要像之前需要透過層層模組(module)才有辦法開始編寫自己的程式

這個套件是由 TJ Holowaychuk 製作而成的套件裡面包含基本的路由處理 (route)http資料處理(GETPOSTPUT)另外還與樣板套件(jshtml template engine)搭配同時也可以處理許多複雜化的問題

61 Express安裝

安裝方式十分簡單只要透過之前介紹的 NPM就可以使用簡單的指令安裝指令如下

這邊建議需要將此套件安裝成為全域模組方便日後使用

51

Nodejs中文電子書

62 Express基本操作

express的使用也十分簡單先來建立一個基本的 hello world

var app = require(rsquoexpressrsquo)createServer()

port = 1337

applisten(port)

appget(rsquorsquo function(req res)

ressend(rsquohello worldrsquo)

)

consolelog(rsquostart express servernrsquo)

可以從上面的程式碼發現基本操作與 nodejs http的建立方式沒有太大差異主要差在當我們設定路由時可以直接透過 appget方式設定回應與接受方式

63 Express路由處理

Express對於 http服務上有許多包裝讓開發者使用及設定上更為方便例如有幾個路由設定那我們就統一藉由 appget來處理

Create http server

appget(rsquorsquo function(req res)

ressend(rsquohello worldrsquo)

)

appget(rsquotestrsquo function(req res)

ressend(rsquotest renderrsquo)

)

appget(rsquouserrsquo function(req res)

ressend(rsquouser pagersquo)

)

52 6 Express介紹

Nodejs中文電子書

如上面的程式碼所表示appget可以帶入兩個參數第一個是路徑名稱設定第二個為回應函式 (call back function)回應函式裡面就如同之前的 createServer方法裡面包含 requestresponse兩個物件可供使用使用者就可以透過瀏覽器輸入不同的 url切換到不同的頁面顯示不同的結果

路由設定上也有基本的配對方式讓使用者從瀏覽器輸入的網址可以

是一個變數只要符合型態就可以有對應的頁面產出例如

Create http server

appget(rsquouseridrsquo function(req res)

ressend(rsquouser rsquo + reqparamsid)

)

appget(rsquonumberrsquo function(req res)

ressend(rsquonumber rsquo + reqparamsnumber)

)

裡 面 使 用 到number 從 網 址 輸 入 之 後 就 可 以 直 接 使 用reqparamsnumber 取得所輸入的資料變成 url 參數使用當然前面也是可以加上路徑的設定userid在瀏覽器上路徑必須符合userxxx透

過 reqparamsid就可以取到 xxx這個字串值

另外express參數處理也提供了路由參數配對處理也可以透過正規表示法作為參數設定

var app = require(rsquoexpressrsquo)createServer()

port = 1337

applisten(port)

appget(^ip((d23)((d23))((d23))((d23))) function(req res)

ressend(reqparams)

)

上面程式碼可以發現後面路由設定的型態是正規表示法裡面設定

格式為ip之後必須要加上 ip型態才會符合資料格式同時取得 ip資料已經由正規表示法將資料做分群因此可以取得 ip的四個數字

此程式執行之後可以透過瀏覽器測試輸入網址為 localhost3000ip25525510010可以從頁面獲得資料

63 Express路由處理 53

Nodejs中文電子書

此章節全部範例程式碼如下

overview

author Caesar Chi

blog clonnblogspotcom

version 20120226

create server

var app = require(rsquoexpressrsquo)createServer()

port = 1337

applisten(port)

normal style

appget(rsquorsquo function(req res)

ressend(rsquohello worldrsquo)

)

appget(rsquotestrsquo function(req res)

ressend(rsquotest renderrsquo)

)

parameter style

appget(rsquouseridrsquo function(req res)

ressend(rsquouser rsquo + reqparamsid)

)

appget(rsquonumberrsquo function(req res)

ressend(rsquonumber rsquo + reqparamsnumber)

)

REGX style

appget(^ip((d23)((d23))((d23))) function(req res)

ressend(reqparams)

)

appget(rsquorsquo function(req res)

ressend(rsquoPage not foundrsquo 404)

54 6 Express介紹

Nodejs中文電子書

)

consolelog(rsquostart express servernrsquo)

64 Express middleware

Express 裡面有一個十分好用的應用概念稱為 middleware可以透過middleware做出複雜的效果同時上面也有介紹 next方法參數傳遞就是靠 middleware的概念來傳遞參數讓開發者可以明確的控制程式邏輯

create http server

appuse(expressbodyParser())

appuse(expressmethodOverride())

appuse(expresssession())

上面都是一種 middleware的使用方式透過 appuse方式裡面載入函式執行方法回應函式會包含三個基本參數responserequestnext其中next表示下一個 middleware執行函式同時會自動將預設三個參數繼續帶往下個函式執行底下有個實驗

上面的片段程式執行後開啟瀏覽器連結上 localhost1337會發現伺服器回應結果順序如下

first middle ware

second middle ware

execute middle ware

end middleware function

從上面的結果可以得知剛才設定的 middleware都生效了在 appuse設定的 middleware是所有 url皆會執行方法如果有指定特定方法就可以使用 appget的 middleware設定在 appget函式的第二個參數就可以帶入函式或者是匿名函式只要函式裡面最後會接受 request response next這三個參數同時也有正確指定 next函式的執行時機最後都會執行到最後一個方法當然開發者也可以評估程式邏輯要執行到哪一個階段讓邏輯

可以更為分明

64 Express middleware 55

Nodejs中文電子書

65 Express路由應用

在實際開發上可能會遇到需要使用參數等方式混和變數一起使用

express裡面提供了一個很棒的處理方法 appall這個方式可以先採用基本路由配對再將設定為每個不同的處理方式開發者可以透過這個方式簡

化自己的程式邏輯

overview

author

version 20120226

create server

var app = require(rsquoexpressrsquo)createServer()

port = 1337

users = [

name rsquoClonnrsquo

name rsquoChirsquo

]

applisten(port)

appall(rsquouseridoprsquo function(req res next)

requser = users[reqparamsid]

if (requser)

next()

else

next(new Error(rsquocannot find user rsquo + reqparamsid))

)

appget(rsquouseridrsquo function(req res)

ressend(rsquoviewing rsquo + requsername)

)

appget(rsquouserideditrsquo function(req res)

ressend(rsquoediting rsquo + requsername)

)

56 6 Express介紹

Nodejs中文電子書

appget(rsquouseriddeletersquo function(req res)

ressend(rsquodeleting rsquo + requsername)

)

appget(rsquorsquo function(req res)

ressend(rsquoPage not foundrsquo 404)

)

consolelog(rsquostart express servernrsquo)

內部宣告一組預設的使用者分別給予名稱設定藉由 appall這個方法可以先將路由雛形建立再接下來設定 appget的路徑格式只要符合格式就會分配進入對應的方法中像上面的程式當中如果使用者輸入路徑為user0除了執行 appall程式之後執行 next方法就會對應到路徑設定為userid的這個方法當中如果使用者輸入路徑為user0edit就會執行到useridedit的對應方法

66 Express GET應用範例

我們準備一個使用 GET方法傳送資料的表單

ltDOCTYPE htmlgt

lthtmlgt

ltheadgt

ltmeta http-equiv=rdquoContent-Typerdquo content=rdquotexthtml charset=utf8rdquogt

lttitlegtNodejs 菜鳥筆記 (1)lttitlegt

ltlink rel=rdquostylesheetrdquo href=rdquocssstylecssrdquo type=rdquotextcssrdquo media=rdquoallrdquo gt

ltheadgt

ltbodygt

lth1gtNodejs 菜鳥筆記-註冊lth1gt

ltform id=rdquosignuprdquo method=rdquoGETrdquo action=rdquohttplocalhost3000Signuprdquogt

ltlabelgt 使用者名稱ltlabelgtltinput type=rdquotextrdquo id=rdquousernamerdquo name=rdquousernamerdquo gtltbrgt

ltlabelgt 電子郵件ltlabelgtltinput type=rdquotextrdquo id=rdquoemailrdquo name=rdquoemailrdquo gtltbrgt

ltinput type=rdquosubmitrdquo value=rdquo 註冊我的帳號rdquo gtltbrgt

ltformgt

ltbodygt

lthtmlgt

66 Express GET應用範例 57

Nodejs中文電子書

這個表單沒有什麼特別的地方我們只需要看第 9 行form 使用的method是 GET然後 action是rdquohttplocalhost3000Signupldquo等一下我們要來撰寫Signup這個 URL Path的處理程式

處理 Signup行為

我們知道所謂的 GET方法會透過 URL來把表單的值給帶過去以上面的表單來說到時候 URL會以這樣的形式傳遞

httplocalhost3000Signupusername=xxxampemail=xxx

所以要能處理這樣的資料必須有以下功能

bull 解析 URL

bull 辨別動作是 Signup

bull 解析出 username和 email

一旦能取得 username和 email的值程式就能加以應用了

處理 Signup的程式碼雛形

load module

var url = require(rsquourlrsquo)

urlData = urlparse(requrltrue)

action = urlDatapathname

reswriteHead(200 rdquoContent-Typerdquordquotexthtml charset=utf-8rdquo)

if (action === rdquoSignuprdquo)

user = urlDataquery

resend(rdquolth1gtrdquo + userusername + rdquo 歡迎您的加入 lth1gtltpgt 我們已經將會員啟用信寄至rdquo + useremail + rdquoltpgtrdquo)

首先需要加載 url module它是用來協助我們解析 URL的模組接著使用 urlparse方法第一個傳入 url字串變數也就是 requrl另外第二個參數的用意是設為 ture則引進 querystring模組來協助處理預設是 false它影響到的是 urlDataquery設為 true會傳回物件不然就只是一般的字串urlparse會將字串內容整理成一個物件我們把它指定給 urlData

action變數作為記錄 pathname這是我們稍後要來判斷目前網頁的動作是什麼接著先將 html表頭資訊 (Header)準備好再來判斷路徑邏輯如

58 6 Express介紹

Nodejs中文電子書

果是 Signup這個動作就把 urlDataquery裡的資料指定給 user然後輸出userusername和 useremail把使用者從表單註冊的資料顯示於頁面中

最後進行程式測試啟動 nodejs主程式之後開啟瀏覽器就會看到表單填寫完畢按下送出就可以看到結果了

完整 nodejs程式碼如下

var http = require(rsquohttprsquo)

url = require(rsquourlrsquo)

fs = require(rdquofsrdquo)

server

server = httpcreateServer(function (reqres)

var urlData

encode = rdquoutf8rdquo

filePath = rdquoviewexpress_get_example_formhtmlrdquo

action

urlData = urlparse(requrltrue)

action = urlDatapathname

reswriteHead(200 rdquoContent-Typerdquordquotexthtml charset=utf-8rdquo)

if (action === rdquoSignuprdquo)

user = urlDataquery

resend(rdquolth1gtrdquo + userusername + rdquo 歡迎您的加入 lth1gtltpgt 我們已經將會員啟用信寄至rdquo + useremail + rdquoltpgtrdquo)

else

fsreadFile(filePath encode function(err file)

reswrite(file)

resend()

)

)

serverlisten(3000)

consolelog(rsquoServer 跑起來了現在時間是rsquo + new Date())

66 Express GET應用範例 59

Nodejs中文電子書

67 Express POST應用範例

一開始準備基本的 html表單傳送內容以 POST方式form的 action屬性設定為 POST其餘 html內容與前一個範例應用相同

ltDOCTYPE htmlgt

lthtmlgt

ltheadgt

ltmeta http-equiv=rdquoContent-Typerdquo content=rdquotexthtml charset=utf8rdquogt

lttitlegtNodejs 菜鳥筆記 (1)lttitlegt

ltlink rel=rdquostylesheetrdquo href=rdquocssstylecssrdquo type=rdquotextcssrdquo media=rdquoallrdquo gt

ltheadgt

ltbodygt

lth1gtNodejs 菜鳥筆記-註冊lth1gt

ltform id=rdquosignuprdquo method=rdquoPOSTrdquo action=rdquohttplocalhost3000Signuprdquogt

ltlabelgt 使用者名稱ltlabelgtltinput type=rdquotextrdquo id=rdquousernamerdquo name=rdquousernamerdquo gtltbrgt

ltlabelgt 電子郵件ltlabelgtltinput type=rdquotextrdquo id=rdquoemailrdquo name=rdquoemailrdquo gtltbrgt

ltinput type=rdquosubmitrdquo value=rdquo 註冊我的帳號rdquo gtltbrgt

ltformgt

ltbodygt

lthtmlgt

nodejs的程式處理邏輯與前面 GET範例類似部分程式碼如下

qs = require(rsquoquerystringrsquo)

if (action === rdquoSignuprdquo)

formData = rsquorsquo

reqon(rdquodatardquo function (data)

formData += data

)

reqon(rdquoendrdquo function ()

user = qsparse(formData)

resend(rdquolth1gtrdquo + userusername + rdquo 歡迎您的加入 lth1gtltpgt 我們已經將會員啟用信寄至rdquo + useremail + rdquoltpgtrdquo)

)

60 6 Express介紹

Nodejs中文電子書

主要加入了rsquoquerystringrsquo這個moduel方便我們等一下解析由表單 POST回來的資料另外加入一個 formData的變數用來搜集待等一下表單回傳的資料前面的 GET範例我們只從 req拿出 url的資料這次要在利用req身上的事件處理

JavaScript 在訂閱事件時使用 addEventListener而 nodejs 使用的則是on這邊加上了監聽 data的事件會在瀏覽器傳送資料到Web Server時被執行參數是它所接收到的資料型態是字串

接著再增加 end的事件當瀏覽器的請求事件結束時它就會動作

由於瀏覽器使用 POST在上傳資料時會將資料一塊塊地上傳因為我們在監聽 data事件時透過 formData變數將它累加起來lt不過由於我們

上傳的資料很少一次就結束不過如果日後需要傳的是資料比較大的檔

案這個累加動作就很重要

當資料傳完就進到 end 事件中會用到 qsparse 來解析 formDataformData的內容是字串內容是

username=wordsmithampemail=wordsmith40somewhere

而 qsparse可以幫我們把這個 querystring轉成物件的格式也就是

username=wordsmithampemail=wordsmith40somewhere

一旦轉成物件並指定給 user之後其他的事情就和 GET方法時操作的一樣寫 response的表頭將內容回傳並將 userusername和 useremail代入到內容中

修改完成後接著執行 nodejs程式啟動 web server開啟瀏覽器進入表單測試看看POST的方式能否順利運作

完整程式碼如下

var http = require(rsquohttprsquo)

url = require(rsquourlrsquo)

fs = require(rdquofsrdquo)

qs = require(rsquoquerystringrsquo)

server

server = httpcreateServer(function (reqres)

var urlData

67 Express POST應用範例 61

Nodejs中文電子書

encode = rdquoutf8rdquo

filePath = rdquoviewexpress_post_example_formhtmlrdquo

formData

action

urlData = urlparse(requrltrue)

action = urlDatapathname

reswriteHead(200 rdquoContent-Typerdquordquotexthtml charset=utf-8rdquo)

if (action === rdquoSignuprdquo)

formData = rsquorsquo

reqon(rdquodatardquo function (data)

formData += data

)

reqon(rdquoendrdquo function ()

user = qsparse(formData)

resend(rdquolth1gtrdquo + userusername + rdquo 歡迎您的加入 lth1gtltpgt 我們已經將會員啟用信寄至rdquo + useremail + rdquoltpgtrdquo)

)

else

fsreadFile(filePath encode function(err file)

reswrite(file)

resend()

)

)

serverlisten(3000)

consolelog(rsquoServer 跑起來了現在時間是rsquo + new Date())

62 6 Express介紹

Nodejs中文電子書

68 Express AJAX應用範例

在 Nodejs要使用 Ajax傳送資料並且與之互動在接受資料的部份沒有太大的差別client端不是用 GET就是用 POST來傳資料重點在處理完後用 JSON格式回傳當然 Ajax不見得只傳 JSON格式有時是回傳一段 HTML碼不過後者對伺服器來說基本上就和前兩篇沒有差別了所以我們還是以回傳 JSON做為這一回的主題

這一回其實大多數的工作都會落在前端 Ajax上面前端要負責發送與接收資料並在接收資料後撤掉原先發送資料的表單並將取得的資料

改成 HTML格式之後放上頁面

首先先準備 HTML靜態頁面資料

ltDOCTYPE htmlgt

lthtmlgt

ltheadgt

lttitlegtNodejs 菜鳥筆記 (3)lttitlegt

ltlink rel=rdquostylesheetrdquo href=rdquocssstylecssrdquo type=rdquotextcssrdquo media=rdquoallrdquo gt

ltscript src=rdquohttpsajaxgoogleapiscomajaxlibsjquery171jqueryminjsrdquogtltscriptgt

ltheadgt

ltbodygt

lth1gtNodejs 菜鳥筆記-註冊 (用 Ajax)lth1gt

ltform id=rdquosignuprdquo action=rdquoSignuprdquo method=rdquoPOSTrdquogt

ltlabelgt 使用者名稱ltlabelgtltinput type=rdquotextrdquo id=rdquousernamerdquo name=rdquousernamerdquo gtltbrgt

ltlabelgt 電子郵件ltlabelgtltinput type=rdquotextrdquo id=rdquoemailrdquo name=rdquoemailrdquo gtltbrgt

ltinput type=rdquosubmitrdquo value=rdquo 註冊我的帳號rdquo gtltbrgt

ltformgt

ltdiv id=rdquoresultrdquogt

ltdivgt

ltscript type=rdquotextjavascriptrdquogt

$(function()$(rsquosignup input[type=rdquosubmitrdquo]rsquo)click(function(e)

var user =

userusername = $(rdquousernamerdquo)val()useremail = $(rdquoemailrdquo)val()

$post(rdquoSignuprdquouserfunction(data)greet(data)

)

68 Express AJAX應用範例 63

Nodejs中文電子書

epreventDefault()

)

var greet = function(msg)

var resultNode = $(rsquoresultrsquo)greeting = $(rsquolth2gtHirsquo+msgusername+rsquo 你的會員 id 是rsquo+ msgid +rsquo 我們會將會員啟動信寄至rsquo+msgemail+rsquolth2gtrsquo)

$(rsquosignuprsquo)hide()resultNodehtml(rdquordquo)

resultNodeappend(greeting)

)

ltscriptgt

ltbodygt

lthtmlgt

HTML頁面上準備了一個表單用來傳送註冊資料接著直接引用了Google CDN來載入 jQuery用來幫我們處理 Ajax的工作這次要傳送和接收的工作很大的變動都在 HTML頁面上的 JavaScript當中我們要做的事有 (相關 jQuery處理這邊不多做贅述指提起主要功能解說)

bull 用 jQUery取得 submit按鈕綁定它的 click動作

bull 取得表單 username和 email的值存放在 user這個物件中

bull 用 jQuery的 $post方法將 user的資料傳到 Server

bull 一旦成功取得資料後透過 greet這個 function組成回報給 user的訊息

bull 清空原本給使用者填資料的表單

bull 將 Server回傳的 usernameemail和 id這 3個資料組成回應的訊息

bull 將訊息放到原本表單的位置

經過以上的處理後一個 Ajax的表單的基本功能已經完成

接著進行 nodejs主要程式的編輯部分程式碼如下

var fs = require(rdquofsrdquo)

64 6 Express介紹

Nodejs中文電子書

qs = require(rsquoquerystringrsquo)

if (action === rdquoSignuprdquo)

formData = rsquorsquo

reqon(rdquodatardquo function (data)

formData += data

)

reqon(rdquoendrdquo function ()

var msg

user = qsparse(formData)

userid = rdquo123456rdquo

msg = JSONstringify(user)

reswriteHead(200 rdquoContent-Typerdquordquoapplicationjson charset=utf-8rdquordquoContent-Lengthrdquomsglength)

resend(msg)

)

這裡的程式和前面 POST範例基本上大同小異差別在

bull 幫 user的資料加上 id隨意存放一些文字進去讓 Server回傳的資料多於 Client端傳上來的不然會覺得 Server都沒做事

bull 增加了 msg 這個變數存放將 user 物件 JSON 文字化的結果JSONstringify這個轉換函式是 V8引擎所提供的如果你好奇的話

bull 大重點來了我們要告訴 Client端這次回傳的資料格式是 JSON所在 Content-type和 Content-Length要提供給 Client

Server很輕鬆就完成任務了最後進行程式測試啟動 nodejs主程式之後開啟瀏覽器就會看到表單填寫完畢按下送出就可以看到結果了

最後 nodejs本篇範例程式碼如下

var http = require(rsquohttprsquo)

url = require(rsquourlrsquo)

fs = require(rdquofsrdquo)

qs = require(rsquoquerystringrsquo)

server

68 Express AJAX應用範例 65

Nodejs中文電子書

server = httpcreateServer(function (reqres)

var urlData

encode = rdquoutf8rdquo

filePath = rdquoviewexpress_ajax_example_formhtmlrdquo

formData

action

urlData = urlparse(requrltrue)

action = urlDatapathname

if (action === rdquoSignuprdquo)

formData = rsquorsquo

reqon(rdquodatardquo function (data)

formData += data

)

reqon(rdquoendrdquo function ()

var msg

user = qsparse(formData)

userid = rdquo123456rdquo

msg = JSONstringify(user)

reswriteHead(200 rdquoContent-TyperdquordquoapplicationjsonrdquordquoContent-Lengthrdquomsglength)

resend(msg)

)

else

fsreadFile(filePath encode function(err file)

reswriteHead(200 rdquoContent-Typerdquordquotexthtml charset=utf-8rdquo)

reswrite(file)

resend()

)

)

serverlisten(3000)

66 6 Express介紹

Nodejs中文電子書

consolelog(rsquoServer 跑起來了現在時間是rsquo + new Date())

69 原始資料提供

bull [NodeJS初學者筆記 (1)-用 GET傳送資料] (httpithelpithomecomtwquestion10087402)

bull [NodeJS初學者筆記 (2)-用 POST傳送資料] (httpithelpithomecomtwquestion10087489)

bull [NodeJS 初學者筆記 (3)-用 Ajax 傳送資料] (httpithelpithomecomtwquestion10087627)

69 原始資料提供 67

Nodejs中文電子書

68 6 Express介紹

7

CoffeeScript

bull Spine

bull Hem

bull Spineapp

Letrsquos Have a Cup of CoffeeScript

bull es5-shim ECMAScript 5 compatibility shims for legacy JavaScript engines

ESnext

node-iform - A middleware for node-validator httpsgithubcomguileennode-iform

69

Nodejs中文電子書

70 7 CoffeeScript

8

製作一個 Hubot的 Plurk Adapter

81 應用事項提醒

此應用範例以CoffeeScript編寫主要程式架構採用Github釋放的Hubot專案以下有幾個主要注意事項請讀者注意

bull Hubot 一開始的架構除了內建的兩個 Adapter 之外其餘都要以Nodejs的Module方式才能運作

bull Hubot的 binhubot裡面寫著 npm install所以不管你怎麼改原始碼也不能改變第一點的狀況

其實上述都在討論同一件事情NodeJS的模組而且模組不能設定相對路徑之類的來安裝一定要包裝成 npm相符和格式才有辦法正確執行

82 建立 Adapter

首先需要準備兩個檔案

bull packagejson

bull plurkcoffee

71

Nodejs中文電子書

有這兩個就足以變成 NodeJS的模組了在開始 Coding之前先來設定一下 packagejson相依性

rdquonamerdquo rdquohubot-plurkrdquo

rdquoversionrdquo rdquo011rdquo

rdquomainrdquo rdquoplurkrdquo

rdquodependenciesrdquo

rdquohubotrdquo rdquogt=205rdquo

rdquooauthrdquo rdquordquo

rdquocronrdquordquordquo

這邊會使用到NodeJS的 cron模組(Module)而登入 Plurk需要OAuth標準授權才行所以也需要加載 OAuth模組

注意Hubot在讀取非內建模組時會自動在前面加上 hubot-的前置

83 建立 Robot跟 API

本篇應用基本上程式碼大部分參考 Hubot - Twitter Adapter來製作主要差異只有在使用 OAuth這個模組Twitter有 Streaming可用而 Plurk則得用 Comet方式來達到即時讀取

plurkcoffee程式碼

Robot = require(rdquohubotrdquo)robot()

Adapter = require(rdquohubtordquo)adapter()

EventEmitter = require(rdquoeventsrdquo)EventEmitter

oauth = require(rdquooauthrdquo)

cronJob = require(rdquocronrdquo)CronJob

class Plurk exntends Adapter

class PlurkStreaming exnteds EventEmitter

72 8 製作一個 Hubot的 Plurk Adapter

Nodejs中文電子書

先弄個基本架構主要 Class皆參考 Twitter Adapter命名方式接著再將 Plurk這個 Class增加幾個Method基本上只要有 run send reply就夠了而 run用來做初始化的部分

class Plurk entends Adapter

send (plurk id strings⋯) -gt

reply (plurk id strings⋯) -gt

run -gt

看起來有點東西接著來處理主要的 Plurk API結合部分

class PlurkStreaming extends EventEmitter

consuctor (options) -gt

plurk (callback) -gt

觀察河道

getChannel -gt

取得 Comet 網址

reply (plurk id message) -gt

回噗

acceptFriends -gt

接受好友

get (path callback) -gt

GET 請求

post (path body callback)-gt

POST 請求(其實是裝飾)

request (method path body callback)-gt

主要的 OAuth 請求

comet (server callback)-gt

噗浪的 Comet 傳回是 JavaScript Callback 要另外處理後才會變成 JSON

然後把注意力集中到 constructor上先把建構子弄好

constructor (options) -gt

super()

if optionskey and optionssecret and optionstoken and optionstoken secret

key = optionskey

secret = optionssecret

token = optionstoken

token secret = optionstoken secret

83 建立 Robot跟 API 73

Nodejs中文電子書

建立 OAuth 連接

consumer = new oauthOAuth(

rdquohttpwwwplurkcomOAuthrequest tokenrdquo

rdquohttpwwwplurkcomOAuthaccess tokenrdquo

key

secret

rdquo10rdquo

rdquohttpwwwplurkcomOAuthauthorizerdquo

rdquoHMAC-SHA1rdquo

)

domain = rdquowwwplurkcomrdquo

初始化取得 Comet 網址

do getChannel

else

throw new Error(rdquo 參數不足需要 Key Secret Token Token Secretrdquo)

接著來處理 request這個 method

request (method path body callback) -gt

記錄一下這次的 Request

consolelog(rdquohttpdomainpathrdquo)

Callback 這邊先不丟進去要用另一種方式處理

request = consumerget(rdquohttpdomainpathrdquo token token secret null)

requeston rdquoresponserdquo (res) -gt

reson rdquodatardquo (chunk) -gt

parseResponse(chunk+rsquorsquo callback)

reson rdquoendrdquo (data) -gt

consolelog rdquoEnd Request pathrdquo

reson rdquoerrorrdquo (data) -gt

consolelog rdquoError rdquo + data

requestend()

處理資料

parseResponse = (data callback) -gt

if datalength gt 0

用 TryCatch 避免處理 JSON 出錯導致整個中斷

try

callback null JSONparse(data)

catch err

74 8 製作一個 Hubot的 Plurk Adapter

Nodejs中文電子書

consolelog(rdquoError Parse JSONrdquo + data err)

繼續執行

callback null data ||

大致上就是這樣上面程式的架構已經將整個 Hubot Plurk Adapter完成因為在測試時竟然因為噗浪 Lag而沒讀到完整的 Comet資料然後造成程式異常為了避免這個問題發生因此需要加上為了完美呈現需要再

加上 Comet的處理所以要使用到 EventEmitter的功能

comet (server callback) -gt

在 Callback 裡面會找不到自身所以設定區域變數

self =

記錄一下這次的 Request

consolelog(rdquo[Comet] serverrdquo)

Callback 這邊先不丟進去要用另一種方式處理

request = consumerget(rdquohttpdomainpathrdquo token token secret null)

requeston rdquoresponserdquo (res) -gt

reson rdquodatardquo (chunk) -gt

parseResponse(chunk+rsquorsquo callback)

reson rdquoendrdquo (data) -gt

consolelog rdquoEnd Request pathrdquo

請求結束發出事件通知可以進行下一次請求

selfemit rdquonextPlurkrdquo

reson rdquoerrorrdquo (data) -gt

consolelog rdquoError rdquo + data

requestend()

處理資料

parseResponse = (data callback) -gt

if datalength gt 0

用 trycatch 避免失敗中斷

try

去掉 JavaScript 的 Callback

data = datamatch(CometChannelscriptCallback((+))s)

jsonData = rdquordquo

if data

83 建立 Robot跟 API 75

Nodejs中文電子書

jsonData = JSONparse(data[1])

else

如果沒有任何 Match 嘗試直接 parse

jsonData = JSONparse(data)

catch err

consolelog(rdquo[Comet] Errorrdquo data err)

用 TryCatch 避免處理 JSON 出錯導致整個中斷

try

只傳入 json 的 data 部分

callback null jsonDatadata

catch err

consolelog(rdquo[Comet]Error Parse JSONrdquo + data err)

繼續執行

callback null data ||

後面的 get跟 post就簡單多了

get (path callback) -gt

request(rdquoGETrdquo path null callback)

post (path body callback) -gt

request(rdquoPOSTrdquo path body callback)

接著處理取的 Comet網址的 getChannel

getChannel -gt

self =

get rdquoAPPRealtimegetUserChannelrdquo (error data) -gt

if error

檢查是否有 comet server

if datacomet server

selfchannel = datacomet server

如果沒有 Channel Ready 就嘗試連接會失敗

selfemit(rsquochannel readyrsquo)

那麼先來處理 Plurk Adaper好處理的部份

send (plruk id strings⋯)-gt

跟 Reply 一樣直接交給 reply 做

reply plurk id strings⋯

76 8 製作一個 Hubot的 Plurk Adapter

Nodejs中文電子書

reply (plurk id strings⋯) -gt

stringsforEach (message) =gt

botreply(plruk id message)

接著把 run處理好就可以上線運作摟

run -gt

self =

options =

key processenvHUBOT PLURK KEY

secret processenvHUBOT PLURK SECRET

token processenvHUBOT PLURK TOKEN

token secret processenvHUBOT PLURK TOKEN SECRET

創建剛剛的 API

bot = new PlurkStreaming(options)

依照 Twitter 的 new RobotTextMessage 會沒有反應所以參考 hubot-minecraft 的方式

r = robotconstructor

處理噗浪河道訊息

doPlurk = (data)-gt

檢查是否為回噗

if dataresponse

datacontent raw = dataresponsecontent raw

datauser id = dataresponseuser id

確定有噗浪 ID 跟訊息

if dataplurk id and datacontent raw

selfreceive new rTextMessage(dataplurk id datacontent raw)

取得 Comet Server 完成開始第一次 Comet 連接

boton rdquochannel readyrdquo () -gt

botplurk selfdoPlurk

上一次 Comet 完成繼續 Polling

boton rdquonextPlurkrdquo ()-gt

botplurk selfdoPlurk

定時接受好友邀請

do botacceptFriends

bot = bot

83 建立 Robot跟 API 77

Nodejs中文電子書

終於完成 AdapterHubot專案裡面的 scripts資料夾內是互動部分不需要像 Adapter如此大費周章處理只新增檔案並且設計好對白之後就會回噗了我開發用的機器人在此大家可以去跟他玩玩[httpplurkcomelct9620 bot](httpplurkcomelct9620 bot)

84 原始資料提供

bull [製 作 一 個 Hubot 的 噗 浪 Adapter](http revo-skillfrosttw blog20120318create-a-hubot-plurk-adapter)

78 8 製作一個 Hubot的 Plurk Adapter

9

Nodejs好用工具介紹

91 logio

Real-time log monitoring in your browser

httplogioorg

nodejs (gt=0412 lt=0611)

npm config set unsafe-perm true

npm install -g --prefix=usrlocal logio

logio server start

httplocalhost8998

79

Nodejs中文電子書

80 9 Nodejs好用工具介紹

10

精選文章

101 Express

bull 使用 NodeJS + Express從 GETPOST Request取值 [邀稿中]

bull link2 [邀稿中]

bull link3 [邀稿中]

81

Nodejs中文電子書

82 10 精選文章

11

參考資源

111 Nodejs書籍

bull Programming Nodejs April 2012 (est) Travis Swicegood Pragmatic Bookshelf

bull e Little Book on CoffeeScript January 2012 Alex MacCaw OrsquoReilly Media

bull Node for Front-End Developers January 2012 Garann Means OrsquoReilly Media

bull Building Hypermedia APIs with HTML5 and Node November 2011 MikeAmundsen OrsquoReilly Media

bull Node Web Development August 2011 David Herron PacktPub

bull CoffeeScript Accelerated JavaScript Development July 2011 Trevor BurnhamPragmatic Bookshelf

bull Getting Started with GEO CouchDB and Nodejs July 2011 Mick omp-son OrsquoReilly Media

bull What Is Node July 2011 Brett McLaughlin OrsquoReilly Media

bull Node Up and Running May 2011 Tom Hughes-Croucher Mike WilsonOrsquoReilly Media

83

Nodejs中文電子書

bull Hands-on Nodejs e Nodejs introduction and API reference May 2011 Pe-dro Teixeira LeanPub

bull e Node Beginner Book Manuel Kiessling

bull Mastering Nodejs visionmedia

112 Nodejs影音教學

bull e Node Sessions e Best of OSCON 2011 August 2011 OrsquoReilly Media(Video)

bull Tom Hughes-Croucher on Node March 2011 OrsquoReilly Media (Video)

113 Nodejs教學網站

bull Node Tuts

114 Nodejs課程

bull Introduction To Nodejs Van Nguyen CodeLesson

84 11 參考資源

Page 17: Nodejs Wiki

2

JavaScript與 NodeJS

其實使用 JavaScript 在網頁端與伺服器端的差距並不大但是為了使NodeJS可以發揮他最強大的能力有一些知識還是必要的所以還是針對這些主題介紹一下其中 Event LoopScope以及 Callback其實是比較需要了解的基本知識cpscurrying ow control是更進階的技巧與應用

21 Event Loop

可能很多人在寫 Javascript 時並不知道他是怎麼被執行的這個時候可以參考一下 jQuery作者 John Resig一篇好文章介紹事件及 timer怎麼在瀏覽器中執行How JavaScript Timers Work通常在網頁中所有的Javascript執行完畢後(這部份全部都在 global scope跑除非執行函數)接下來就是如 John Resig解釋的這樣所有的事件處理函數以及 timer執行的函數會排在一個 queue結構中利用一個無窮迴圈不斷從 queue中取出函數來執行這個就是 event loop

(除了 John Resig的那篇文章Nicholas C Zakas的 ldquoProfessional Javascriptfor Web Developer 2nd editionrdquo 有一個試閱本httpyuiblogcomassetspdfzakas-projs-2ed-ch18pdf598頁剛好也有簡短的說明)

所以在 Javascript中雖然有非同步但是他並不是使用執行緒所有

11

Nodejs中文電子書

的事件或是非同步執行的函數都是在同一個執行緒中利用 event loop的方式在執行至於一些比較慢的動作例如 IO網頁 render re ow等實際動作會在其他執行緒跑等到有結果時才利用事件來觸發處理函數來處理

這樣的模型有幾個好處沒有執行緒的額外成本所以反應速度很快不會

有任何程式同時用到同一個變數不必考慮 lock也不會產生 dead lock所以程式撰寫很簡單但是也有一些潛在問題任一個函數執行時間較長都

會讓其他函數更慢執行(因為一個跑完才會跑另一個)在多核心硬體普遍

的現在無法用單一的應用程式 instance發揮所有的硬體能力用 NodeJS撰寫伺服器程式碰到的也是一樣的狀況要讓系統發揮 event loop的效能就要盡量利用事件的方式來組織程式架構另外對於一些有可能較為耗

時的操作可以考慮使用 processnextTick函數來讓他以非同步的方式執行避免在同一個函數中執行太久擋住所有函數的執行

如果想要測試 event loop怎樣在「瀏覽器」中運行可以在函數中呼叫alert()這樣會讓所有 Javascript的執行停下來尤其會干擾所有使用 timer的函數執行有一個簡單的例子這是一個會依照設定的時間間隔嚴格執

行動作的動畫如果時間過了就會跳過要執行的動作點按圖片以後人

物會快速旋轉但是在旋轉執行完畢前按下「delay」按鈕讓 alert訊息等久一點接下來的動畫就完全不會出現了

22 Scope與 Closure

要快速理解 JavaScript的 Scope(變數作用範圍)原理只要記住他是Lexical Scope就差不多了簡單地說變數作用範圍是依照程式定義時(或者叫做程式文本)的上下文決定而不是執行時的上下文決定

為了維護程式執行時所依賴的變數即使執行時程式運行在原本的

scope之外他的變數作用範圍仍然維持不變這時程式依賴的自由變數(定義時不是 local的而是在上一層 scope定義的變數)一樣可以使用就好像被關閉起來所以叫做 Closure用程式看比較好懂

function outter(arg1)

arg1 及 free_variable1 對 inner 函數來說都是自由變數

var free_variable1 = 3

return function inner(arg2)

12 2 JavaScript與 NodeJS

Nodejs中文電子書

var local_variable1 =2arg2 及 local_variable1 對 inner 函數來說都是本地變數

return arg1 + arg2 + free_variable + local_variable1

var a = outter(1)變數 a就是 outter函數執行後返回的 inner函數 var b =a(4)執行 inner函數執行時上下文已經在 outter函數之外但是仍然能正常執行而且可以使用定義在 outter函數裡面的 arg1及 free variable1變數 consolelog(b)結果 10

在 Javascript中scope最主要的單位是函數(另外有 global及 eval)所以有可能製造出 closure的狀況通常在形式上都是有巢狀的函數定義而且內側的函數使用到定義在外側函數裡面的變數

Closure有可能會造成記憶體洩漏主要是因為被參考的變數無法被垃圾收集機制處理造成佔用的資源無法釋放所以使用上必須考慮清楚

不要造成意外的記憶體洩漏(在上面的例子中如果 a一直未執行使用到的記憶體就不會被釋放)

跟透過函數的參數把變數傳給函數比較起來Javascript Engine會比較難對 Closure進行最佳化如果有效能上的考量這一點也需要注意

23 Callback

要介紹 Callback之前要先提到 JavaScript的特色

JavaScript是一種函數式語言(functional language)所有 Javascript語言內的函數都是高階函數 (higher order function這是數學名詞計算機用語好像是 rst class function意指函數使用沒有任何限制與其他物件一樣)也就是說函數可以作為函數的參數傳給函數也可以當作函數的返回值這個特性讓 Javascript的函數使用上非常有彈性而且功能強大

callback在形式上其實就是把函數傳給函數然後在適當的時機呼叫傳入的函數Javascript使用的事件系統通常就是使用這種形式NodeJS中有一個物件叫做 EventEmitter這是 NodeJS事件處理的核心物件所有會使用事件處理的函數都會「繼承」這個物件(這裡說的繼承實

作上應該像是 mixin)他的使用很簡單可以使用物件on(事件名稱callback

23 Callback 13

Nodejs中文電子書

函數) 或是物件addListener(事件名稱callback 函數) 把你想要處理事件的函數傳入在物件中可以使用物件emit(事件名稱 參數) 呼叫傳入的callback函數這是 Observer Pattern的簡單實作而且跟在網頁中使用 DOM的 addEventListener使用上很類似也很容易上手不過 NodeJS是大量使用非同步方式執行的應用所以程式邏輯幾乎都是寫在 callback函數中當邏輯比較複雜時大量的 callback會讓程式看起來很複雜也比較難單元測試舉例來說

var p client = new Db(lsquointegration tests 20rsquo new Server(ldquo127001rdquo 27017) lsquopkrsquoCustomPKFactory) p clientopen(function(err p client)

p clientdropDatabase(function(err done)

p clientcreateCollection(lsquotest custom keyrsquo function(err collection)

collectioninsert(lsquoarsquo1 function(err docs)

collection nd(lsquo idrsquonew ObjectID(ldquoaaaaaaaaaaaardquo) function(err cursor)

cursortoArray(function(err items) testassertEquals(1 itemslength) p clientclose()

)

)

)

)

)

)

這是在網路上看到的一段操作 mongodb的程式碼為了循序操作所以必須在一個 callback裡面呼叫下一個動作要使用的函數這個函數裡面還是會使用 callback最後就形成一個非常深的巢狀

這樣的程式碼會比較難進行單元測試有一個簡單的解決方式是

盡量不要使用匿名函數來當作 callback或是 event handler透過這樣的方式

14 2 JavaScript與 NodeJS

Nodejs中文電子書

就可以對各個 handler做單元測試了例如

var http = require(lsquohttprsquo) var tools =

cookieParser function(request response) if(requestheaders[rsquoCookiersquo]) do parsing

var server = httpcreateServer(function(request response)

thisemit(lsquoinitrsquo request response)

) serveron(lsquoinitrsquo toolscookieParser) serverlisten(8080 lsquo127001rsquo)

更進一步可以把 tools改成外部 module例如叫做 toolsjs

moduleexports = cookieParser function(request response) if(requestheaders[rsquoCookiersquo]) do parsing

然後把程式改成

var http = require(lsquohttprsquo)

var server = httpcreateServer(function(request response) thisemit(lsquoinitrsquo re-quest response)

) serveron(lsquoinitrsquo require(lsquo toolsrsquo)cookieParser) serverlisten(8080lsquo127001rsquo)

這樣就可以單元測試 cookieParser了例如使用 nodeunit時可以這樣寫

var testCase = require(lsquonodeunitrsquo)testCase moduleexports = testCase(

ldquosetUprdquo function(cb) thisrequest = headers Cookielsquoname1val1 name2val2rsquo thisresponse = thisresult= name1rsquoval1rsquoname2rsquoval2rsquo

cb()

ldquotearDownrdquo function(cb)

cb()

23 Callback 15

Nodejs中文電子書

ldquonormal caserdquo function(test)

testexpect(1) var obj = require(lsquotoolsrsquo)cookieParser(thisrequest thisresponse)testdeepEqual(obj thisresult) testdone()

)

善於利用模組可以讓程式更好維護與測試

24 CPS(Continuation-Passing Style)

cps 是 callback 使用上的特例形式上就是在函數最後呼叫 callback這樣就好像把函數執行後把結果交給 callback 繼續運行所以稱作continuation-passing style利用 cps可以在非同步執行的情況下透過傳給 callback的這個 cps callback來獲知 callback執行完畢或是取得執行結果例如

lthtmlgt ltbodygt ltdiv id=rdquopanelrdquo style=rdquovisibilityhiddenrdquogtlt divgtlt bodygt lt htmlgt ltscriptgt var request = new XMLHttpRequest() re-questopen(lsquoGETrsquo lsquotest749txt timestamp=rsquo+new Date()getTime() true) re-questaddEventListener(lsquoreadystatechangersquo function(next)

return function() if(thisreadyState===4ampampthisstatus===200) next(thisresponseText)lt== 傳入的 cps callback 在動作完成時執行並取得結果進一步處理

(function(str)lt==這個匿名函數就是 cps callback

documentgetElementById(lsquopanelrsquo)innerHTML=str docu-mentgetElementById(lsquopanelrsquo)stylevisibility = lsquovisiblersquo

) false) requestsend() ltscriptgt

進一步的應用也可以參考 2-6流程控制

16 2 JavaScript與 NodeJS

Nodejs中文電子書

25 函數返回函數與 Currying

前面的 cps範例裡面使用了函數返回函數這是為了把 cps callback傳遞給 onreadystatechange事件處理函數的方法(因為這個事件處理函數並沒有設計好會傳送接收這樣的參數)實際會執行的事件處理函數其實是內層返回的那個函數之外包覆的這個函數主要是為了利用 Closure把 next傳給內層的事件處理函數這個方法更常使用的地方是為了解決一些

scope問題例如

ltscriptgt var accu=0count=10 for(var i=0 iltcount i++)

setTimeout(

function() countndash accu+=i if(countlt=0)

consolelog(accu)

50)

ltscriptgt

最後得出的結果會是 100而不是想像中的 45這是因為等到 setTime-out指定的函數執行時變數 i已經變成 10而離開迴圈了要解決這個問題就需要透過 Closure來保存變數 i

ltscriptgt var accu=0count=10 for(var i=0 iltcount i++)

setTimeout(

function(i) return function() countndash

accu+=i if(countlt=0)

consolelog(accu)

(i)

50)

25 函數返回函數與 Currying 17

Nodejs中文電子書

淺藍色底色的部份是跟上面例子不一樣的地方 ltscriptgt

函數返回函數的另外一個用途是可以暫緩函數執行例如

function add(m n) return m+n

var a = add(20 10) consolelog(a)

add這個函數必須同時輸入兩個參數才有辦法執行如果我希望這個函數可以先給它一個參數等一些處理過後再給一個參數然後得到結

果就必須用函數返回函數的方式做修改

function add(m)

return function(n) return m+n

var wait another arg = add(20)先給一個參數 var a = function(arr)

var ret=0 for(var i=0iltarrlengthi++) ret+=arr[i] return ret

([1234])計算一下另一個參數 var b = wait another arg(a)然後再繼續執行 consolelog(b)

像這樣利用函數返回函數使得原本接受多個參數的函數可以一次

接受一個參數直到參數接收完成才執行得到結果的方式有一個學名就

叫做Currying

綜合以上許多奇技淫巧就可以透過用函數來處理函數的方式調整

程式流程接下來看看

26 流程控制

(以 sync方式使用 async函數避開巢狀 callback循序呼叫 async callback等奇技淫巧)

建議參考

bull httphowtonodeorgcontrol- ow

bull httphowtonodeorgcontrol- ow-part-ii

18 2 JavaScript與 NodeJS

Nodejs中文電子書

bull httphowtonodeorgcontrol- ow-part-iii

bull httpblogmixunet20110202essential-node-js-patterns-and-snippets

這幾篇都是非常經典的 NodeJSJavascript流程控制好文章(阿mixu是在介紹一些 pattern時提到這方面的主題)不過我還是用幾個簡單的程式介紹一下做法跟概念

並發與等待

下面的程式參考了 mixu文章中的做法

var wait = function(callbacks done) consolelog(lsquowait startrsquo) var counter = call-backslength var results = [] var next = function(result) 接收函數執行結果並判斷是否結束執行 resultspush(result) if(ndashcounter == 0) done(results)如果結束執行就把所有執行結果傳給指定的 callback處理 for(var i = 0 i lt callbackslength i++) 依次呼叫所有要執行的函數 callbacks[i](next) consolelog(lsquowait endrsquo)

wait( [ function(next) setTimeout(function() consolelog(lsquodone arsquo) var result =500 next(result) 500) function(next) setTimeout(function() con-solelog(lsquodone brsquo) var result = 1000 next(result) 1000) function(next)setTimeout(function() consolelog(lsquodone crsquo) var result = 1500 next(1500) 1500) ] function(results) var ret = 0 i=0 for( iltresultslength i++) ret+= results[i] consolelog(lsquodone all result lsquo+ret)

)

執行結果wait start wait end done a done b done c done all result 3000

可以看出來其實 wait並不是真的等到所有函數執行完才結束執行而是在所有傳給他的函數執行完畢後(不論同步非同步)才執行處理結

果的函數(也就是 done())

不過這樣的寫法還不夠實用因為沒辦法實際讓函數可以等待執行

完畢又能當作事件處理函數來實際使用上面參考到的 Tim Caswell的文

26 流程控制 19

Nodejs中文電子書

章裡面有一種解法不過還需要額外包裝(在他的例子中)NodeJS核心的 fs物件把一些函數(例如 readFile)用 Currying處理類似像這樣

var fs = require(lsquofsrsquo) var readFile = function(path)

return function(callback errback)

fsreadFile(path function(err data)

if(err) errback()

else callback(data)

)

其他部份可以參考 Tim Caswell的文章他的 Doparallel跟上面的 wait差不多意思這裡只提示一下他沒說到的地方

另外一種做法是去修飾一下 callback當他作為事件處理函數執行後再用 cps的方式取得結果

ltscriptgt function Wait(fns done)

var count = 0 var results = [] thisgetCallback = function(index)

count++ return (function(waitback)

return function() var i=0args=[]for(iltargumentslengthi++)

argspush(arguments[i])

argspush(waitback) fns[index]apply(thisargs)

)(function(result) resultspush(result) if(ndashcount == 0)

20 2 JavaScript與 NodeJS

Nodejs中文電子書

done(results)

)

var a = new Wait(

[ function(waitback) consolelog(lsquodone arsquo) var result = 500 wait-back(result) function(waitback) consolelog(lsquodone brsquo) var result =1000 waitback(result) function(waitback) consolelog(lsquodone crsquo) varresult = 1500 waitback(result) ] function(results) var ret = 0 i=0for( iltresultslength i++) ret += results[i] consolelog(lsquodone allresult lsquo+ret)

) var callbacks = [agetCallback(0)agetCallback(1)agetCallback(0)agetCallback(2)]一次取出要使用的 callbacks避免結果提早送出 setTimeout(callbacks[0]500) setTimeout(callbacks[1] 1000) setTimeout(callbacks[2] 1500) setTime-out(callbacks[3] 2000) 當所有取出的 callbacks執行完畢就呼叫 done()來處理結果 ltscriptgt

執行結果

done a done b done a done c done all result 3500

上面只是一些小實驗更成熟的作品是 Tim Caswell 的 stephttpsgithubcomcreationixstep

如果希望真正使用同步的方式寫非同步則需要使用 Promisejs這一類的 library來轉換非同步函數不過他結構比較複雜 XD(見仁見智不過有些人認為 Promise有點過頭了)httpblogsmsdncombrbucktonarchive20110815promise-js-2-0-promise-framework-for-javascriptaspx

如果想不透過其他 Library做轉換又能直接用同步方式執行非同步函數大概就要使用一些需要額外 compile原始程式碼的方法了例如 BrunoJouhier的 streamlinejshttpsgithubcomSagestreamlinejs

26 流程控制 21

Nodejs中文電子書

循序執行

循序執行可以協助把非常深的巢狀 callback結構攤平例如用這樣的簡單模組來做(serialjs)

moduleexports = function(funs) var c = 0 if(isArrayOfFunctions(funs))

throw(lsquoArgument type was not matched Should be array of func-tionsrsquo)

return function()

var args = Arrayprototypeslicecall(arguments 0) if((cgt=funslength))

c++ return funs[c-1]apply(this args)

function isArrayOfFunctions(f ) if(typeof f == lsquoobjectrsquo) return false if(flength)return false if(fconcat) return false if(fsplice) return false var i = 0 for(iltflength i++)

if(typeof f[i] == lsquofunctionrsquo) return false

return true

簡單的測試範例(testSerialjs)使用 fs 模組確定某個 path 是檔案然後讀取印出檔案內容這樣會用到兩層的 callback所以測試中有使用serial的版本與 nested callbacks的版本做對照

var serial = require(lsquoserialrsquo) fs = require(lsquofsrsquo) path = lsquodclientjsrsquo cb = serial([ func-tion(err data)

if(err)

if(dataisFile) fsreadFile(path cb)

22 2 JavaScript與 NodeJS

Nodejs中文電子書

else consolelog(err)

function(err data)

if(err) consolelog(lsquo[ attened by searial]rsquo) con-solelog(datatoString(lsquoutf8rsquo))

else consolelog(err)

]) fsstat(path cb)

fsstat(path function(err data) 第一層 callback if(err)

if(dataisFile)

fsreadFile(path function(err data) 第二層 callback if(err)

consolelog(lsquo[nested callbacks]rsquo) con-solelog(datatoString(lsquoutf8rsquo))

else consolelog(err)

)

else consolelog(err)

)

關鍵在於這些 callback的執行是有順序性的所以利用 serial返回的一個函數 cb來取代這些 callback然後在 cb中控制每次會循序呼叫的函數

26 流程控制 23

Nodejs中文電子書

就可以把巢狀的 callback攤平成循序的 function陣列(就是傳給 serial函數的參數)

測試中的dclientjs 是一個簡單的 dnode 測試程式放在跟 testSerialjs同一個目錄

var dnode = require(lsquodnodersquo)

dnodeconnect(8000 lsquolocalhostrsquo function(remote)

remoterestart(function(str) consolelog(str) processexit()

)

)

執行測試程式後出現結果

[ attened by searial] var dnode = require(lsquodnodersquo)

dnodeconnect(8000 lsquolocalhostrsquo function(remote)

remoterestart(function(str) consolelog(str) processexit()

)

)

[nested callbacks] var dnode = require(lsquodnodersquo)

dnodeconnect(8000 lsquolocalhostrsquo function(remote)

remoterestart(function(str) consolelog(str) processexit()

)

)

對照起來看兩種寫法的結果其實是一樣的但是利用 serialjs巢狀的 callback結構就會消失

不過這樣也只限於順序單純的狀況如果函數執行的順序比較複雜

(不只是一直線)還是需要用功能更完整的流程控制模組比較好例如

httpsgithubcomcaolanasync

24 2 JavaScript與 NodeJS

3

Nodejs安裝與設定

本篇將講解如何在各個不同 OS建立 NodeJS環境目前 NodeJS 048版本環境架設方式需依賴 Linux指令才可編譯完成當然在不同作業系統中也已經有 NodeJS package可以直接使用指令快速架設以下各不同作業系統解說如何安裝 NodeJS

31 Ubuntu Linux

更新推薦使用 nvm

git clone gitgithubcomcreationixnvmgit ~nvm

echo rdquo ~nvmnvmshrdquo gtgt ~bashrc

nvm install v0614

nvm alias default v0614

以 上 可 參 考 http dreamerslabcom blog tw how-to-setup-a-node-js-development-environment-on-ubuntu-11-04

使用 APT套件管理工具是常見的方法以下是使用社群提供的 PPA安裝方式

sudo apt-get install python-software-properties

sudo add-apt-repository ppachris-leanodejs-devel

25

Nodejs中文電子書

sudo apt-get update

sudo apt-get install nodejs

32 Other Linux

Linux很適合作為 NodeJS的伺服器作業系統及開發環境安裝前請先確認以下套件已正確安裝

bull curl (wget)用來下載檔案的工具

bull git先進的版本控制工具

bull g++ GNU C++軟體編譯工具

bull make GNU軟體專案建置工具

安裝指令如下如設有權限問題請在指令前面加上 sudo

git clone httpsgithubcomjoyentnodegit

cd node

git checkout v067

configure

make

sudo make install

接著測試 nodeJS是否正常執行

node --version

出現版本訊息即表示安裝成功

33 Windows

nodeJS 在 v060 版本之後開始正式支援 windows native直接使用nodeexe 就可以執行程式支援性完全與 linux 相同更棒的部份就是不需經過編譯經過下載之後簡單設定完成立即開發 node程式

26 3 Nodejs安裝與設定

Nodejs中文電子書

下載 nodejs安裝檔案 lthttpnodejsorgdownloadgt

如此完成 windows native nodeexe安裝接著可以進入 command line執行測試在 command line輸入rdquonoderdquo會出現 nodejs版本訊息畫面表示安裝完成

33 Windows 27

Nodejs中文電子書

28 3 Nodejs安裝與設定

4

Nodejs基礎

前篇文章已經由介紹安裝至設定都有完整介紹nodeJS 內部除了javascript常用的函式 (function)物件 (object)之外也有許多不同的自訂物件nodeJS預設建立這些物件為核心物件是為了要讓開發流程更為這些資料在官方文件已經具有許多具體說明接下來將會介紹在開發 nodeJS程式時常見的物件特性與使用方法

41 nodejs http伺服器建立

在 lsquonodejs官方網站 lthttpnodejsorggtlsquo裡面有舉一個最簡單的 HTTP伺服器建立一開始初步就是建立一個伺服器平台讓 nodejs可以與瀏覽器互相行為每種語言一開始的程式建立都是以 Hello world開始最初也從 Hello world帶各位進入 nodejs的世界

輸入以下程式碼儲存檔案為 node basic http hello worldjs

var server

ip = rdquo127001rdquo

port = 1337

http = require(rsquohttprsquo)

server = httpcreateServer(function (req res)

29

Nodejs中文電子書

reswriteHead(200 rsquoContent-Typersquo rsquotextplainrsquo)

resend(rsquoHello Worldnrsquo)

)

serverlisten(port ip)

consolelog(rdquoServer running at httprdquo + ip + rdquordquo + port)

程式碼解講一開始需要有幾個基本的變數

bull ip 機器本身的 ip位置因為使用本地端因此設定為 127001

bull port 需要開通的阜號通常設定為 http port 80因範例不希望與基本 port相衝隨意設定為 1337

在 nodejs 的程式中有許多預設的模組可以使用因此需要使用require方法將模組引入在這邊我們需要使用 http這個模組因此將 http載入Http模組裡面內建有許多方法可以使用這邊採用 createServer創建一個基本的 http伺服器再將 http伺服器給予一個 server變數裡面的回呼函式 (call back function)可以載入 http伺服器的資料與回應方法 (requestresponse)在程式裡面就可以看到我們直接回應給瀏覽器端所需的 Header回應內容

reswriteHead(200 rsquoContent-Typersquo rsquotextplainrsquo)

resend(rsquoHello Worldnrsquo)

Http伺服器需要設定 port ip在最後需要設定 Http監聽需要使用到listen事件監聽所有 Http伺服器行為

httplisten(port ip)

所有事情都完成之後需要確認伺服器正確執行因此使用 console在javascript裡就有這個原生物件console所印出的資料都會顯示於 nodejs伺服器頁面這邊印出的資料並不會傳送到使用者頁面上之後許多除壞

(debug)都會用到 console物件

consolelog(rdquoServer running at httprdquo + ip + rdquordquo + port)

30 4 Nodejs基礎

Nodejs中文電子書

42 nodejs http路徑建立

前面已經介紹如何建立一個簡單的 http伺服器接下來本章節將會介紹如何處理伺服器路徑 (rout)問題在 http的協定下所有從瀏覽器發出的要求 (request)都需要經過處理路徑上的建立也是如此

路徑就是指伺服器 ip位置或者是網域名稱之後對於伺服器給予的要求修改剛才的 hello world檔案修改如下

server = httpcreateServer(function (req res)

consolelog(requrl)

reswriteHead(200 rsquoContent-Typersquo rsquotextplainrsquo)

resend(rsquohello worldnrsquo)

)

重新啟動 nodejs程式後在瀏覽器端測試一下路徑行為結果如下圖

當在瀏覽器輸入 http1270011337test 在伺服器端會收到兩個要求一個是我們輸入的test要求另外一個則是faviconicotest的路徑要求http伺服器本身需要經過程式設定才有辦法回應給瀏覽器端所需要的回應在伺服器中所有的路徑要求都是需要被解析才有辦法取得資料從

42 nodejs http路徑建立 31

Nodejs中文電子書

上面解說可以了解到在 nodejs當中所有的路徑都需要經過設定未經過設定的路由會讓瀏覽器無法取得任何資料導致錯誤頁面的發生底下將會解

說如何設定路由同時避免發生錯誤情形先前 nodejs程式需要增加一些修改才能讓使用者透過瀏覽器在不同路徑時有不同的結果根據剛才

的程式做如下的修改

var server

ip = rdquo127001rdquo

port = 1337

http = require(rsquohttprsquo)

url = require(rsquourlrsquo)

path

server = httpcreateServer(function (req res)

path = urlparse(requrl)

reswriteHead(200 rsquoContent-Typersquo rsquotextplainrsquo)

switch (pathpathname)

case rdquoindexrdquo

resend(rsquoI am indexnrsquo)

break

case rdquotestrdquo

resend(rsquothis is test pagenrsquo)

break

default

resend(rsquodefault pagenrsquo)

break

)

serverlisten(port ip)

consolelog(rdquoServer running at httprdquo + ip + rdquordquo + port)

程式做了片段的修改首先載入 url模組另外增加一個 path變數url模組就跟如同他的命名一般專門處理 url字串處理裡面提供了許多方法來解決路徑上的問題因為從瀏覽器發出的要求路徑可能會帶有多種需求

或者 GET參數組合等因此我們需要將路徑單純化取用路徑部分的資料即可例如使用者可能會送出 http1270011337testsend=1如果直接

32 4 Nodejs基礎

Nodejs中文電子書

信任 requrl就會收到結果為testsend=1所以需要透過 url模組的方法將路徑資料過濾

在這邊使用 urlparse的方法裡面帶入網址格式資料會回傳路徑資料為了後需方便使用將回傳的資料設定到 path變數當中在回傳的路徑資料裡面包含資訊如下圖

這邊只需要使用單純的路徑要求直接取用 pathpathname就可以達到我們的目的

最後要做路徑的判別在不同的路徑可以指定不同的輸出在範例中

有三個可能結果第一個從瀏覽器輸入index就會顯示 index結果test 就會呈現出 test頁面最後如果都不符合預期的輸入會直接顯示 default的頁面最後的預防可以讓瀏覽器不會出現非預期結果讓程式的可靠性提昇

底下為測試結果

42 nodejs http路徑建立 33

Nodejs中文電子書

43 nodejs檔案讀取

前面已經介紹如何使用路由(rount)做出不同的回應實際應用只有在瀏覽器只有輸出幾個文字資料總是不夠的在本章節中將介紹如何使

用檔案讀取輸出檔案資料讓使用者在前端瀏覽器也可以讀取到完整的

html css javascript檔案輸出

檔案管理最重要的部分就是 lsquoFile system lthttpnodejsorgdocslatestapifshtmlgtlsquo這個模組此模組可以針對檔案做管理監控讀取等行為裡面有許多預設的方法底下是檔案輸出的基本範例底下會有兩個檔案

第一個是靜態 html檔案

ltDOCTYPE htmlgt

lthtml xmlns=rdquohttpwwww3org1999xhtmlrdquo xmllang=rdquozh-CNrdquo lang=rdquozh-CNrdquogt

ltheadgt

ltmeta http-equiv=rdquoContent-Typerdquo content=rdquotexthtml charset=UTF-7rdquo gt

lttitlegtnodejs index html filelttitlegt

ltheadgt

ltbodygt

34 4 Nodejs基礎

Nodejs中文電子書

lth1gtnodejs index html filelth1gt

ltbodygt

lthtmlgt

另一個為 nodejs程式

var fs = require(rdquofsrdquo)

filename = rdquostaticindexhtmlrdquo

encode = rdquoutf8rdquo

fsreadFile(filename encode function(err file)

consolelog(file)

)

一開始直接載入 le system模組 載入名稱為 fs讀取檔案主要使

用的方法為 readFile裡面以三個參數路徑 ( le path) 編碼方式 (encoding)

回應函式 (callback)路徑必須要設定為靜態 html所在位置才能指定到正確的檔案靜態檔案的編碼方式也必須正確這邊使用靜態檔案的編

碼為 utf8如果編碼設定錯誤nodejs讀取出來檔案結果會使用 byte raw格式輸出如果錯誤編碼格式會導致輸出資料為 byte raw

回應函式中裡面會使用兩個變數error為錯誤資訊如果讀取的檔案不存在或者發生錯誤error數值會是 true如果成功讀取資料 error將會是 falsecontent則是檔案內容資料讀取後將會把資料全數丟到 content這個變數當中

最後程式的輸出結果畫面如下

43 nodejs檔案讀取 35

Nodejs中文電子書

44 nodejs http靜態檔案輸出

前面已經了解如何讀取本地端檔案接下來將配合 http伺服器路由讓每個路由都能夠輸出相對應的靜態 html檔案首先新增加幾個靜態 html檔案

ltDOCTYPE htmlgt

lthtml xmlns=rdquohttpwwww3org1999xhtmlrdquo xmllang=rdquozh-CNrdquo lang=rdquozh-CNrdquogt

ltheadgt

ltmeta http-equiv=rdquoContent-Typerdquo content=rdquotexthtml charset=UTF-7rdquo gt

lttitlegtnodejs index html filelttitlegt

ltheadgt

ltbodygt

lth1gtnodejs index html filelth1gt

ltbodygt

lthtmlgt

ltDOCTYPE htmlgt

lthtml xmlns=rdquohttpwwww3org1999xhtmlrdquo xmllang=rdquozh-CNrdquo lang=rdquozh-CNrdquogt

ltheadgt

ltmeta http-equiv=rdquoContent-Typerdquo content=rdquotexthtml charset=UTF-7rdquo gt

lttitlegtnodejs test html filelttitlegt

ltheadgt

ltbodygt

lth1gtnodejs test html filelth1gt

ltbodygt

lthtmlgt

ltDOCTYPE htmlgt

lthtml xmlns=rdquohttpwwww3org1999xhtmlrdquo xmllang=rdquozh-CNrdquo lang=rdquozh-CNrdquogt

ltheadgt

ltmeta http-equiv=rdquoContent-Typerdquo content=rdquotexthtml charset=UTF-7rdquo gt

lttitlegtnodejs static html filelttitlegt

ltheadgt

ltbodygt

lth1gtnodejs static html filelth1gt

ltbodygt

lthtmlgt

36 4 Nodejs基礎

Nodejs中文電子書

準備一個包含基本路由功能的 http伺服器

var server

ip = rdquo127001rdquo

port = 1337

http = require(rsquohttprsquo)

url = require(rsquourlrsquo)

server = httpcreateServer(function (req res)

var path = urlparse(requrl)

)

serverlisten(port ip)

consolelog(rdquoServer running at httprdquo + ip + rdquordquo + port)

加入 le system 模組使用 readFile 的功能將這一段程式放置於

createServer的回應函式中

fsreadFile(filePath encode function(err file)

)

readFile的回應函式裡面加入頁面輸出讓瀏覽器可以正確讀到檔案在這邊我們設定讀取的檔案為 html 靜態檔案所以 Content-type 設定為texthtml讀取到檔案的內容將會正確輸出成 html靜態檔案

fsreadFile(filePath encode function(err file)

reswriteHead(200 rsquoContent-Typersquo rsquotexthtmlrsquo)

reswrite(file)

resend()

)

到這邊為止基本的程式內容都已經完成剩下一些細節的調整首先

路徑上必須做調整目前的靜態檔案全部都放置於 static資料夾底下設定一個變數來記住資料夾位置

接著將瀏覽器發出要求路徑與資料夾組合讀取正確 html靜態檔案使用者有可能會輸入錯誤路徑所以在讀取檔案的時候要加入錯誤處理

同時回應 404伺服器無法正確回應的 http header格式

加入這些細節的修改一個基本的 http 靜態 html輸出伺服器就完成了完整程式碼如下

44 nodejs http靜態檔案輸出 37

Nodejs中文電子書

var server

ip = rdquo127001rdquo

port = 1337

http = require(rsquohttprsquo)

fs = require(rdquofsrdquo)

folderPath = rdquostaticrdquo

url = require(rsquourlrsquo)

path

filePath

encode = rdquoutf8rdquo

server = httpcreateServer(function (req res)

path = urlparse(requrl)

filePath = folderPath + pathpathname

fsreadFile(filePath encode function(err file)

if (err)

reswriteHead(404 rsquoContent-Typersquo rsquotextplainrsquo)

resend()

return

reswriteHead(200 rsquoContent-Typersquo rsquotextapplicationrsquo)

reswrite(file)

resend()

)

)

serverlisten(port ip)

consolelog(rdquoServer running at httprdquo + ip + rdquordquo + port)

45 nodejs http GET資料擷取

http伺服器中除了路由之外另一個最常使用的方法就是擷取 GET資料本單元將會介紹如何透過基本 http伺服器擷取瀏覽器傳來的要求擷取 GET資料

在 http協定中GET參數都是藉由 URL從瀏覽器發出要求送至伺服器

38 4 Nodejs基礎

Nodejs中文電子書

端基本的傳送網址格式可能如下

http127001testsend=1amptest=2

上面這段網址裡面的 GET參數就是 send而這個變數的數值就為 1如果想要在 http伺服器取得 GET資料需要在瀏覽器給予的要求 (request)做處理

首先需要載入 query string這個模組這個模組主要是用來將字串資料

過濾後轉換成javascript物件

qs = require(rsquoquerystringrsquo)

接著在第一階段利用 url模組過濾瀏覽器發出的 URL資料後將回應的物件裡面的 query這個變數是一個字串值資料過濾後如下

send=1amptest=2

透過 query string使用 parse這個方法將資料轉換成 javascript物件就表示 GET的資料已經被伺服器端正式擷取下來

path = urlparse(requrl)

parameter = qsparse(pathquery)

整個 nodejs http GET參數完整擷取程式碼如下

var server

ip = rdquo127001rdquo

port = 1337

http = require(rsquohttprsquo)

qs = require(rsquoquerystringrsquo)

url = require(rsquourlrsquo)

server = httpcreateServer(function (req res)

var path = urlparse(requrl)

parameter = qsparse(pathquery)

consoledir(parameter)

reswriteHead(200 rsquoContent-Typersquo rsquotextplainrsquo)

reswrite(rsquoBrowser test GET parameternrsquo)

resend()

)

45 nodejs http GET資料擷取 39

Nodejs中文電子書

serverlisten(port ip)

consolelog(rdquoServer running at httprdquo + ip + rdquordquo + port)

程式運作之後由瀏覽器輸入要求網址之後nodejs伺服器端回應資料為

Server running at http1270011337

send rsquo1rsquo test rsquo1rsquo

46 本章結語

前面所解說的部份一大部分主要是處理 http伺服器基本問題雖然在某些部分有牽扯到 http 伺服器基本運作原理主要還是希望可以藉由這些基本範例練習 nodejs練習回應函式與語法串接的特點習慣編寫javascript風格程式當然直接這樣開發 nodejs是非常辛苦的接下來在模組實戰開發的部份將會介紹特定的模組一步一步帶領各位從無到有進行

nodejs應用程式開發

40 4 Nodejs基礎

5

NPM套件管理工具

npm全名為 Node Package Manager是 Nodejs的套件(package)管理工具類似 Perl的 ppm或 PHP的 PEAR等安裝 npm後使用npm install

module name指令即可安裝新套件維護管理套件的工作會更加輕鬆

npm可以讓Nodejs的開發者直接利用擴充線上的套件庫(packagesregistry)加速軟體專案的開發npm提供很友善的搜尋功能可以快速找到安裝需要的套件當這些套件發行新版本時npm也可以協助開發者自動更新這些套件

npm不僅可用於安裝新的套件它也支援搜尋列出已安裝模組及更新的功能

51 安裝 NPM

Nodejs在 063版本開始內建 npm讀者安裝的版本若是此版本或更新的版本就可以略過以下安裝說明

若要檢查 npm是否正確安裝可以使用以下的指令

npm -v

41

Nodejs中文電子書

執行結果說明

若 npm正確安裝執行 npm -v將會看到類似 110-2的版本訊息

若讀者安裝的 Nodejs版本比較舊或是有興趣嘗試自己動手安裝 npm工具則可以參考以下的說明

安裝於Windows系統

Nodejs for Windows 於 062 版開始內建 npm使用 nodejsorg 官方提供的安裝程式不需要進一步的設定就可以立即使用 npm指令對於Windows的開發者來說大幅降低環境設定的問題與門檻

除了使用 Nodejs內建的 npm讀者也可以從 npm官方提供的以下網址

httpnpmjsorgdist

這是由 npm提供的 Fancy Windows Install版本請下載壓縮檔(例如npm-110-3zip)並將壓縮檔內容解壓縮至 Nodejs的安裝路徑(例如CProgram Filesnodejs)

解壓縮後在 Nodejs的安裝路徑下應該有以下的檔案及資料夾

bull npmcmd(檔案)

bull node modules(資料夾)

安裝於 Linux系統

Ubuntu Linux的使用者可以加入 NPM Unoffcial PPA這個 repository即可使用 apt-get完成 npm安裝

42 5 NPM套件管理工具

Nodejs中文電子書

Ubuntu Linux使用 apt-get安裝 npm

sudo apt-get install python-software-properties

sudo add-apt-repository ppagias-kay-leenpm

sudo apt-get update

sudo apt-get install npm

npm官方提供的安裝程式 installsh可以適用於大多數的 Linux系統使用這個安裝程式請先確認

1 系統已安裝 curl工具(請使用 curl --version查看版本訊息)

2 已安裝 Nodejs並且 PATH正確設置

3 Nodejs的版本必須大於 04x

以下為 npm提供的安裝指令

curl httpnpmjsorginstallsh | sh

安裝成功會看到如下訊息

installsh安裝成功的訊息

npm10105 homeUSERNAMElocalnodelibnode_modulesnpm

It worked

安裝於Mac OS X

建議採用與 Nodejs相同的方式進行 npm的安裝例如使用MacPorts安裝 Nodejs就同樣使用MacPorts安裝 npm這樣對日後的維護才會更方便容易

使用 MacPorts安裝 npm是本書比較建議的方式它可以讓 npm的安裝移除及更新工作自動化將會幫助開發者節省寶貴時間

51 安裝 NPM 43

Nodejs中文電子書

安裝MacPorts的提示

在MacPorts網站可以取得 OS X系統版本對應的安裝程式(例如 106或 107)httpwwwmacportsorg安裝過程會詢問系統管理者密碼使用預設的選項完成安裝即可安

裝MacPorts之後在終端機執行 port -v將會看到MacPorts的版本訊息

安裝 npm之前先更新MacPorts的套件清單以確保安裝的 npm是最新版本

sudo port -d selfupdate

接著安裝 npm

sudo port install npm

若讀者的 Nodejs並非使用MacPorts安裝則不建議使用MacPorts安裝npm因為 MacPorts會自動檢查並安裝相依套件而 npm相依 nodejs所以MacPorts也會一併將 nodejs套件安裝造成先前讀者使用其它方式安裝的 nodejs被覆蓋

讀者可以先使用 MacPorts安裝 curl(sudo port install curl)

再參考 Linux的 installsh安裝方式即可使用 npm官方提供的安裝程式

NPM安裝後測試

npm是指令列工具(command-line tool)使用時請先打開系統的文字終端機工具

測試 npm安裝與設定是否正確請輸入指令如下

npm -v

或是

npm --version

如果 npm已經正確安裝設定就會顯示版本訊息

44 5 NPM套件管理工具

Nodejs中文電子書

執行結果(範例)

110-2

52 使用 NPM安裝套件

npm目前擁有超過 6000種套件(packages)可以在 npm registry使用關鍵字搜尋套件

httpsearchnpmjsorg

舉例來說在關鍵字欄位輸入「coffee-script」下方的清單就會自動列出包含 coffee-script關鍵字的套件

接著我們回到終端機模式的操作npm的指令工具本身就可以完成套

件搜尋的任務

例如以下的指令同樣可以找出 coffee-script相關套件

npm search coffee-script

以下是搜尋結果的參考畫面

52 使用 NPM安裝套件 45

Nodejs中文電子書

找到需要的套件後(例如 express)即可使用以下指令安裝

npm install coffee-script

值得注意的一點是使用 npm install會將指定的套件安裝在工

作目錄(Working Directory)的 node modules資料夾下

以Windows為例如果執行 npm install的目錄位於

Cproject1

那麼 npm將會自動建立一個 node modules的子目錄(如果不存在)

Cproject1node modules

並且將下載的套件放置於這個子目錄例如

Cproject1node modulescoffee-script

這個設計讓專案可以個別管理相依的套件並且可以在專案佈署或發

行時將這些套件(位於 node modules)一併打包方便其它專案的使用者不必再重新下載套件

這個 npm install的預設安裝模式為 local(本地)只會變更當前專案的資料夾不會影響系統

另一種安裝模式稱為 global(全域)這種模式會將套件安裝到系統資

料夾也就是 npm安裝路徑的 node modules資料夾例如

CProgram Filesnodejsnode modules

46 5 NPM套件管理工具

Nodejs中文電子書

是否要使用全域安裝可以依照套件是否提供新指令來判斷舉例來說express 套件提供 express 這個指令而 coffee-script 則提供 coffee

指令

在 local 安 裝 模 式 中這 些 指 令 的 程 式 檔 案會 被 安 裝 到node modules的 bin這個隱藏資料夾下除非將bin的路徑加入 PATH環境變數否則要執行這些指令將會相當不便

為了方便指令的執行我們可以在 npm install 加上 -g 或 --

global參數啟用 global安裝模式例如

npm install -g coffee-script

npm install -g express

使用 global安裝模式需要注意執行權限的問題若權限不足可能會出現類似以下的錯誤訊息

npm ERR Error EACCES permission denied rsquorsquo

npm ERR

npm ERR Please try running this command again as rootAdministrator

要獲得足夠得執行權限請參考以下說明

bull Windows 7 或 2008 以上在「命令提示字元」的捷徑按右鍵選擇「以系統管理員身分執行」執行 npm指令時就會具有 Administrator身分

bull Mac OS X或 Linux系統可以使用 sudo指令例如

sudo npm install -g express

bull Linux系統可以使用 root權限登入或是以「sudo su -」切換成 root身分(使用 root權限操作系統相當危險因此並不建議使用這種方式)

若加上 -g參數使用 npm install -g coffee-script完成安裝

後就可以在終端機執行 coffee指令例如

coffee -v

52 使用 NPM安裝套件 47

Nodejs中文電子書

執行結果(範例)

CoffeeScript version 120

53 套件的更新及維護

除了前一節說明的 search 及 install 用法npm 還提供其他許多指令(commands)

使用 npm help可以查詢可用的指令

npm help

執行結果(部分)

where ltcommandgt is one of

adduser apihelp author bin bugs c cache completion

config deprecate docs edit explore faq find get

help help-search home i info init install la link

list ll ln login ls outdated owner pack prefix

prune publish r rb rebuild remove restart rm root

run-script s se search set show star start stop

submodule tag test un uninstall unlink unpublish

unstar up update version view whoami

使用 npm help command可以查詢指令的詳細用法例如

npm help list

接下來本節要介紹開發過程常用的 npm指令

使用 list可以列出已安裝套件

npm list

48 5 NPM套件管理工具

Nodejs中文電子書

執行結果(範例)

-- coffee-script120

- express256

- connect185

| -- formidable108

-- mime124

-- mkdirp007

-- qs041

檢視某個套件的詳細資訊例如

npm show express

升級所有套件(如果該套件已發佈更新版本)

npm update

升級指定的套件

npm update express

移除指定的套件

npm uninstall express

54 使用 packagejson

對於正式的 Nodejs專案可以建立一個命名為 packagejson的設

定檔(純文字格式)檔案內容參考範例如下

54 使用 packagejson 49

Nodejs中文電子書

packagejson(範例)

rdquonamerdquo rdquoapplication-namerdquo

rdquoversionrdquo rdquo001rdquo

rdquoprivaterdquo true

rdquodependenciesrdquo

rdquoexpressrdquo rdquo255rdquo

rdquocoffee-scriptrdquo rdquolatestrdquo

rdquomongooserdquo rdquogt= 253rdquo

其中 name與 version依照專案的需求設置

需要注意的是 dependencies的設定它用於指定專案相依的套件名

稱及版本

bull rdquoexpressrdquo rdquo255rdquo

代表此專案相依版本 255的 express套件

bull rdquocoffee-scriptrdquo rdquolatestrdquo

使用最新版的 coffee-script套件(每次更新都會檢查新版)

bull rdquomongooserdquo rdquogt= 253rdquo

使用版本大於 253的 mongoose套件

假設某個套件的新版可能造成專案無法正常運作就必須指定套件的

版本避免專案的程式碼來不及更新以相容新版套件通常在開發初期的

專案需要盡可能維持新套件的相容性(以取得套件的更新或修正)可以

用「gt=」設定最低相容的版本或是使用「latest」設定永遠保持最新

套件

50 5 NPM套件管理工具

6

Express介紹

在前面的 nodejs基礎當中介紹許多許多開設 http的使用方法及介紹以及許多基本的 nodejs基本應用

接下來要介紹一個套件稱為 express [Express](httpexpressjscom)這個套件主要幫忙解決許多 nodejs http server所需要的基本服務讓開發 httpservice變得更為容易不需要像之前需要透過層層模組(module)才有辦法開始編寫自己的程式

這個套件是由 TJ Holowaychuk 製作而成的套件裡面包含基本的路由處理 (route)http資料處理(GETPOSTPUT)另外還與樣板套件(jshtml template engine)搭配同時也可以處理許多複雜化的問題

61 Express安裝

安裝方式十分簡單只要透過之前介紹的 NPM就可以使用簡單的指令安裝指令如下

這邊建議需要將此套件安裝成為全域模組方便日後使用

51

Nodejs中文電子書

62 Express基本操作

express的使用也十分簡單先來建立一個基本的 hello world

var app = require(rsquoexpressrsquo)createServer()

port = 1337

applisten(port)

appget(rsquorsquo function(req res)

ressend(rsquohello worldrsquo)

)

consolelog(rsquostart express servernrsquo)

可以從上面的程式碼發現基本操作與 nodejs http的建立方式沒有太大差異主要差在當我們設定路由時可以直接透過 appget方式設定回應與接受方式

63 Express路由處理

Express對於 http服務上有許多包裝讓開發者使用及設定上更為方便例如有幾個路由設定那我們就統一藉由 appget來處理

Create http server

appget(rsquorsquo function(req res)

ressend(rsquohello worldrsquo)

)

appget(rsquotestrsquo function(req res)

ressend(rsquotest renderrsquo)

)

appget(rsquouserrsquo function(req res)

ressend(rsquouser pagersquo)

)

52 6 Express介紹

Nodejs中文電子書

如上面的程式碼所表示appget可以帶入兩個參數第一個是路徑名稱設定第二個為回應函式 (call back function)回應函式裡面就如同之前的 createServer方法裡面包含 requestresponse兩個物件可供使用使用者就可以透過瀏覽器輸入不同的 url切換到不同的頁面顯示不同的結果

路由設定上也有基本的配對方式讓使用者從瀏覽器輸入的網址可以

是一個變數只要符合型態就可以有對應的頁面產出例如

Create http server

appget(rsquouseridrsquo function(req res)

ressend(rsquouser rsquo + reqparamsid)

)

appget(rsquonumberrsquo function(req res)

ressend(rsquonumber rsquo + reqparamsnumber)

)

裡 面 使 用 到number 從 網 址 輸 入 之 後 就 可 以 直 接 使 用reqparamsnumber 取得所輸入的資料變成 url 參數使用當然前面也是可以加上路徑的設定userid在瀏覽器上路徑必須符合userxxx透

過 reqparamsid就可以取到 xxx這個字串值

另外express參數處理也提供了路由參數配對處理也可以透過正規表示法作為參數設定

var app = require(rsquoexpressrsquo)createServer()

port = 1337

applisten(port)

appget(^ip((d23)((d23))((d23))((d23))) function(req res)

ressend(reqparams)

)

上面程式碼可以發現後面路由設定的型態是正規表示法裡面設定

格式為ip之後必須要加上 ip型態才會符合資料格式同時取得 ip資料已經由正規表示法將資料做分群因此可以取得 ip的四個數字

此程式執行之後可以透過瀏覽器測試輸入網址為 localhost3000ip25525510010可以從頁面獲得資料

63 Express路由處理 53

Nodejs中文電子書

此章節全部範例程式碼如下

overview

author Caesar Chi

blog clonnblogspotcom

version 20120226

create server

var app = require(rsquoexpressrsquo)createServer()

port = 1337

applisten(port)

normal style

appget(rsquorsquo function(req res)

ressend(rsquohello worldrsquo)

)

appget(rsquotestrsquo function(req res)

ressend(rsquotest renderrsquo)

)

parameter style

appget(rsquouseridrsquo function(req res)

ressend(rsquouser rsquo + reqparamsid)

)

appget(rsquonumberrsquo function(req res)

ressend(rsquonumber rsquo + reqparamsnumber)

)

REGX style

appget(^ip((d23)((d23))((d23))) function(req res)

ressend(reqparams)

)

appget(rsquorsquo function(req res)

ressend(rsquoPage not foundrsquo 404)

54 6 Express介紹

Nodejs中文電子書

)

consolelog(rsquostart express servernrsquo)

64 Express middleware

Express 裡面有一個十分好用的應用概念稱為 middleware可以透過middleware做出複雜的效果同時上面也有介紹 next方法參數傳遞就是靠 middleware的概念來傳遞參數讓開發者可以明確的控制程式邏輯

create http server

appuse(expressbodyParser())

appuse(expressmethodOverride())

appuse(expresssession())

上面都是一種 middleware的使用方式透過 appuse方式裡面載入函式執行方法回應函式會包含三個基本參數responserequestnext其中next表示下一個 middleware執行函式同時會自動將預設三個參數繼續帶往下個函式執行底下有個實驗

上面的片段程式執行後開啟瀏覽器連結上 localhost1337會發現伺服器回應結果順序如下

first middle ware

second middle ware

execute middle ware

end middleware function

從上面的結果可以得知剛才設定的 middleware都生效了在 appuse設定的 middleware是所有 url皆會執行方法如果有指定特定方法就可以使用 appget的 middleware設定在 appget函式的第二個參數就可以帶入函式或者是匿名函式只要函式裡面最後會接受 request response next這三個參數同時也有正確指定 next函式的執行時機最後都會執行到最後一個方法當然開發者也可以評估程式邏輯要執行到哪一個階段讓邏輯

可以更為分明

64 Express middleware 55

Nodejs中文電子書

65 Express路由應用

在實際開發上可能會遇到需要使用參數等方式混和變數一起使用

express裡面提供了一個很棒的處理方法 appall這個方式可以先採用基本路由配對再將設定為每個不同的處理方式開發者可以透過這個方式簡

化自己的程式邏輯

overview

author

version 20120226

create server

var app = require(rsquoexpressrsquo)createServer()

port = 1337

users = [

name rsquoClonnrsquo

name rsquoChirsquo

]

applisten(port)

appall(rsquouseridoprsquo function(req res next)

requser = users[reqparamsid]

if (requser)

next()

else

next(new Error(rsquocannot find user rsquo + reqparamsid))

)

appget(rsquouseridrsquo function(req res)

ressend(rsquoviewing rsquo + requsername)

)

appget(rsquouserideditrsquo function(req res)

ressend(rsquoediting rsquo + requsername)

)

56 6 Express介紹

Nodejs中文電子書

appget(rsquouseriddeletersquo function(req res)

ressend(rsquodeleting rsquo + requsername)

)

appget(rsquorsquo function(req res)

ressend(rsquoPage not foundrsquo 404)

)

consolelog(rsquostart express servernrsquo)

內部宣告一組預設的使用者分別給予名稱設定藉由 appall這個方法可以先將路由雛形建立再接下來設定 appget的路徑格式只要符合格式就會分配進入對應的方法中像上面的程式當中如果使用者輸入路徑為user0除了執行 appall程式之後執行 next方法就會對應到路徑設定為userid的這個方法當中如果使用者輸入路徑為user0edit就會執行到useridedit的對應方法

66 Express GET應用範例

我們準備一個使用 GET方法傳送資料的表單

ltDOCTYPE htmlgt

lthtmlgt

ltheadgt

ltmeta http-equiv=rdquoContent-Typerdquo content=rdquotexthtml charset=utf8rdquogt

lttitlegtNodejs 菜鳥筆記 (1)lttitlegt

ltlink rel=rdquostylesheetrdquo href=rdquocssstylecssrdquo type=rdquotextcssrdquo media=rdquoallrdquo gt

ltheadgt

ltbodygt

lth1gtNodejs 菜鳥筆記-註冊lth1gt

ltform id=rdquosignuprdquo method=rdquoGETrdquo action=rdquohttplocalhost3000Signuprdquogt

ltlabelgt 使用者名稱ltlabelgtltinput type=rdquotextrdquo id=rdquousernamerdquo name=rdquousernamerdquo gtltbrgt

ltlabelgt 電子郵件ltlabelgtltinput type=rdquotextrdquo id=rdquoemailrdquo name=rdquoemailrdquo gtltbrgt

ltinput type=rdquosubmitrdquo value=rdquo 註冊我的帳號rdquo gtltbrgt

ltformgt

ltbodygt

lthtmlgt

66 Express GET應用範例 57

Nodejs中文電子書

這個表單沒有什麼特別的地方我們只需要看第 9 行form 使用的method是 GET然後 action是rdquohttplocalhost3000Signupldquo等一下我們要來撰寫Signup這個 URL Path的處理程式

處理 Signup行為

我們知道所謂的 GET方法會透過 URL來把表單的值給帶過去以上面的表單來說到時候 URL會以這樣的形式傳遞

httplocalhost3000Signupusername=xxxampemail=xxx

所以要能處理這樣的資料必須有以下功能

bull 解析 URL

bull 辨別動作是 Signup

bull 解析出 username和 email

一旦能取得 username和 email的值程式就能加以應用了

處理 Signup的程式碼雛形

load module

var url = require(rsquourlrsquo)

urlData = urlparse(requrltrue)

action = urlDatapathname

reswriteHead(200 rdquoContent-Typerdquordquotexthtml charset=utf-8rdquo)

if (action === rdquoSignuprdquo)

user = urlDataquery

resend(rdquolth1gtrdquo + userusername + rdquo 歡迎您的加入 lth1gtltpgt 我們已經將會員啟用信寄至rdquo + useremail + rdquoltpgtrdquo)

首先需要加載 url module它是用來協助我們解析 URL的模組接著使用 urlparse方法第一個傳入 url字串變數也就是 requrl另外第二個參數的用意是設為 ture則引進 querystring模組來協助處理預設是 false它影響到的是 urlDataquery設為 true會傳回物件不然就只是一般的字串urlparse會將字串內容整理成一個物件我們把它指定給 urlData

action變數作為記錄 pathname這是我們稍後要來判斷目前網頁的動作是什麼接著先將 html表頭資訊 (Header)準備好再來判斷路徑邏輯如

58 6 Express介紹

Nodejs中文電子書

果是 Signup這個動作就把 urlDataquery裡的資料指定給 user然後輸出userusername和 useremail把使用者從表單註冊的資料顯示於頁面中

最後進行程式測試啟動 nodejs主程式之後開啟瀏覽器就會看到表單填寫完畢按下送出就可以看到結果了

完整 nodejs程式碼如下

var http = require(rsquohttprsquo)

url = require(rsquourlrsquo)

fs = require(rdquofsrdquo)

server

server = httpcreateServer(function (reqres)

var urlData

encode = rdquoutf8rdquo

filePath = rdquoviewexpress_get_example_formhtmlrdquo

action

urlData = urlparse(requrltrue)

action = urlDatapathname

reswriteHead(200 rdquoContent-Typerdquordquotexthtml charset=utf-8rdquo)

if (action === rdquoSignuprdquo)

user = urlDataquery

resend(rdquolth1gtrdquo + userusername + rdquo 歡迎您的加入 lth1gtltpgt 我們已經將會員啟用信寄至rdquo + useremail + rdquoltpgtrdquo)

else

fsreadFile(filePath encode function(err file)

reswrite(file)

resend()

)

)

serverlisten(3000)

consolelog(rsquoServer 跑起來了現在時間是rsquo + new Date())

66 Express GET應用範例 59

Nodejs中文電子書

67 Express POST應用範例

一開始準備基本的 html表單傳送內容以 POST方式form的 action屬性設定為 POST其餘 html內容與前一個範例應用相同

ltDOCTYPE htmlgt

lthtmlgt

ltheadgt

ltmeta http-equiv=rdquoContent-Typerdquo content=rdquotexthtml charset=utf8rdquogt

lttitlegtNodejs 菜鳥筆記 (1)lttitlegt

ltlink rel=rdquostylesheetrdquo href=rdquocssstylecssrdquo type=rdquotextcssrdquo media=rdquoallrdquo gt

ltheadgt

ltbodygt

lth1gtNodejs 菜鳥筆記-註冊lth1gt

ltform id=rdquosignuprdquo method=rdquoPOSTrdquo action=rdquohttplocalhost3000Signuprdquogt

ltlabelgt 使用者名稱ltlabelgtltinput type=rdquotextrdquo id=rdquousernamerdquo name=rdquousernamerdquo gtltbrgt

ltlabelgt 電子郵件ltlabelgtltinput type=rdquotextrdquo id=rdquoemailrdquo name=rdquoemailrdquo gtltbrgt

ltinput type=rdquosubmitrdquo value=rdquo 註冊我的帳號rdquo gtltbrgt

ltformgt

ltbodygt

lthtmlgt

nodejs的程式處理邏輯與前面 GET範例類似部分程式碼如下

qs = require(rsquoquerystringrsquo)

if (action === rdquoSignuprdquo)

formData = rsquorsquo

reqon(rdquodatardquo function (data)

formData += data

)

reqon(rdquoendrdquo function ()

user = qsparse(formData)

resend(rdquolth1gtrdquo + userusername + rdquo 歡迎您的加入 lth1gtltpgt 我們已經將會員啟用信寄至rdquo + useremail + rdquoltpgtrdquo)

)

60 6 Express介紹

Nodejs中文電子書

主要加入了rsquoquerystringrsquo這個moduel方便我們等一下解析由表單 POST回來的資料另外加入一個 formData的變數用來搜集待等一下表單回傳的資料前面的 GET範例我們只從 req拿出 url的資料這次要在利用req身上的事件處理

JavaScript 在訂閱事件時使用 addEventListener而 nodejs 使用的則是on這邊加上了監聽 data的事件會在瀏覽器傳送資料到Web Server時被執行參數是它所接收到的資料型態是字串

接著再增加 end的事件當瀏覽器的請求事件結束時它就會動作

由於瀏覽器使用 POST在上傳資料時會將資料一塊塊地上傳因為我們在監聽 data事件時透過 formData變數將它累加起來lt不過由於我們

上傳的資料很少一次就結束不過如果日後需要傳的是資料比較大的檔

案這個累加動作就很重要

當資料傳完就進到 end 事件中會用到 qsparse 來解析 formDataformData的內容是字串內容是

username=wordsmithampemail=wordsmith40somewhere

而 qsparse可以幫我們把這個 querystring轉成物件的格式也就是

username=wordsmithampemail=wordsmith40somewhere

一旦轉成物件並指定給 user之後其他的事情就和 GET方法時操作的一樣寫 response的表頭將內容回傳並將 userusername和 useremail代入到內容中

修改完成後接著執行 nodejs程式啟動 web server開啟瀏覽器進入表單測試看看POST的方式能否順利運作

完整程式碼如下

var http = require(rsquohttprsquo)

url = require(rsquourlrsquo)

fs = require(rdquofsrdquo)

qs = require(rsquoquerystringrsquo)

server

server = httpcreateServer(function (reqres)

var urlData

67 Express POST應用範例 61

Nodejs中文電子書

encode = rdquoutf8rdquo

filePath = rdquoviewexpress_post_example_formhtmlrdquo

formData

action

urlData = urlparse(requrltrue)

action = urlDatapathname

reswriteHead(200 rdquoContent-Typerdquordquotexthtml charset=utf-8rdquo)

if (action === rdquoSignuprdquo)

formData = rsquorsquo

reqon(rdquodatardquo function (data)

formData += data

)

reqon(rdquoendrdquo function ()

user = qsparse(formData)

resend(rdquolth1gtrdquo + userusername + rdquo 歡迎您的加入 lth1gtltpgt 我們已經將會員啟用信寄至rdquo + useremail + rdquoltpgtrdquo)

)

else

fsreadFile(filePath encode function(err file)

reswrite(file)

resend()

)

)

serverlisten(3000)

consolelog(rsquoServer 跑起來了現在時間是rsquo + new Date())

62 6 Express介紹

Nodejs中文電子書

68 Express AJAX應用範例

在 Nodejs要使用 Ajax傳送資料並且與之互動在接受資料的部份沒有太大的差別client端不是用 GET就是用 POST來傳資料重點在處理完後用 JSON格式回傳當然 Ajax不見得只傳 JSON格式有時是回傳一段 HTML碼不過後者對伺服器來說基本上就和前兩篇沒有差別了所以我們還是以回傳 JSON做為這一回的主題

這一回其實大多數的工作都會落在前端 Ajax上面前端要負責發送與接收資料並在接收資料後撤掉原先發送資料的表單並將取得的資料

改成 HTML格式之後放上頁面

首先先準備 HTML靜態頁面資料

ltDOCTYPE htmlgt

lthtmlgt

ltheadgt

lttitlegtNodejs 菜鳥筆記 (3)lttitlegt

ltlink rel=rdquostylesheetrdquo href=rdquocssstylecssrdquo type=rdquotextcssrdquo media=rdquoallrdquo gt

ltscript src=rdquohttpsajaxgoogleapiscomajaxlibsjquery171jqueryminjsrdquogtltscriptgt

ltheadgt

ltbodygt

lth1gtNodejs 菜鳥筆記-註冊 (用 Ajax)lth1gt

ltform id=rdquosignuprdquo action=rdquoSignuprdquo method=rdquoPOSTrdquogt

ltlabelgt 使用者名稱ltlabelgtltinput type=rdquotextrdquo id=rdquousernamerdquo name=rdquousernamerdquo gtltbrgt

ltlabelgt 電子郵件ltlabelgtltinput type=rdquotextrdquo id=rdquoemailrdquo name=rdquoemailrdquo gtltbrgt

ltinput type=rdquosubmitrdquo value=rdquo 註冊我的帳號rdquo gtltbrgt

ltformgt

ltdiv id=rdquoresultrdquogt

ltdivgt

ltscript type=rdquotextjavascriptrdquogt

$(function()$(rsquosignup input[type=rdquosubmitrdquo]rsquo)click(function(e)

var user =

userusername = $(rdquousernamerdquo)val()useremail = $(rdquoemailrdquo)val()

$post(rdquoSignuprdquouserfunction(data)greet(data)

)

68 Express AJAX應用範例 63

Nodejs中文電子書

epreventDefault()

)

var greet = function(msg)

var resultNode = $(rsquoresultrsquo)greeting = $(rsquolth2gtHirsquo+msgusername+rsquo 你的會員 id 是rsquo+ msgid +rsquo 我們會將會員啟動信寄至rsquo+msgemail+rsquolth2gtrsquo)

$(rsquosignuprsquo)hide()resultNodehtml(rdquordquo)

resultNodeappend(greeting)

)

ltscriptgt

ltbodygt

lthtmlgt

HTML頁面上準備了一個表單用來傳送註冊資料接著直接引用了Google CDN來載入 jQuery用來幫我們處理 Ajax的工作這次要傳送和接收的工作很大的變動都在 HTML頁面上的 JavaScript當中我們要做的事有 (相關 jQuery處理這邊不多做贅述指提起主要功能解說)

bull 用 jQUery取得 submit按鈕綁定它的 click動作

bull 取得表單 username和 email的值存放在 user這個物件中

bull 用 jQuery的 $post方法將 user的資料傳到 Server

bull 一旦成功取得資料後透過 greet這個 function組成回報給 user的訊息

bull 清空原本給使用者填資料的表單

bull 將 Server回傳的 usernameemail和 id這 3個資料組成回應的訊息

bull 將訊息放到原本表單的位置

經過以上的處理後一個 Ajax的表單的基本功能已經完成

接著進行 nodejs主要程式的編輯部分程式碼如下

var fs = require(rdquofsrdquo)

64 6 Express介紹

Nodejs中文電子書

qs = require(rsquoquerystringrsquo)

if (action === rdquoSignuprdquo)

formData = rsquorsquo

reqon(rdquodatardquo function (data)

formData += data

)

reqon(rdquoendrdquo function ()

var msg

user = qsparse(formData)

userid = rdquo123456rdquo

msg = JSONstringify(user)

reswriteHead(200 rdquoContent-Typerdquordquoapplicationjson charset=utf-8rdquordquoContent-Lengthrdquomsglength)

resend(msg)

)

這裡的程式和前面 POST範例基本上大同小異差別在

bull 幫 user的資料加上 id隨意存放一些文字進去讓 Server回傳的資料多於 Client端傳上來的不然會覺得 Server都沒做事

bull 增加了 msg 這個變數存放將 user 物件 JSON 文字化的結果JSONstringify這個轉換函式是 V8引擎所提供的如果你好奇的話

bull 大重點來了我們要告訴 Client端這次回傳的資料格式是 JSON所在 Content-type和 Content-Length要提供給 Client

Server很輕鬆就完成任務了最後進行程式測試啟動 nodejs主程式之後開啟瀏覽器就會看到表單填寫完畢按下送出就可以看到結果了

最後 nodejs本篇範例程式碼如下

var http = require(rsquohttprsquo)

url = require(rsquourlrsquo)

fs = require(rdquofsrdquo)

qs = require(rsquoquerystringrsquo)

server

68 Express AJAX應用範例 65

Nodejs中文電子書

server = httpcreateServer(function (reqres)

var urlData

encode = rdquoutf8rdquo

filePath = rdquoviewexpress_ajax_example_formhtmlrdquo

formData

action

urlData = urlparse(requrltrue)

action = urlDatapathname

if (action === rdquoSignuprdquo)

formData = rsquorsquo

reqon(rdquodatardquo function (data)

formData += data

)

reqon(rdquoendrdquo function ()

var msg

user = qsparse(formData)

userid = rdquo123456rdquo

msg = JSONstringify(user)

reswriteHead(200 rdquoContent-TyperdquordquoapplicationjsonrdquordquoContent-Lengthrdquomsglength)

resend(msg)

)

else

fsreadFile(filePath encode function(err file)

reswriteHead(200 rdquoContent-Typerdquordquotexthtml charset=utf-8rdquo)

reswrite(file)

resend()

)

)

serverlisten(3000)

66 6 Express介紹

Nodejs中文電子書

consolelog(rsquoServer 跑起來了現在時間是rsquo + new Date())

69 原始資料提供

bull [NodeJS初學者筆記 (1)-用 GET傳送資料] (httpithelpithomecomtwquestion10087402)

bull [NodeJS初學者筆記 (2)-用 POST傳送資料] (httpithelpithomecomtwquestion10087489)

bull [NodeJS 初學者筆記 (3)-用 Ajax 傳送資料] (httpithelpithomecomtwquestion10087627)

69 原始資料提供 67

Nodejs中文電子書

68 6 Express介紹

7

CoffeeScript

bull Spine

bull Hem

bull Spineapp

Letrsquos Have a Cup of CoffeeScript

bull es5-shim ECMAScript 5 compatibility shims for legacy JavaScript engines

ESnext

node-iform - A middleware for node-validator httpsgithubcomguileennode-iform

69

Nodejs中文電子書

70 7 CoffeeScript

8

製作一個 Hubot的 Plurk Adapter

81 應用事項提醒

此應用範例以CoffeeScript編寫主要程式架構採用Github釋放的Hubot專案以下有幾個主要注意事項請讀者注意

bull Hubot 一開始的架構除了內建的兩個 Adapter 之外其餘都要以Nodejs的Module方式才能運作

bull Hubot的 binhubot裡面寫著 npm install所以不管你怎麼改原始碼也不能改變第一點的狀況

其實上述都在討論同一件事情NodeJS的模組而且模組不能設定相對路徑之類的來安裝一定要包裝成 npm相符和格式才有辦法正確執行

82 建立 Adapter

首先需要準備兩個檔案

bull packagejson

bull plurkcoffee

71

Nodejs中文電子書

有這兩個就足以變成 NodeJS的模組了在開始 Coding之前先來設定一下 packagejson相依性

rdquonamerdquo rdquohubot-plurkrdquo

rdquoversionrdquo rdquo011rdquo

rdquomainrdquo rdquoplurkrdquo

rdquodependenciesrdquo

rdquohubotrdquo rdquogt=205rdquo

rdquooauthrdquo rdquordquo

rdquocronrdquordquordquo

這邊會使用到NodeJS的 cron模組(Module)而登入 Plurk需要OAuth標準授權才行所以也需要加載 OAuth模組

注意Hubot在讀取非內建模組時會自動在前面加上 hubot-的前置

83 建立 Robot跟 API

本篇應用基本上程式碼大部分參考 Hubot - Twitter Adapter來製作主要差異只有在使用 OAuth這個模組Twitter有 Streaming可用而 Plurk則得用 Comet方式來達到即時讀取

plurkcoffee程式碼

Robot = require(rdquohubotrdquo)robot()

Adapter = require(rdquohubtordquo)adapter()

EventEmitter = require(rdquoeventsrdquo)EventEmitter

oauth = require(rdquooauthrdquo)

cronJob = require(rdquocronrdquo)CronJob

class Plurk exntends Adapter

class PlurkStreaming exnteds EventEmitter

72 8 製作一個 Hubot的 Plurk Adapter

Nodejs中文電子書

先弄個基本架構主要 Class皆參考 Twitter Adapter命名方式接著再將 Plurk這個 Class增加幾個Method基本上只要有 run send reply就夠了而 run用來做初始化的部分

class Plurk entends Adapter

send (plurk id strings⋯) -gt

reply (plurk id strings⋯) -gt

run -gt

看起來有點東西接著來處理主要的 Plurk API結合部分

class PlurkStreaming extends EventEmitter

consuctor (options) -gt

plurk (callback) -gt

觀察河道

getChannel -gt

取得 Comet 網址

reply (plurk id message) -gt

回噗

acceptFriends -gt

接受好友

get (path callback) -gt

GET 請求

post (path body callback)-gt

POST 請求(其實是裝飾)

request (method path body callback)-gt

主要的 OAuth 請求

comet (server callback)-gt

噗浪的 Comet 傳回是 JavaScript Callback 要另外處理後才會變成 JSON

然後把注意力集中到 constructor上先把建構子弄好

constructor (options) -gt

super()

if optionskey and optionssecret and optionstoken and optionstoken secret

key = optionskey

secret = optionssecret

token = optionstoken

token secret = optionstoken secret

83 建立 Robot跟 API 73

Nodejs中文電子書

建立 OAuth 連接

consumer = new oauthOAuth(

rdquohttpwwwplurkcomOAuthrequest tokenrdquo

rdquohttpwwwplurkcomOAuthaccess tokenrdquo

key

secret

rdquo10rdquo

rdquohttpwwwplurkcomOAuthauthorizerdquo

rdquoHMAC-SHA1rdquo

)

domain = rdquowwwplurkcomrdquo

初始化取得 Comet 網址

do getChannel

else

throw new Error(rdquo 參數不足需要 Key Secret Token Token Secretrdquo)

接著來處理 request這個 method

request (method path body callback) -gt

記錄一下這次的 Request

consolelog(rdquohttpdomainpathrdquo)

Callback 這邊先不丟進去要用另一種方式處理

request = consumerget(rdquohttpdomainpathrdquo token token secret null)

requeston rdquoresponserdquo (res) -gt

reson rdquodatardquo (chunk) -gt

parseResponse(chunk+rsquorsquo callback)

reson rdquoendrdquo (data) -gt

consolelog rdquoEnd Request pathrdquo

reson rdquoerrorrdquo (data) -gt

consolelog rdquoError rdquo + data

requestend()

處理資料

parseResponse = (data callback) -gt

if datalength gt 0

用 TryCatch 避免處理 JSON 出錯導致整個中斷

try

callback null JSONparse(data)

catch err

74 8 製作一個 Hubot的 Plurk Adapter

Nodejs中文電子書

consolelog(rdquoError Parse JSONrdquo + data err)

繼續執行

callback null data ||

大致上就是這樣上面程式的架構已經將整個 Hubot Plurk Adapter完成因為在測試時竟然因為噗浪 Lag而沒讀到完整的 Comet資料然後造成程式異常為了避免這個問題發生因此需要加上為了完美呈現需要再

加上 Comet的處理所以要使用到 EventEmitter的功能

comet (server callback) -gt

在 Callback 裡面會找不到自身所以設定區域變數

self =

記錄一下這次的 Request

consolelog(rdquo[Comet] serverrdquo)

Callback 這邊先不丟進去要用另一種方式處理

request = consumerget(rdquohttpdomainpathrdquo token token secret null)

requeston rdquoresponserdquo (res) -gt

reson rdquodatardquo (chunk) -gt

parseResponse(chunk+rsquorsquo callback)

reson rdquoendrdquo (data) -gt

consolelog rdquoEnd Request pathrdquo

請求結束發出事件通知可以進行下一次請求

selfemit rdquonextPlurkrdquo

reson rdquoerrorrdquo (data) -gt

consolelog rdquoError rdquo + data

requestend()

處理資料

parseResponse = (data callback) -gt

if datalength gt 0

用 trycatch 避免失敗中斷

try

去掉 JavaScript 的 Callback

data = datamatch(CometChannelscriptCallback((+))s)

jsonData = rdquordquo

if data

83 建立 Robot跟 API 75

Nodejs中文電子書

jsonData = JSONparse(data[1])

else

如果沒有任何 Match 嘗試直接 parse

jsonData = JSONparse(data)

catch err

consolelog(rdquo[Comet] Errorrdquo data err)

用 TryCatch 避免處理 JSON 出錯導致整個中斷

try

只傳入 json 的 data 部分

callback null jsonDatadata

catch err

consolelog(rdquo[Comet]Error Parse JSONrdquo + data err)

繼續執行

callback null data ||

後面的 get跟 post就簡單多了

get (path callback) -gt

request(rdquoGETrdquo path null callback)

post (path body callback) -gt

request(rdquoPOSTrdquo path body callback)

接著處理取的 Comet網址的 getChannel

getChannel -gt

self =

get rdquoAPPRealtimegetUserChannelrdquo (error data) -gt

if error

檢查是否有 comet server

if datacomet server

selfchannel = datacomet server

如果沒有 Channel Ready 就嘗試連接會失敗

selfemit(rsquochannel readyrsquo)

那麼先來處理 Plurk Adaper好處理的部份

send (plruk id strings⋯)-gt

跟 Reply 一樣直接交給 reply 做

reply plurk id strings⋯

76 8 製作一個 Hubot的 Plurk Adapter

Nodejs中文電子書

reply (plurk id strings⋯) -gt

stringsforEach (message) =gt

botreply(plruk id message)

接著把 run處理好就可以上線運作摟

run -gt

self =

options =

key processenvHUBOT PLURK KEY

secret processenvHUBOT PLURK SECRET

token processenvHUBOT PLURK TOKEN

token secret processenvHUBOT PLURK TOKEN SECRET

創建剛剛的 API

bot = new PlurkStreaming(options)

依照 Twitter 的 new RobotTextMessage 會沒有反應所以參考 hubot-minecraft 的方式

r = robotconstructor

處理噗浪河道訊息

doPlurk = (data)-gt

檢查是否為回噗

if dataresponse

datacontent raw = dataresponsecontent raw

datauser id = dataresponseuser id

確定有噗浪 ID 跟訊息

if dataplurk id and datacontent raw

selfreceive new rTextMessage(dataplurk id datacontent raw)

取得 Comet Server 完成開始第一次 Comet 連接

boton rdquochannel readyrdquo () -gt

botplurk selfdoPlurk

上一次 Comet 完成繼續 Polling

boton rdquonextPlurkrdquo ()-gt

botplurk selfdoPlurk

定時接受好友邀請

do botacceptFriends

bot = bot

83 建立 Robot跟 API 77

Nodejs中文電子書

終於完成 AdapterHubot專案裡面的 scripts資料夾內是互動部分不需要像 Adapter如此大費周章處理只新增檔案並且設計好對白之後就會回噗了我開發用的機器人在此大家可以去跟他玩玩[httpplurkcomelct9620 bot](httpplurkcomelct9620 bot)

84 原始資料提供

bull [製 作 一 個 Hubot 的 噗 浪 Adapter](http revo-skillfrosttw blog20120318create-a-hubot-plurk-adapter)

78 8 製作一個 Hubot的 Plurk Adapter

9

Nodejs好用工具介紹

91 logio

Real-time log monitoring in your browser

httplogioorg

nodejs (gt=0412 lt=0611)

npm config set unsafe-perm true

npm install -g --prefix=usrlocal logio

logio server start

httplocalhost8998

79

Nodejs中文電子書

80 9 Nodejs好用工具介紹

10

精選文章

101 Express

bull 使用 NodeJS + Express從 GETPOST Request取值 [邀稿中]

bull link2 [邀稿中]

bull link3 [邀稿中]

81

Nodejs中文電子書

82 10 精選文章

11

參考資源

111 Nodejs書籍

bull Programming Nodejs April 2012 (est) Travis Swicegood Pragmatic Bookshelf

bull e Little Book on CoffeeScript January 2012 Alex MacCaw OrsquoReilly Media

bull Node for Front-End Developers January 2012 Garann Means OrsquoReilly Media

bull Building Hypermedia APIs with HTML5 and Node November 2011 MikeAmundsen OrsquoReilly Media

bull Node Web Development August 2011 David Herron PacktPub

bull CoffeeScript Accelerated JavaScript Development July 2011 Trevor BurnhamPragmatic Bookshelf

bull Getting Started with GEO CouchDB and Nodejs July 2011 Mick omp-son OrsquoReilly Media

bull What Is Node July 2011 Brett McLaughlin OrsquoReilly Media

bull Node Up and Running May 2011 Tom Hughes-Croucher Mike WilsonOrsquoReilly Media

83

Nodejs中文電子書

bull Hands-on Nodejs e Nodejs introduction and API reference May 2011 Pe-dro Teixeira LeanPub

bull e Node Beginner Book Manuel Kiessling

bull Mastering Nodejs visionmedia

112 Nodejs影音教學

bull e Node Sessions e Best of OSCON 2011 August 2011 OrsquoReilly Media(Video)

bull Tom Hughes-Croucher on Node March 2011 OrsquoReilly Media (Video)

113 Nodejs教學網站

bull Node Tuts

114 Nodejs課程

bull Introduction To Nodejs Van Nguyen CodeLesson

84 11 參考資源

Page 18: Nodejs Wiki

Nodejs中文電子書

的事件或是非同步執行的函數都是在同一個執行緒中利用 event loop的方式在執行至於一些比較慢的動作例如 IO網頁 render re ow等實際動作會在其他執行緒跑等到有結果時才利用事件來觸發處理函數來處理

這樣的模型有幾個好處沒有執行緒的額外成本所以反應速度很快不會

有任何程式同時用到同一個變數不必考慮 lock也不會產生 dead lock所以程式撰寫很簡單但是也有一些潛在問題任一個函數執行時間較長都

會讓其他函數更慢執行(因為一個跑完才會跑另一個)在多核心硬體普遍

的現在無法用單一的應用程式 instance發揮所有的硬體能力用 NodeJS撰寫伺服器程式碰到的也是一樣的狀況要讓系統發揮 event loop的效能就要盡量利用事件的方式來組織程式架構另外對於一些有可能較為耗

時的操作可以考慮使用 processnextTick函數來讓他以非同步的方式執行避免在同一個函數中執行太久擋住所有函數的執行

如果想要測試 event loop怎樣在「瀏覽器」中運行可以在函數中呼叫alert()這樣會讓所有 Javascript的執行停下來尤其會干擾所有使用 timer的函數執行有一個簡單的例子這是一個會依照設定的時間間隔嚴格執

行動作的動畫如果時間過了就會跳過要執行的動作點按圖片以後人

物會快速旋轉但是在旋轉執行完畢前按下「delay」按鈕讓 alert訊息等久一點接下來的動畫就完全不會出現了

22 Scope與 Closure

要快速理解 JavaScript的 Scope(變數作用範圍)原理只要記住他是Lexical Scope就差不多了簡單地說變數作用範圍是依照程式定義時(或者叫做程式文本)的上下文決定而不是執行時的上下文決定

為了維護程式執行時所依賴的變數即使執行時程式運行在原本的

scope之外他的變數作用範圍仍然維持不變這時程式依賴的自由變數(定義時不是 local的而是在上一層 scope定義的變數)一樣可以使用就好像被關閉起來所以叫做 Closure用程式看比較好懂

function outter(arg1)

arg1 及 free_variable1 對 inner 函數來說都是自由變數

var free_variable1 = 3

return function inner(arg2)

12 2 JavaScript與 NodeJS

Nodejs中文電子書

var local_variable1 =2arg2 及 local_variable1 對 inner 函數來說都是本地變數

return arg1 + arg2 + free_variable + local_variable1

var a = outter(1)變數 a就是 outter函數執行後返回的 inner函數 var b =a(4)執行 inner函數執行時上下文已經在 outter函數之外但是仍然能正常執行而且可以使用定義在 outter函數裡面的 arg1及 free variable1變數 consolelog(b)結果 10

在 Javascript中scope最主要的單位是函數(另外有 global及 eval)所以有可能製造出 closure的狀況通常在形式上都是有巢狀的函數定義而且內側的函數使用到定義在外側函數裡面的變數

Closure有可能會造成記憶體洩漏主要是因為被參考的變數無法被垃圾收集機制處理造成佔用的資源無法釋放所以使用上必須考慮清楚

不要造成意外的記憶體洩漏(在上面的例子中如果 a一直未執行使用到的記憶體就不會被釋放)

跟透過函數的參數把變數傳給函數比較起來Javascript Engine會比較難對 Closure進行最佳化如果有效能上的考量這一點也需要注意

23 Callback

要介紹 Callback之前要先提到 JavaScript的特色

JavaScript是一種函數式語言(functional language)所有 Javascript語言內的函數都是高階函數 (higher order function這是數學名詞計算機用語好像是 rst class function意指函數使用沒有任何限制與其他物件一樣)也就是說函數可以作為函數的參數傳給函數也可以當作函數的返回值這個特性讓 Javascript的函數使用上非常有彈性而且功能強大

callback在形式上其實就是把函數傳給函數然後在適當的時機呼叫傳入的函數Javascript使用的事件系統通常就是使用這種形式NodeJS中有一個物件叫做 EventEmitter這是 NodeJS事件處理的核心物件所有會使用事件處理的函數都會「繼承」這個物件(這裡說的繼承實

作上應該像是 mixin)他的使用很簡單可以使用物件on(事件名稱callback

23 Callback 13

Nodejs中文電子書

函數) 或是物件addListener(事件名稱callback 函數) 把你想要處理事件的函數傳入在物件中可以使用物件emit(事件名稱 參數) 呼叫傳入的callback函數這是 Observer Pattern的簡單實作而且跟在網頁中使用 DOM的 addEventListener使用上很類似也很容易上手不過 NodeJS是大量使用非同步方式執行的應用所以程式邏輯幾乎都是寫在 callback函數中當邏輯比較複雜時大量的 callback會讓程式看起來很複雜也比較難單元測試舉例來說

var p client = new Db(lsquointegration tests 20rsquo new Server(ldquo127001rdquo 27017) lsquopkrsquoCustomPKFactory) p clientopen(function(err p client)

p clientdropDatabase(function(err done)

p clientcreateCollection(lsquotest custom keyrsquo function(err collection)

collectioninsert(lsquoarsquo1 function(err docs)

collection nd(lsquo idrsquonew ObjectID(ldquoaaaaaaaaaaaardquo) function(err cursor)

cursortoArray(function(err items) testassertEquals(1 itemslength) p clientclose()

)

)

)

)

)

)

這是在網路上看到的一段操作 mongodb的程式碼為了循序操作所以必須在一個 callback裡面呼叫下一個動作要使用的函數這個函數裡面還是會使用 callback最後就形成一個非常深的巢狀

這樣的程式碼會比較難進行單元測試有一個簡單的解決方式是

盡量不要使用匿名函數來當作 callback或是 event handler透過這樣的方式

14 2 JavaScript與 NodeJS

Nodejs中文電子書

就可以對各個 handler做單元測試了例如

var http = require(lsquohttprsquo) var tools =

cookieParser function(request response) if(requestheaders[rsquoCookiersquo]) do parsing

var server = httpcreateServer(function(request response)

thisemit(lsquoinitrsquo request response)

) serveron(lsquoinitrsquo toolscookieParser) serverlisten(8080 lsquo127001rsquo)

更進一步可以把 tools改成外部 module例如叫做 toolsjs

moduleexports = cookieParser function(request response) if(requestheaders[rsquoCookiersquo]) do parsing

然後把程式改成

var http = require(lsquohttprsquo)

var server = httpcreateServer(function(request response) thisemit(lsquoinitrsquo re-quest response)

) serveron(lsquoinitrsquo require(lsquo toolsrsquo)cookieParser) serverlisten(8080lsquo127001rsquo)

這樣就可以單元測試 cookieParser了例如使用 nodeunit時可以這樣寫

var testCase = require(lsquonodeunitrsquo)testCase moduleexports = testCase(

ldquosetUprdquo function(cb) thisrequest = headers Cookielsquoname1val1 name2val2rsquo thisresponse = thisresult= name1rsquoval1rsquoname2rsquoval2rsquo

cb()

ldquotearDownrdquo function(cb)

cb()

23 Callback 15

Nodejs中文電子書

ldquonormal caserdquo function(test)

testexpect(1) var obj = require(lsquotoolsrsquo)cookieParser(thisrequest thisresponse)testdeepEqual(obj thisresult) testdone()

)

善於利用模組可以讓程式更好維護與測試

24 CPS(Continuation-Passing Style)

cps 是 callback 使用上的特例形式上就是在函數最後呼叫 callback這樣就好像把函數執行後把結果交給 callback 繼續運行所以稱作continuation-passing style利用 cps可以在非同步執行的情況下透過傳給 callback的這個 cps callback來獲知 callback執行完畢或是取得執行結果例如

lthtmlgt ltbodygt ltdiv id=rdquopanelrdquo style=rdquovisibilityhiddenrdquogtlt divgtlt bodygt lt htmlgt ltscriptgt var request = new XMLHttpRequest() re-questopen(lsquoGETrsquo lsquotest749txt timestamp=rsquo+new Date()getTime() true) re-questaddEventListener(lsquoreadystatechangersquo function(next)

return function() if(thisreadyState===4ampampthisstatus===200) next(thisresponseText)lt== 傳入的 cps callback 在動作完成時執行並取得結果進一步處理

(function(str)lt==這個匿名函數就是 cps callback

documentgetElementById(lsquopanelrsquo)innerHTML=str docu-mentgetElementById(lsquopanelrsquo)stylevisibility = lsquovisiblersquo

) false) requestsend() ltscriptgt

進一步的應用也可以參考 2-6流程控制

16 2 JavaScript與 NodeJS

Nodejs中文電子書

25 函數返回函數與 Currying

前面的 cps範例裡面使用了函數返回函數這是為了把 cps callback傳遞給 onreadystatechange事件處理函數的方法(因為這個事件處理函數並沒有設計好會傳送接收這樣的參數)實際會執行的事件處理函數其實是內層返回的那個函數之外包覆的這個函數主要是為了利用 Closure把 next傳給內層的事件處理函數這個方法更常使用的地方是為了解決一些

scope問題例如

ltscriptgt var accu=0count=10 for(var i=0 iltcount i++)

setTimeout(

function() countndash accu+=i if(countlt=0)

consolelog(accu)

50)

ltscriptgt

最後得出的結果會是 100而不是想像中的 45這是因為等到 setTime-out指定的函數執行時變數 i已經變成 10而離開迴圈了要解決這個問題就需要透過 Closure來保存變數 i

ltscriptgt var accu=0count=10 for(var i=0 iltcount i++)

setTimeout(

function(i) return function() countndash

accu+=i if(countlt=0)

consolelog(accu)

(i)

50)

25 函數返回函數與 Currying 17

Nodejs中文電子書

淺藍色底色的部份是跟上面例子不一樣的地方 ltscriptgt

函數返回函數的另外一個用途是可以暫緩函數執行例如

function add(m n) return m+n

var a = add(20 10) consolelog(a)

add這個函數必須同時輸入兩個參數才有辦法執行如果我希望這個函數可以先給它一個參數等一些處理過後再給一個參數然後得到結

果就必須用函數返回函數的方式做修改

function add(m)

return function(n) return m+n

var wait another arg = add(20)先給一個參數 var a = function(arr)

var ret=0 for(var i=0iltarrlengthi++) ret+=arr[i] return ret

([1234])計算一下另一個參數 var b = wait another arg(a)然後再繼續執行 consolelog(b)

像這樣利用函數返回函數使得原本接受多個參數的函數可以一次

接受一個參數直到參數接收完成才執行得到結果的方式有一個學名就

叫做Currying

綜合以上許多奇技淫巧就可以透過用函數來處理函數的方式調整

程式流程接下來看看

26 流程控制

(以 sync方式使用 async函數避開巢狀 callback循序呼叫 async callback等奇技淫巧)

建議參考

bull httphowtonodeorgcontrol- ow

bull httphowtonodeorgcontrol- ow-part-ii

18 2 JavaScript與 NodeJS

Nodejs中文電子書

bull httphowtonodeorgcontrol- ow-part-iii

bull httpblogmixunet20110202essential-node-js-patterns-and-snippets

這幾篇都是非常經典的 NodeJSJavascript流程控制好文章(阿mixu是在介紹一些 pattern時提到這方面的主題)不過我還是用幾個簡單的程式介紹一下做法跟概念

並發與等待

下面的程式參考了 mixu文章中的做法

var wait = function(callbacks done) consolelog(lsquowait startrsquo) var counter = call-backslength var results = [] var next = function(result) 接收函數執行結果並判斷是否結束執行 resultspush(result) if(ndashcounter == 0) done(results)如果結束執行就把所有執行結果傳給指定的 callback處理 for(var i = 0 i lt callbackslength i++) 依次呼叫所有要執行的函數 callbacks[i](next) consolelog(lsquowait endrsquo)

wait( [ function(next) setTimeout(function() consolelog(lsquodone arsquo) var result =500 next(result) 500) function(next) setTimeout(function() con-solelog(lsquodone brsquo) var result = 1000 next(result) 1000) function(next)setTimeout(function() consolelog(lsquodone crsquo) var result = 1500 next(1500) 1500) ] function(results) var ret = 0 i=0 for( iltresultslength i++) ret+= results[i] consolelog(lsquodone all result lsquo+ret)

)

執行結果wait start wait end done a done b done c done all result 3000

可以看出來其實 wait並不是真的等到所有函數執行完才結束執行而是在所有傳給他的函數執行完畢後(不論同步非同步)才執行處理結

果的函數(也就是 done())

不過這樣的寫法還不夠實用因為沒辦法實際讓函數可以等待執行

完畢又能當作事件處理函數來實際使用上面參考到的 Tim Caswell的文

26 流程控制 19

Nodejs中文電子書

章裡面有一種解法不過還需要額外包裝(在他的例子中)NodeJS核心的 fs物件把一些函數(例如 readFile)用 Currying處理類似像這樣

var fs = require(lsquofsrsquo) var readFile = function(path)

return function(callback errback)

fsreadFile(path function(err data)

if(err) errback()

else callback(data)

)

其他部份可以參考 Tim Caswell的文章他的 Doparallel跟上面的 wait差不多意思這裡只提示一下他沒說到的地方

另外一種做法是去修飾一下 callback當他作為事件處理函數執行後再用 cps的方式取得結果

ltscriptgt function Wait(fns done)

var count = 0 var results = [] thisgetCallback = function(index)

count++ return (function(waitback)

return function() var i=0args=[]for(iltargumentslengthi++)

argspush(arguments[i])

argspush(waitback) fns[index]apply(thisargs)

)(function(result) resultspush(result) if(ndashcount == 0)

20 2 JavaScript與 NodeJS

Nodejs中文電子書

done(results)

)

var a = new Wait(

[ function(waitback) consolelog(lsquodone arsquo) var result = 500 wait-back(result) function(waitback) consolelog(lsquodone brsquo) var result =1000 waitback(result) function(waitback) consolelog(lsquodone crsquo) varresult = 1500 waitback(result) ] function(results) var ret = 0 i=0for( iltresultslength i++) ret += results[i] consolelog(lsquodone allresult lsquo+ret)

) var callbacks = [agetCallback(0)agetCallback(1)agetCallback(0)agetCallback(2)]一次取出要使用的 callbacks避免結果提早送出 setTimeout(callbacks[0]500) setTimeout(callbacks[1] 1000) setTimeout(callbacks[2] 1500) setTime-out(callbacks[3] 2000) 當所有取出的 callbacks執行完畢就呼叫 done()來處理結果 ltscriptgt

執行結果

done a done b done a done c done all result 3500

上面只是一些小實驗更成熟的作品是 Tim Caswell 的 stephttpsgithubcomcreationixstep

如果希望真正使用同步的方式寫非同步則需要使用 Promisejs這一類的 library來轉換非同步函數不過他結構比較複雜 XD(見仁見智不過有些人認為 Promise有點過頭了)httpblogsmsdncombrbucktonarchive20110815promise-js-2-0-promise-framework-for-javascriptaspx

如果想不透過其他 Library做轉換又能直接用同步方式執行非同步函數大概就要使用一些需要額外 compile原始程式碼的方法了例如 BrunoJouhier的 streamlinejshttpsgithubcomSagestreamlinejs

26 流程控制 21

Nodejs中文電子書

循序執行

循序執行可以協助把非常深的巢狀 callback結構攤平例如用這樣的簡單模組來做(serialjs)

moduleexports = function(funs) var c = 0 if(isArrayOfFunctions(funs))

throw(lsquoArgument type was not matched Should be array of func-tionsrsquo)

return function()

var args = Arrayprototypeslicecall(arguments 0) if((cgt=funslength))

c++ return funs[c-1]apply(this args)

function isArrayOfFunctions(f ) if(typeof f == lsquoobjectrsquo) return false if(flength)return false if(fconcat) return false if(fsplice) return false var i = 0 for(iltflength i++)

if(typeof f[i] == lsquofunctionrsquo) return false

return true

簡單的測試範例(testSerialjs)使用 fs 模組確定某個 path 是檔案然後讀取印出檔案內容這樣會用到兩層的 callback所以測試中有使用serial的版本與 nested callbacks的版本做對照

var serial = require(lsquoserialrsquo) fs = require(lsquofsrsquo) path = lsquodclientjsrsquo cb = serial([ func-tion(err data)

if(err)

if(dataisFile) fsreadFile(path cb)

22 2 JavaScript與 NodeJS

Nodejs中文電子書

else consolelog(err)

function(err data)

if(err) consolelog(lsquo[ attened by searial]rsquo) con-solelog(datatoString(lsquoutf8rsquo))

else consolelog(err)

]) fsstat(path cb)

fsstat(path function(err data) 第一層 callback if(err)

if(dataisFile)

fsreadFile(path function(err data) 第二層 callback if(err)

consolelog(lsquo[nested callbacks]rsquo) con-solelog(datatoString(lsquoutf8rsquo))

else consolelog(err)

)

else consolelog(err)

)

關鍵在於這些 callback的執行是有順序性的所以利用 serial返回的一個函數 cb來取代這些 callback然後在 cb中控制每次會循序呼叫的函數

26 流程控制 23

Nodejs中文電子書

就可以把巢狀的 callback攤平成循序的 function陣列(就是傳給 serial函數的參數)

測試中的dclientjs 是一個簡單的 dnode 測試程式放在跟 testSerialjs同一個目錄

var dnode = require(lsquodnodersquo)

dnodeconnect(8000 lsquolocalhostrsquo function(remote)

remoterestart(function(str) consolelog(str) processexit()

)

)

執行測試程式後出現結果

[ attened by searial] var dnode = require(lsquodnodersquo)

dnodeconnect(8000 lsquolocalhostrsquo function(remote)

remoterestart(function(str) consolelog(str) processexit()

)

)

[nested callbacks] var dnode = require(lsquodnodersquo)

dnodeconnect(8000 lsquolocalhostrsquo function(remote)

remoterestart(function(str) consolelog(str) processexit()

)

)

對照起來看兩種寫法的結果其實是一樣的但是利用 serialjs巢狀的 callback結構就會消失

不過這樣也只限於順序單純的狀況如果函數執行的順序比較複雜

(不只是一直線)還是需要用功能更完整的流程控制模組比較好例如

httpsgithubcomcaolanasync

24 2 JavaScript與 NodeJS

3

Nodejs安裝與設定

本篇將講解如何在各個不同 OS建立 NodeJS環境目前 NodeJS 048版本環境架設方式需依賴 Linux指令才可編譯完成當然在不同作業系統中也已經有 NodeJS package可以直接使用指令快速架設以下各不同作業系統解說如何安裝 NodeJS

31 Ubuntu Linux

更新推薦使用 nvm

git clone gitgithubcomcreationixnvmgit ~nvm

echo rdquo ~nvmnvmshrdquo gtgt ~bashrc

nvm install v0614

nvm alias default v0614

以 上 可 參 考 http dreamerslabcom blog tw how-to-setup-a-node-js-development-environment-on-ubuntu-11-04

使用 APT套件管理工具是常見的方法以下是使用社群提供的 PPA安裝方式

sudo apt-get install python-software-properties

sudo add-apt-repository ppachris-leanodejs-devel

25

Nodejs中文電子書

sudo apt-get update

sudo apt-get install nodejs

32 Other Linux

Linux很適合作為 NodeJS的伺服器作業系統及開發環境安裝前請先確認以下套件已正確安裝

bull curl (wget)用來下載檔案的工具

bull git先進的版本控制工具

bull g++ GNU C++軟體編譯工具

bull make GNU軟體專案建置工具

安裝指令如下如設有權限問題請在指令前面加上 sudo

git clone httpsgithubcomjoyentnodegit

cd node

git checkout v067

configure

make

sudo make install

接著測試 nodeJS是否正常執行

node --version

出現版本訊息即表示安裝成功

33 Windows

nodeJS 在 v060 版本之後開始正式支援 windows native直接使用nodeexe 就可以執行程式支援性完全與 linux 相同更棒的部份就是不需經過編譯經過下載之後簡單設定完成立即開發 node程式

26 3 Nodejs安裝與設定

Nodejs中文電子書

下載 nodejs安裝檔案 lthttpnodejsorgdownloadgt

如此完成 windows native nodeexe安裝接著可以進入 command line執行測試在 command line輸入rdquonoderdquo會出現 nodejs版本訊息畫面表示安裝完成

33 Windows 27

Nodejs中文電子書

28 3 Nodejs安裝與設定

4

Nodejs基礎

前篇文章已經由介紹安裝至設定都有完整介紹nodeJS 內部除了javascript常用的函式 (function)物件 (object)之外也有許多不同的自訂物件nodeJS預設建立這些物件為核心物件是為了要讓開發流程更為這些資料在官方文件已經具有許多具體說明接下來將會介紹在開發 nodeJS程式時常見的物件特性與使用方法

41 nodejs http伺服器建立

在 lsquonodejs官方網站 lthttpnodejsorggtlsquo裡面有舉一個最簡單的 HTTP伺服器建立一開始初步就是建立一個伺服器平台讓 nodejs可以與瀏覽器互相行為每種語言一開始的程式建立都是以 Hello world開始最初也從 Hello world帶各位進入 nodejs的世界

輸入以下程式碼儲存檔案為 node basic http hello worldjs

var server

ip = rdquo127001rdquo

port = 1337

http = require(rsquohttprsquo)

server = httpcreateServer(function (req res)

29

Nodejs中文電子書

reswriteHead(200 rsquoContent-Typersquo rsquotextplainrsquo)

resend(rsquoHello Worldnrsquo)

)

serverlisten(port ip)

consolelog(rdquoServer running at httprdquo + ip + rdquordquo + port)

程式碼解講一開始需要有幾個基本的變數

bull ip 機器本身的 ip位置因為使用本地端因此設定為 127001

bull port 需要開通的阜號通常設定為 http port 80因範例不希望與基本 port相衝隨意設定為 1337

在 nodejs 的程式中有許多預設的模組可以使用因此需要使用require方法將模組引入在這邊我們需要使用 http這個模組因此將 http載入Http模組裡面內建有許多方法可以使用這邊採用 createServer創建一個基本的 http伺服器再將 http伺服器給予一個 server變數裡面的回呼函式 (call back function)可以載入 http伺服器的資料與回應方法 (requestresponse)在程式裡面就可以看到我們直接回應給瀏覽器端所需的 Header回應內容

reswriteHead(200 rsquoContent-Typersquo rsquotextplainrsquo)

resend(rsquoHello Worldnrsquo)

Http伺服器需要設定 port ip在最後需要設定 Http監聽需要使用到listen事件監聽所有 Http伺服器行為

httplisten(port ip)

所有事情都完成之後需要確認伺服器正確執行因此使用 console在javascript裡就有這個原生物件console所印出的資料都會顯示於 nodejs伺服器頁面這邊印出的資料並不會傳送到使用者頁面上之後許多除壞

(debug)都會用到 console物件

consolelog(rdquoServer running at httprdquo + ip + rdquordquo + port)

30 4 Nodejs基礎

Nodejs中文電子書

42 nodejs http路徑建立

前面已經介紹如何建立一個簡單的 http伺服器接下來本章節將會介紹如何處理伺服器路徑 (rout)問題在 http的協定下所有從瀏覽器發出的要求 (request)都需要經過處理路徑上的建立也是如此

路徑就是指伺服器 ip位置或者是網域名稱之後對於伺服器給予的要求修改剛才的 hello world檔案修改如下

server = httpcreateServer(function (req res)

consolelog(requrl)

reswriteHead(200 rsquoContent-Typersquo rsquotextplainrsquo)

resend(rsquohello worldnrsquo)

)

重新啟動 nodejs程式後在瀏覽器端測試一下路徑行為結果如下圖

當在瀏覽器輸入 http1270011337test 在伺服器端會收到兩個要求一個是我們輸入的test要求另外一個則是faviconicotest的路徑要求http伺服器本身需要經過程式設定才有辦法回應給瀏覽器端所需要的回應在伺服器中所有的路徑要求都是需要被解析才有辦法取得資料從

42 nodejs http路徑建立 31

Nodejs中文電子書

上面解說可以了解到在 nodejs當中所有的路徑都需要經過設定未經過設定的路由會讓瀏覽器無法取得任何資料導致錯誤頁面的發生底下將會解

說如何設定路由同時避免發生錯誤情形先前 nodejs程式需要增加一些修改才能讓使用者透過瀏覽器在不同路徑時有不同的結果根據剛才

的程式做如下的修改

var server

ip = rdquo127001rdquo

port = 1337

http = require(rsquohttprsquo)

url = require(rsquourlrsquo)

path

server = httpcreateServer(function (req res)

path = urlparse(requrl)

reswriteHead(200 rsquoContent-Typersquo rsquotextplainrsquo)

switch (pathpathname)

case rdquoindexrdquo

resend(rsquoI am indexnrsquo)

break

case rdquotestrdquo

resend(rsquothis is test pagenrsquo)

break

default

resend(rsquodefault pagenrsquo)

break

)

serverlisten(port ip)

consolelog(rdquoServer running at httprdquo + ip + rdquordquo + port)

程式做了片段的修改首先載入 url模組另外增加一個 path變數url模組就跟如同他的命名一般專門處理 url字串處理裡面提供了許多方法來解決路徑上的問題因為從瀏覽器發出的要求路徑可能會帶有多種需求

或者 GET參數組合等因此我們需要將路徑單純化取用路徑部分的資料即可例如使用者可能會送出 http1270011337testsend=1如果直接

32 4 Nodejs基礎

Nodejs中文電子書

信任 requrl就會收到結果為testsend=1所以需要透過 url模組的方法將路徑資料過濾

在這邊使用 urlparse的方法裡面帶入網址格式資料會回傳路徑資料為了後需方便使用將回傳的資料設定到 path變數當中在回傳的路徑資料裡面包含資訊如下圖

這邊只需要使用單純的路徑要求直接取用 pathpathname就可以達到我們的目的

最後要做路徑的判別在不同的路徑可以指定不同的輸出在範例中

有三個可能結果第一個從瀏覽器輸入index就會顯示 index結果test 就會呈現出 test頁面最後如果都不符合預期的輸入會直接顯示 default的頁面最後的預防可以讓瀏覽器不會出現非預期結果讓程式的可靠性提昇

底下為測試結果

42 nodejs http路徑建立 33

Nodejs中文電子書

43 nodejs檔案讀取

前面已經介紹如何使用路由(rount)做出不同的回應實際應用只有在瀏覽器只有輸出幾個文字資料總是不夠的在本章節中將介紹如何使

用檔案讀取輸出檔案資料讓使用者在前端瀏覽器也可以讀取到完整的

html css javascript檔案輸出

檔案管理最重要的部分就是 lsquoFile system lthttpnodejsorgdocslatestapifshtmlgtlsquo這個模組此模組可以針對檔案做管理監控讀取等行為裡面有許多預設的方法底下是檔案輸出的基本範例底下會有兩個檔案

第一個是靜態 html檔案

ltDOCTYPE htmlgt

lthtml xmlns=rdquohttpwwww3org1999xhtmlrdquo xmllang=rdquozh-CNrdquo lang=rdquozh-CNrdquogt

ltheadgt

ltmeta http-equiv=rdquoContent-Typerdquo content=rdquotexthtml charset=UTF-7rdquo gt

lttitlegtnodejs index html filelttitlegt

ltheadgt

ltbodygt

34 4 Nodejs基礎

Nodejs中文電子書

lth1gtnodejs index html filelth1gt

ltbodygt

lthtmlgt

另一個為 nodejs程式

var fs = require(rdquofsrdquo)

filename = rdquostaticindexhtmlrdquo

encode = rdquoutf8rdquo

fsreadFile(filename encode function(err file)

consolelog(file)

)

一開始直接載入 le system模組 載入名稱為 fs讀取檔案主要使

用的方法為 readFile裡面以三個參數路徑 ( le path) 編碼方式 (encoding)

回應函式 (callback)路徑必須要設定為靜態 html所在位置才能指定到正確的檔案靜態檔案的編碼方式也必須正確這邊使用靜態檔案的編

碼為 utf8如果編碼設定錯誤nodejs讀取出來檔案結果會使用 byte raw格式輸出如果錯誤編碼格式會導致輸出資料為 byte raw

回應函式中裡面會使用兩個變數error為錯誤資訊如果讀取的檔案不存在或者發生錯誤error數值會是 true如果成功讀取資料 error將會是 falsecontent則是檔案內容資料讀取後將會把資料全數丟到 content這個變數當中

最後程式的輸出結果畫面如下

43 nodejs檔案讀取 35

Nodejs中文電子書

44 nodejs http靜態檔案輸出

前面已經了解如何讀取本地端檔案接下來將配合 http伺服器路由讓每個路由都能夠輸出相對應的靜態 html檔案首先新增加幾個靜態 html檔案

ltDOCTYPE htmlgt

lthtml xmlns=rdquohttpwwww3org1999xhtmlrdquo xmllang=rdquozh-CNrdquo lang=rdquozh-CNrdquogt

ltheadgt

ltmeta http-equiv=rdquoContent-Typerdquo content=rdquotexthtml charset=UTF-7rdquo gt

lttitlegtnodejs index html filelttitlegt

ltheadgt

ltbodygt

lth1gtnodejs index html filelth1gt

ltbodygt

lthtmlgt

ltDOCTYPE htmlgt

lthtml xmlns=rdquohttpwwww3org1999xhtmlrdquo xmllang=rdquozh-CNrdquo lang=rdquozh-CNrdquogt

ltheadgt

ltmeta http-equiv=rdquoContent-Typerdquo content=rdquotexthtml charset=UTF-7rdquo gt

lttitlegtnodejs test html filelttitlegt

ltheadgt

ltbodygt

lth1gtnodejs test html filelth1gt

ltbodygt

lthtmlgt

ltDOCTYPE htmlgt

lthtml xmlns=rdquohttpwwww3org1999xhtmlrdquo xmllang=rdquozh-CNrdquo lang=rdquozh-CNrdquogt

ltheadgt

ltmeta http-equiv=rdquoContent-Typerdquo content=rdquotexthtml charset=UTF-7rdquo gt

lttitlegtnodejs static html filelttitlegt

ltheadgt

ltbodygt

lth1gtnodejs static html filelth1gt

ltbodygt

lthtmlgt

36 4 Nodejs基礎

Nodejs中文電子書

準備一個包含基本路由功能的 http伺服器

var server

ip = rdquo127001rdquo

port = 1337

http = require(rsquohttprsquo)

url = require(rsquourlrsquo)

server = httpcreateServer(function (req res)

var path = urlparse(requrl)

)

serverlisten(port ip)

consolelog(rdquoServer running at httprdquo + ip + rdquordquo + port)

加入 le system 模組使用 readFile 的功能將這一段程式放置於

createServer的回應函式中

fsreadFile(filePath encode function(err file)

)

readFile的回應函式裡面加入頁面輸出讓瀏覽器可以正確讀到檔案在這邊我們設定讀取的檔案為 html 靜態檔案所以 Content-type 設定為texthtml讀取到檔案的內容將會正確輸出成 html靜態檔案

fsreadFile(filePath encode function(err file)

reswriteHead(200 rsquoContent-Typersquo rsquotexthtmlrsquo)

reswrite(file)

resend()

)

到這邊為止基本的程式內容都已經完成剩下一些細節的調整首先

路徑上必須做調整目前的靜態檔案全部都放置於 static資料夾底下設定一個變數來記住資料夾位置

接著將瀏覽器發出要求路徑與資料夾組合讀取正確 html靜態檔案使用者有可能會輸入錯誤路徑所以在讀取檔案的時候要加入錯誤處理

同時回應 404伺服器無法正確回應的 http header格式

加入這些細節的修改一個基本的 http 靜態 html輸出伺服器就完成了完整程式碼如下

44 nodejs http靜態檔案輸出 37

Nodejs中文電子書

var server

ip = rdquo127001rdquo

port = 1337

http = require(rsquohttprsquo)

fs = require(rdquofsrdquo)

folderPath = rdquostaticrdquo

url = require(rsquourlrsquo)

path

filePath

encode = rdquoutf8rdquo

server = httpcreateServer(function (req res)

path = urlparse(requrl)

filePath = folderPath + pathpathname

fsreadFile(filePath encode function(err file)

if (err)

reswriteHead(404 rsquoContent-Typersquo rsquotextplainrsquo)

resend()

return

reswriteHead(200 rsquoContent-Typersquo rsquotextapplicationrsquo)

reswrite(file)

resend()

)

)

serverlisten(port ip)

consolelog(rdquoServer running at httprdquo + ip + rdquordquo + port)

45 nodejs http GET資料擷取

http伺服器中除了路由之外另一個最常使用的方法就是擷取 GET資料本單元將會介紹如何透過基本 http伺服器擷取瀏覽器傳來的要求擷取 GET資料

在 http協定中GET參數都是藉由 URL從瀏覽器發出要求送至伺服器

38 4 Nodejs基礎

Nodejs中文電子書

端基本的傳送網址格式可能如下

http127001testsend=1amptest=2

上面這段網址裡面的 GET參數就是 send而這個變數的數值就為 1如果想要在 http伺服器取得 GET資料需要在瀏覽器給予的要求 (request)做處理

首先需要載入 query string這個模組這個模組主要是用來將字串資料

過濾後轉換成javascript物件

qs = require(rsquoquerystringrsquo)

接著在第一階段利用 url模組過濾瀏覽器發出的 URL資料後將回應的物件裡面的 query這個變數是一個字串值資料過濾後如下

send=1amptest=2

透過 query string使用 parse這個方法將資料轉換成 javascript物件就表示 GET的資料已經被伺服器端正式擷取下來

path = urlparse(requrl)

parameter = qsparse(pathquery)

整個 nodejs http GET參數完整擷取程式碼如下

var server

ip = rdquo127001rdquo

port = 1337

http = require(rsquohttprsquo)

qs = require(rsquoquerystringrsquo)

url = require(rsquourlrsquo)

server = httpcreateServer(function (req res)

var path = urlparse(requrl)

parameter = qsparse(pathquery)

consoledir(parameter)

reswriteHead(200 rsquoContent-Typersquo rsquotextplainrsquo)

reswrite(rsquoBrowser test GET parameternrsquo)

resend()

)

45 nodejs http GET資料擷取 39

Nodejs中文電子書

serverlisten(port ip)

consolelog(rdquoServer running at httprdquo + ip + rdquordquo + port)

程式運作之後由瀏覽器輸入要求網址之後nodejs伺服器端回應資料為

Server running at http1270011337

send rsquo1rsquo test rsquo1rsquo

46 本章結語

前面所解說的部份一大部分主要是處理 http伺服器基本問題雖然在某些部分有牽扯到 http 伺服器基本運作原理主要還是希望可以藉由這些基本範例練習 nodejs練習回應函式與語法串接的特點習慣編寫javascript風格程式當然直接這樣開發 nodejs是非常辛苦的接下來在模組實戰開發的部份將會介紹特定的模組一步一步帶領各位從無到有進行

nodejs應用程式開發

40 4 Nodejs基礎

5

NPM套件管理工具

npm全名為 Node Package Manager是 Nodejs的套件(package)管理工具類似 Perl的 ppm或 PHP的 PEAR等安裝 npm後使用npm install

module name指令即可安裝新套件維護管理套件的工作會更加輕鬆

npm可以讓Nodejs的開發者直接利用擴充線上的套件庫(packagesregistry)加速軟體專案的開發npm提供很友善的搜尋功能可以快速找到安裝需要的套件當這些套件發行新版本時npm也可以協助開發者自動更新這些套件

npm不僅可用於安裝新的套件它也支援搜尋列出已安裝模組及更新的功能

51 安裝 NPM

Nodejs在 063版本開始內建 npm讀者安裝的版本若是此版本或更新的版本就可以略過以下安裝說明

若要檢查 npm是否正確安裝可以使用以下的指令

npm -v

41

Nodejs中文電子書

執行結果說明

若 npm正確安裝執行 npm -v將會看到類似 110-2的版本訊息

若讀者安裝的 Nodejs版本比較舊或是有興趣嘗試自己動手安裝 npm工具則可以參考以下的說明

安裝於Windows系統

Nodejs for Windows 於 062 版開始內建 npm使用 nodejsorg 官方提供的安裝程式不需要進一步的設定就可以立即使用 npm指令對於Windows的開發者來說大幅降低環境設定的問題與門檻

除了使用 Nodejs內建的 npm讀者也可以從 npm官方提供的以下網址

httpnpmjsorgdist

這是由 npm提供的 Fancy Windows Install版本請下載壓縮檔(例如npm-110-3zip)並將壓縮檔內容解壓縮至 Nodejs的安裝路徑(例如CProgram Filesnodejs)

解壓縮後在 Nodejs的安裝路徑下應該有以下的檔案及資料夾

bull npmcmd(檔案)

bull node modules(資料夾)

安裝於 Linux系統

Ubuntu Linux的使用者可以加入 NPM Unoffcial PPA這個 repository即可使用 apt-get完成 npm安裝

42 5 NPM套件管理工具

Nodejs中文電子書

Ubuntu Linux使用 apt-get安裝 npm

sudo apt-get install python-software-properties

sudo add-apt-repository ppagias-kay-leenpm

sudo apt-get update

sudo apt-get install npm

npm官方提供的安裝程式 installsh可以適用於大多數的 Linux系統使用這個安裝程式請先確認

1 系統已安裝 curl工具(請使用 curl --version查看版本訊息)

2 已安裝 Nodejs並且 PATH正確設置

3 Nodejs的版本必須大於 04x

以下為 npm提供的安裝指令

curl httpnpmjsorginstallsh | sh

安裝成功會看到如下訊息

installsh安裝成功的訊息

npm10105 homeUSERNAMElocalnodelibnode_modulesnpm

It worked

安裝於Mac OS X

建議採用與 Nodejs相同的方式進行 npm的安裝例如使用MacPorts安裝 Nodejs就同樣使用MacPorts安裝 npm這樣對日後的維護才會更方便容易

使用 MacPorts安裝 npm是本書比較建議的方式它可以讓 npm的安裝移除及更新工作自動化將會幫助開發者節省寶貴時間

51 安裝 NPM 43

Nodejs中文電子書

安裝MacPorts的提示

在MacPorts網站可以取得 OS X系統版本對應的安裝程式(例如 106或 107)httpwwwmacportsorg安裝過程會詢問系統管理者密碼使用預設的選項完成安裝即可安

裝MacPorts之後在終端機執行 port -v將會看到MacPorts的版本訊息

安裝 npm之前先更新MacPorts的套件清單以確保安裝的 npm是最新版本

sudo port -d selfupdate

接著安裝 npm

sudo port install npm

若讀者的 Nodejs並非使用MacPorts安裝則不建議使用MacPorts安裝npm因為 MacPorts會自動檢查並安裝相依套件而 npm相依 nodejs所以MacPorts也會一併將 nodejs套件安裝造成先前讀者使用其它方式安裝的 nodejs被覆蓋

讀者可以先使用 MacPorts安裝 curl(sudo port install curl)

再參考 Linux的 installsh安裝方式即可使用 npm官方提供的安裝程式

NPM安裝後測試

npm是指令列工具(command-line tool)使用時請先打開系統的文字終端機工具

測試 npm安裝與設定是否正確請輸入指令如下

npm -v

或是

npm --version

如果 npm已經正確安裝設定就會顯示版本訊息

44 5 NPM套件管理工具

Nodejs中文電子書

執行結果(範例)

110-2

52 使用 NPM安裝套件

npm目前擁有超過 6000種套件(packages)可以在 npm registry使用關鍵字搜尋套件

httpsearchnpmjsorg

舉例來說在關鍵字欄位輸入「coffee-script」下方的清單就會自動列出包含 coffee-script關鍵字的套件

接著我們回到終端機模式的操作npm的指令工具本身就可以完成套

件搜尋的任務

例如以下的指令同樣可以找出 coffee-script相關套件

npm search coffee-script

以下是搜尋結果的參考畫面

52 使用 NPM安裝套件 45

Nodejs中文電子書

找到需要的套件後(例如 express)即可使用以下指令安裝

npm install coffee-script

值得注意的一點是使用 npm install會將指定的套件安裝在工

作目錄(Working Directory)的 node modules資料夾下

以Windows為例如果執行 npm install的目錄位於

Cproject1

那麼 npm將會自動建立一個 node modules的子目錄(如果不存在)

Cproject1node modules

並且將下載的套件放置於這個子目錄例如

Cproject1node modulescoffee-script

這個設計讓專案可以個別管理相依的套件並且可以在專案佈署或發

行時將這些套件(位於 node modules)一併打包方便其它專案的使用者不必再重新下載套件

這個 npm install的預設安裝模式為 local(本地)只會變更當前專案的資料夾不會影響系統

另一種安裝模式稱為 global(全域)這種模式會將套件安裝到系統資

料夾也就是 npm安裝路徑的 node modules資料夾例如

CProgram Filesnodejsnode modules

46 5 NPM套件管理工具

Nodejs中文電子書

是否要使用全域安裝可以依照套件是否提供新指令來判斷舉例來說express 套件提供 express 這個指令而 coffee-script 則提供 coffee

指令

在 local 安 裝 模 式 中這 些 指 令 的 程 式 檔 案會 被 安 裝 到node modules的 bin這個隱藏資料夾下除非將bin的路徑加入 PATH環境變數否則要執行這些指令將會相當不便

為了方便指令的執行我們可以在 npm install 加上 -g 或 --

global參數啟用 global安裝模式例如

npm install -g coffee-script

npm install -g express

使用 global安裝模式需要注意執行權限的問題若權限不足可能會出現類似以下的錯誤訊息

npm ERR Error EACCES permission denied rsquorsquo

npm ERR

npm ERR Please try running this command again as rootAdministrator

要獲得足夠得執行權限請參考以下說明

bull Windows 7 或 2008 以上在「命令提示字元」的捷徑按右鍵選擇「以系統管理員身分執行」執行 npm指令時就會具有 Administrator身分

bull Mac OS X或 Linux系統可以使用 sudo指令例如

sudo npm install -g express

bull Linux系統可以使用 root權限登入或是以「sudo su -」切換成 root身分(使用 root權限操作系統相當危險因此並不建議使用這種方式)

若加上 -g參數使用 npm install -g coffee-script完成安裝

後就可以在終端機執行 coffee指令例如

coffee -v

52 使用 NPM安裝套件 47

Nodejs中文電子書

執行結果(範例)

CoffeeScript version 120

53 套件的更新及維護

除了前一節說明的 search 及 install 用法npm 還提供其他許多指令(commands)

使用 npm help可以查詢可用的指令

npm help

執行結果(部分)

where ltcommandgt is one of

adduser apihelp author bin bugs c cache completion

config deprecate docs edit explore faq find get

help help-search home i info init install la link

list ll ln login ls outdated owner pack prefix

prune publish r rb rebuild remove restart rm root

run-script s se search set show star start stop

submodule tag test un uninstall unlink unpublish

unstar up update version view whoami

使用 npm help command可以查詢指令的詳細用法例如

npm help list

接下來本節要介紹開發過程常用的 npm指令

使用 list可以列出已安裝套件

npm list

48 5 NPM套件管理工具

Nodejs中文電子書

執行結果(範例)

-- coffee-script120

- express256

- connect185

| -- formidable108

-- mime124

-- mkdirp007

-- qs041

檢視某個套件的詳細資訊例如

npm show express

升級所有套件(如果該套件已發佈更新版本)

npm update

升級指定的套件

npm update express

移除指定的套件

npm uninstall express

54 使用 packagejson

對於正式的 Nodejs專案可以建立一個命名為 packagejson的設

定檔(純文字格式)檔案內容參考範例如下

54 使用 packagejson 49

Nodejs中文電子書

packagejson(範例)

rdquonamerdquo rdquoapplication-namerdquo

rdquoversionrdquo rdquo001rdquo

rdquoprivaterdquo true

rdquodependenciesrdquo

rdquoexpressrdquo rdquo255rdquo

rdquocoffee-scriptrdquo rdquolatestrdquo

rdquomongooserdquo rdquogt= 253rdquo

其中 name與 version依照專案的需求設置

需要注意的是 dependencies的設定它用於指定專案相依的套件名

稱及版本

bull rdquoexpressrdquo rdquo255rdquo

代表此專案相依版本 255的 express套件

bull rdquocoffee-scriptrdquo rdquolatestrdquo

使用最新版的 coffee-script套件(每次更新都會檢查新版)

bull rdquomongooserdquo rdquogt= 253rdquo

使用版本大於 253的 mongoose套件

假設某個套件的新版可能造成專案無法正常運作就必須指定套件的

版本避免專案的程式碼來不及更新以相容新版套件通常在開發初期的

專案需要盡可能維持新套件的相容性(以取得套件的更新或修正)可以

用「gt=」設定最低相容的版本或是使用「latest」設定永遠保持最新

套件

50 5 NPM套件管理工具

6

Express介紹

在前面的 nodejs基礎當中介紹許多許多開設 http的使用方法及介紹以及許多基本的 nodejs基本應用

接下來要介紹一個套件稱為 express [Express](httpexpressjscom)這個套件主要幫忙解決許多 nodejs http server所需要的基本服務讓開發 httpservice變得更為容易不需要像之前需要透過層層模組(module)才有辦法開始編寫自己的程式

這個套件是由 TJ Holowaychuk 製作而成的套件裡面包含基本的路由處理 (route)http資料處理(GETPOSTPUT)另外還與樣板套件(jshtml template engine)搭配同時也可以處理許多複雜化的問題

61 Express安裝

安裝方式十分簡單只要透過之前介紹的 NPM就可以使用簡單的指令安裝指令如下

這邊建議需要將此套件安裝成為全域模組方便日後使用

51

Nodejs中文電子書

62 Express基本操作

express的使用也十分簡單先來建立一個基本的 hello world

var app = require(rsquoexpressrsquo)createServer()

port = 1337

applisten(port)

appget(rsquorsquo function(req res)

ressend(rsquohello worldrsquo)

)

consolelog(rsquostart express servernrsquo)

可以從上面的程式碼發現基本操作與 nodejs http的建立方式沒有太大差異主要差在當我們設定路由時可以直接透過 appget方式設定回應與接受方式

63 Express路由處理

Express對於 http服務上有許多包裝讓開發者使用及設定上更為方便例如有幾個路由設定那我們就統一藉由 appget來處理

Create http server

appget(rsquorsquo function(req res)

ressend(rsquohello worldrsquo)

)

appget(rsquotestrsquo function(req res)

ressend(rsquotest renderrsquo)

)

appget(rsquouserrsquo function(req res)

ressend(rsquouser pagersquo)

)

52 6 Express介紹

Nodejs中文電子書

如上面的程式碼所表示appget可以帶入兩個參數第一個是路徑名稱設定第二個為回應函式 (call back function)回應函式裡面就如同之前的 createServer方法裡面包含 requestresponse兩個物件可供使用使用者就可以透過瀏覽器輸入不同的 url切換到不同的頁面顯示不同的結果

路由設定上也有基本的配對方式讓使用者從瀏覽器輸入的網址可以

是一個變數只要符合型態就可以有對應的頁面產出例如

Create http server

appget(rsquouseridrsquo function(req res)

ressend(rsquouser rsquo + reqparamsid)

)

appget(rsquonumberrsquo function(req res)

ressend(rsquonumber rsquo + reqparamsnumber)

)

裡 面 使 用 到number 從 網 址 輸 入 之 後 就 可 以 直 接 使 用reqparamsnumber 取得所輸入的資料變成 url 參數使用當然前面也是可以加上路徑的設定userid在瀏覽器上路徑必須符合userxxx透

過 reqparamsid就可以取到 xxx這個字串值

另外express參數處理也提供了路由參數配對處理也可以透過正規表示法作為參數設定

var app = require(rsquoexpressrsquo)createServer()

port = 1337

applisten(port)

appget(^ip((d23)((d23))((d23))((d23))) function(req res)

ressend(reqparams)

)

上面程式碼可以發現後面路由設定的型態是正規表示法裡面設定

格式為ip之後必須要加上 ip型態才會符合資料格式同時取得 ip資料已經由正規表示法將資料做分群因此可以取得 ip的四個數字

此程式執行之後可以透過瀏覽器測試輸入網址為 localhost3000ip25525510010可以從頁面獲得資料

63 Express路由處理 53

Nodejs中文電子書

此章節全部範例程式碼如下

overview

author Caesar Chi

blog clonnblogspotcom

version 20120226

create server

var app = require(rsquoexpressrsquo)createServer()

port = 1337

applisten(port)

normal style

appget(rsquorsquo function(req res)

ressend(rsquohello worldrsquo)

)

appget(rsquotestrsquo function(req res)

ressend(rsquotest renderrsquo)

)

parameter style

appget(rsquouseridrsquo function(req res)

ressend(rsquouser rsquo + reqparamsid)

)

appget(rsquonumberrsquo function(req res)

ressend(rsquonumber rsquo + reqparamsnumber)

)

REGX style

appget(^ip((d23)((d23))((d23))) function(req res)

ressend(reqparams)

)

appget(rsquorsquo function(req res)

ressend(rsquoPage not foundrsquo 404)

54 6 Express介紹

Nodejs中文電子書

)

consolelog(rsquostart express servernrsquo)

64 Express middleware

Express 裡面有一個十分好用的應用概念稱為 middleware可以透過middleware做出複雜的效果同時上面也有介紹 next方法參數傳遞就是靠 middleware的概念來傳遞參數讓開發者可以明確的控制程式邏輯

create http server

appuse(expressbodyParser())

appuse(expressmethodOverride())

appuse(expresssession())

上面都是一種 middleware的使用方式透過 appuse方式裡面載入函式執行方法回應函式會包含三個基本參數responserequestnext其中next表示下一個 middleware執行函式同時會自動將預設三個參數繼續帶往下個函式執行底下有個實驗

上面的片段程式執行後開啟瀏覽器連結上 localhost1337會發現伺服器回應結果順序如下

first middle ware

second middle ware

execute middle ware

end middleware function

從上面的結果可以得知剛才設定的 middleware都生效了在 appuse設定的 middleware是所有 url皆會執行方法如果有指定特定方法就可以使用 appget的 middleware設定在 appget函式的第二個參數就可以帶入函式或者是匿名函式只要函式裡面最後會接受 request response next這三個參數同時也有正確指定 next函式的執行時機最後都會執行到最後一個方法當然開發者也可以評估程式邏輯要執行到哪一個階段讓邏輯

可以更為分明

64 Express middleware 55

Nodejs中文電子書

65 Express路由應用

在實際開發上可能會遇到需要使用參數等方式混和變數一起使用

express裡面提供了一個很棒的處理方法 appall這個方式可以先採用基本路由配對再將設定為每個不同的處理方式開發者可以透過這個方式簡

化自己的程式邏輯

overview

author

version 20120226

create server

var app = require(rsquoexpressrsquo)createServer()

port = 1337

users = [

name rsquoClonnrsquo

name rsquoChirsquo

]

applisten(port)

appall(rsquouseridoprsquo function(req res next)

requser = users[reqparamsid]

if (requser)

next()

else

next(new Error(rsquocannot find user rsquo + reqparamsid))

)

appget(rsquouseridrsquo function(req res)

ressend(rsquoviewing rsquo + requsername)

)

appget(rsquouserideditrsquo function(req res)

ressend(rsquoediting rsquo + requsername)

)

56 6 Express介紹

Nodejs中文電子書

appget(rsquouseriddeletersquo function(req res)

ressend(rsquodeleting rsquo + requsername)

)

appget(rsquorsquo function(req res)

ressend(rsquoPage not foundrsquo 404)

)

consolelog(rsquostart express servernrsquo)

內部宣告一組預設的使用者分別給予名稱設定藉由 appall這個方法可以先將路由雛形建立再接下來設定 appget的路徑格式只要符合格式就會分配進入對應的方法中像上面的程式當中如果使用者輸入路徑為user0除了執行 appall程式之後執行 next方法就會對應到路徑設定為userid的這個方法當中如果使用者輸入路徑為user0edit就會執行到useridedit的對應方法

66 Express GET應用範例

我們準備一個使用 GET方法傳送資料的表單

ltDOCTYPE htmlgt

lthtmlgt

ltheadgt

ltmeta http-equiv=rdquoContent-Typerdquo content=rdquotexthtml charset=utf8rdquogt

lttitlegtNodejs 菜鳥筆記 (1)lttitlegt

ltlink rel=rdquostylesheetrdquo href=rdquocssstylecssrdquo type=rdquotextcssrdquo media=rdquoallrdquo gt

ltheadgt

ltbodygt

lth1gtNodejs 菜鳥筆記-註冊lth1gt

ltform id=rdquosignuprdquo method=rdquoGETrdquo action=rdquohttplocalhost3000Signuprdquogt

ltlabelgt 使用者名稱ltlabelgtltinput type=rdquotextrdquo id=rdquousernamerdquo name=rdquousernamerdquo gtltbrgt

ltlabelgt 電子郵件ltlabelgtltinput type=rdquotextrdquo id=rdquoemailrdquo name=rdquoemailrdquo gtltbrgt

ltinput type=rdquosubmitrdquo value=rdquo 註冊我的帳號rdquo gtltbrgt

ltformgt

ltbodygt

lthtmlgt

66 Express GET應用範例 57

Nodejs中文電子書

這個表單沒有什麼特別的地方我們只需要看第 9 行form 使用的method是 GET然後 action是rdquohttplocalhost3000Signupldquo等一下我們要來撰寫Signup這個 URL Path的處理程式

處理 Signup行為

我們知道所謂的 GET方法會透過 URL來把表單的值給帶過去以上面的表單來說到時候 URL會以這樣的形式傳遞

httplocalhost3000Signupusername=xxxampemail=xxx

所以要能處理這樣的資料必須有以下功能

bull 解析 URL

bull 辨別動作是 Signup

bull 解析出 username和 email

一旦能取得 username和 email的值程式就能加以應用了

處理 Signup的程式碼雛形

load module

var url = require(rsquourlrsquo)

urlData = urlparse(requrltrue)

action = urlDatapathname

reswriteHead(200 rdquoContent-Typerdquordquotexthtml charset=utf-8rdquo)

if (action === rdquoSignuprdquo)

user = urlDataquery

resend(rdquolth1gtrdquo + userusername + rdquo 歡迎您的加入 lth1gtltpgt 我們已經將會員啟用信寄至rdquo + useremail + rdquoltpgtrdquo)

首先需要加載 url module它是用來協助我們解析 URL的模組接著使用 urlparse方法第一個傳入 url字串變數也就是 requrl另外第二個參數的用意是設為 ture則引進 querystring模組來協助處理預設是 false它影響到的是 urlDataquery設為 true會傳回物件不然就只是一般的字串urlparse會將字串內容整理成一個物件我們把它指定給 urlData

action變數作為記錄 pathname這是我們稍後要來判斷目前網頁的動作是什麼接著先將 html表頭資訊 (Header)準備好再來判斷路徑邏輯如

58 6 Express介紹

Nodejs中文電子書

果是 Signup這個動作就把 urlDataquery裡的資料指定給 user然後輸出userusername和 useremail把使用者從表單註冊的資料顯示於頁面中

最後進行程式測試啟動 nodejs主程式之後開啟瀏覽器就會看到表單填寫完畢按下送出就可以看到結果了

完整 nodejs程式碼如下

var http = require(rsquohttprsquo)

url = require(rsquourlrsquo)

fs = require(rdquofsrdquo)

server

server = httpcreateServer(function (reqres)

var urlData

encode = rdquoutf8rdquo

filePath = rdquoviewexpress_get_example_formhtmlrdquo

action

urlData = urlparse(requrltrue)

action = urlDatapathname

reswriteHead(200 rdquoContent-Typerdquordquotexthtml charset=utf-8rdquo)

if (action === rdquoSignuprdquo)

user = urlDataquery

resend(rdquolth1gtrdquo + userusername + rdquo 歡迎您的加入 lth1gtltpgt 我們已經將會員啟用信寄至rdquo + useremail + rdquoltpgtrdquo)

else

fsreadFile(filePath encode function(err file)

reswrite(file)

resend()

)

)

serverlisten(3000)

consolelog(rsquoServer 跑起來了現在時間是rsquo + new Date())

66 Express GET應用範例 59

Nodejs中文電子書

67 Express POST應用範例

一開始準備基本的 html表單傳送內容以 POST方式form的 action屬性設定為 POST其餘 html內容與前一個範例應用相同

ltDOCTYPE htmlgt

lthtmlgt

ltheadgt

ltmeta http-equiv=rdquoContent-Typerdquo content=rdquotexthtml charset=utf8rdquogt

lttitlegtNodejs 菜鳥筆記 (1)lttitlegt

ltlink rel=rdquostylesheetrdquo href=rdquocssstylecssrdquo type=rdquotextcssrdquo media=rdquoallrdquo gt

ltheadgt

ltbodygt

lth1gtNodejs 菜鳥筆記-註冊lth1gt

ltform id=rdquosignuprdquo method=rdquoPOSTrdquo action=rdquohttplocalhost3000Signuprdquogt

ltlabelgt 使用者名稱ltlabelgtltinput type=rdquotextrdquo id=rdquousernamerdquo name=rdquousernamerdquo gtltbrgt

ltlabelgt 電子郵件ltlabelgtltinput type=rdquotextrdquo id=rdquoemailrdquo name=rdquoemailrdquo gtltbrgt

ltinput type=rdquosubmitrdquo value=rdquo 註冊我的帳號rdquo gtltbrgt

ltformgt

ltbodygt

lthtmlgt

nodejs的程式處理邏輯與前面 GET範例類似部分程式碼如下

qs = require(rsquoquerystringrsquo)

if (action === rdquoSignuprdquo)

formData = rsquorsquo

reqon(rdquodatardquo function (data)

formData += data

)

reqon(rdquoendrdquo function ()

user = qsparse(formData)

resend(rdquolth1gtrdquo + userusername + rdquo 歡迎您的加入 lth1gtltpgt 我們已經將會員啟用信寄至rdquo + useremail + rdquoltpgtrdquo)

)

60 6 Express介紹

Nodejs中文電子書

主要加入了rsquoquerystringrsquo這個moduel方便我們等一下解析由表單 POST回來的資料另外加入一個 formData的變數用來搜集待等一下表單回傳的資料前面的 GET範例我們只從 req拿出 url的資料這次要在利用req身上的事件處理

JavaScript 在訂閱事件時使用 addEventListener而 nodejs 使用的則是on這邊加上了監聽 data的事件會在瀏覽器傳送資料到Web Server時被執行參數是它所接收到的資料型態是字串

接著再增加 end的事件當瀏覽器的請求事件結束時它就會動作

由於瀏覽器使用 POST在上傳資料時會將資料一塊塊地上傳因為我們在監聽 data事件時透過 formData變數將它累加起來lt不過由於我們

上傳的資料很少一次就結束不過如果日後需要傳的是資料比較大的檔

案這個累加動作就很重要

當資料傳完就進到 end 事件中會用到 qsparse 來解析 formDataformData的內容是字串內容是

username=wordsmithampemail=wordsmith40somewhere

而 qsparse可以幫我們把這個 querystring轉成物件的格式也就是

username=wordsmithampemail=wordsmith40somewhere

一旦轉成物件並指定給 user之後其他的事情就和 GET方法時操作的一樣寫 response的表頭將內容回傳並將 userusername和 useremail代入到內容中

修改完成後接著執行 nodejs程式啟動 web server開啟瀏覽器進入表單測試看看POST的方式能否順利運作

完整程式碼如下

var http = require(rsquohttprsquo)

url = require(rsquourlrsquo)

fs = require(rdquofsrdquo)

qs = require(rsquoquerystringrsquo)

server

server = httpcreateServer(function (reqres)

var urlData

67 Express POST應用範例 61

Nodejs中文電子書

encode = rdquoutf8rdquo

filePath = rdquoviewexpress_post_example_formhtmlrdquo

formData

action

urlData = urlparse(requrltrue)

action = urlDatapathname

reswriteHead(200 rdquoContent-Typerdquordquotexthtml charset=utf-8rdquo)

if (action === rdquoSignuprdquo)

formData = rsquorsquo

reqon(rdquodatardquo function (data)

formData += data

)

reqon(rdquoendrdquo function ()

user = qsparse(formData)

resend(rdquolth1gtrdquo + userusername + rdquo 歡迎您的加入 lth1gtltpgt 我們已經將會員啟用信寄至rdquo + useremail + rdquoltpgtrdquo)

)

else

fsreadFile(filePath encode function(err file)

reswrite(file)

resend()

)

)

serverlisten(3000)

consolelog(rsquoServer 跑起來了現在時間是rsquo + new Date())

62 6 Express介紹

Nodejs中文電子書

68 Express AJAX應用範例

在 Nodejs要使用 Ajax傳送資料並且與之互動在接受資料的部份沒有太大的差別client端不是用 GET就是用 POST來傳資料重點在處理完後用 JSON格式回傳當然 Ajax不見得只傳 JSON格式有時是回傳一段 HTML碼不過後者對伺服器來說基本上就和前兩篇沒有差別了所以我們還是以回傳 JSON做為這一回的主題

這一回其實大多數的工作都會落在前端 Ajax上面前端要負責發送與接收資料並在接收資料後撤掉原先發送資料的表單並將取得的資料

改成 HTML格式之後放上頁面

首先先準備 HTML靜態頁面資料

ltDOCTYPE htmlgt

lthtmlgt

ltheadgt

lttitlegtNodejs 菜鳥筆記 (3)lttitlegt

ltlink rel=rdquostylesheetrdquo href=rdquocssstylecssrdquo type=rdquotextcssrdquo media=rdquoallrdquo gt

ltscript src=rdquohttpsajaxgoogleapiscomajaxlibsjquery171jqueryminjsrdquogtltscriptgt

ltheadgt

ltbodygt

lth1gtNodejs 菜鳥筆記-註冊 (用 Ajax)lth1gt

ltform id=rdquosignuprdquo action=rdquoSignuprdquo method=rdquoPOSTrdquogt

ltlabelgt 使用者名稱ltlabelgtltinput type=rdquotextrdquo id=rdquousernamerdquo name=rdquousernamerdquo gtltbrgt

ltlabelgt 電子郵件ltlabelgtltinput type=rdquotextrdquo id=rdquoemailrdquo name=rdquoemailrdquo gtltbrgt

ltinput type=rdquosubmitrdquo value=rdquo 註冊我的帳號rdquo gtltbrgt

ltformgt

ltdiv id=rdquoresultrdquogt

ltdivgt

ltscript type=rdquotextjavascriptrdquogt

$(function()$(rsquosignup input[type=rdquosubmitrdquo]rsquo)click(function(e)

var user =

userusername = $(rdquousernamerdquo)val()useremail = $(rdquoemailrdquo)val()

$post(rdquoSignuprdquouserfunction(data)greet(data)

)

68 Express AJAX應用範例 63

Nodejs中文電子書

epreventDefault()

)

var greet = function(msg)

var resultNode = $(rsquoresultrsquo)greeting = $(rsquolth2gtHirsquo+msgusername+rsquo 你的會員 id 是rsquo+ msgid +rsquo 我們會將會員啟動信寄至rsquo+msgemail+rsquolth2gtrsquo)

$(rsquosignuprsquo)hide()resultNodehtml(rdquordquo)

resultNodeappend(greeting)

)

ltscriptgt

ltbodygt

lthtmlgt

HTML頁面上準備了一個表單用來傳送註冊資料接著直接引用了Google CDN來載入 jQuery用來幫我們處理 Ajax的工作這次要傳送和接收的工作很大的變動都在 HTML頁面上的 JavaScript當中我們要做的事有 (相關 jQuery處理這邊不多做贅述指提起主要功能解說)

bull 用 jQUery取得 submit按鈕綁定它的 click動作

bull 取得表單 username和 email的值存放在 user這個物件中

bull 用 jQuery的 $post方法將 user的資料傳到 Server

bull 一旦成功取得資料後透過 greet這個 function組成回報給 user的訊息

bull 清空原本給使用者填資料的表單

bull 將 Server回傳的 usernameemail和 id這 3個資料組成回應的訊息

bull 將訊息放到原本表單的位置

經過以上的處理後一個 Ajax的表單的基本功能已經完成

接著進行 nodejs主要程式的編輯部分程式碼如下

var fs = require(rdquofsrdquo)

64 6 Express介紹

Nodejs中文電子書

qs = require(rsquoquerystringrsquo)

if (action === rdquoSignuprdquo)

formData = rsquorsquo

reqon(rdquodatardquo function (data)

formData += data

)

reqon(rdquoendrdquo function ()

var msg

user = qsparse(formData)

userid = rdquo123456rdquo

msg = JSONstringify(user)

reswriteHead(200 rdquoContent-Typerdquordquoapplicationjson charset=utf-8rdquordquoContent-Lengthrdquomsglength)

resend(msg)

)

這裡的程式和前面 POST範例基本上大同小異差別在

bull 幫 user的資料加上 id隨意存放一些文字進去讓 Server回傳的資料多於 Client端傳上來的不然會覺得 Server都沒做事

bull 增加了 msg 這個變數存放將 user 物件 JSON 文字化的結果JSONstringify這個轉換函式是 V8引擎所提供的如果你好奇的話

bull 大重點來了我們要告訴 Client端這次回傳的資料格式是 JSON所在 Content-type和 Content-Length要提供給 Client

Server很輕鬆就完成任務了最後進行程式測試啟動 nodejs主程式之後開啟瀏覽器就會看到表單填寫完畢按下送出就可以看到結果了

最後 nodejs本篇範例程式碼如下

var http = require(rsquohttprsquo)

url = require(rsquourlrsquo)

fs = require(rdquofsrdquo)

qs = require(rsquoquerystringrsquo)

server

68 Express AJAX應用範例 65

Nodejs中文電子書

server = httpcreateServer(function (reqres)

var urlData

encode = rdquoutf8rdquo

filePath = rdquoviewexpress_ajax_example_formhtmlrdquo

formData

action

urlData = urlparse(requrltrue)

action = urlDatapathname

if (action === rdquoSignuprdquo)

formData = rsquorsquo

reqon(rdquodatardquo function (data)

formData += data

)

reqon(rdquoendrdquo function ()

var msg

user = qsparse(formData)

userid = rdquo123456rdquo

msg = JSONstringify(user)

reswriteHead(200 rdquoContent-TyperdquordquoapplicationjsonrdquordquoContent-Lengthrdquomsglength)

resend(msg)

)

else

fsreadFile(filePath encode function(err file)

reswriteHead(200 rdquoContent-Typerdquordquotexthtml charset=utf-8rdquo)

reswrite(file)

resend()

)

)

serverlisten(3000)

66 6 Express介紹

Nodejs中文電子書

consolelog(rsquoServer 跑起來了現在時間是rsquo + new Date())

69 原始資料提供

bull [NodeJS初學者筆記 (1)-用 GET傳送資料] (httpithelpithomecomtwquestion10087402)

bull [NodeJS初學者筆記 (2)-用 POST傳送資料] (httpithelpithomecomtwquestion10087489)

bull [NodeJS 初學者筆記 (3)-用 Ajax 傳送資料] (httpithelpithomecomtwquestion10087627)

69 原始資料提供 67

Nodejs中文電子書

68 6 Express介紹

7

CoffeeScript

bull Spine

bull Hem

bull Spineapp

Letrsquos Have a Cup of CoffeeScript

bull es5-shim ECMAScript 5 compatibility shims for legacy JavaScript engines

ESnext

node-iform - A middleware for node-validator httpsgithubcomguileennode-iform

69

Nodejs中文電子書

70 7 CoffeeScript

8

製作一個 Hubot的 Plurk Adapter

81 應用事項提醒

此應用範例以CoffeeScript編寫主要程式架構採用Github釋放的Hubot專案以下有幾個主要注意事項請讀者注意

bull Hubot 一開始的架構除了內建的兩個 Adapter 之外其餘都要以Nodejs的Module方式才能運作

bull Hubot的 binhubot裡面寫著 npm install所以不管你怎麼改原始碼也不能改變第一點的狀況

其實上述都在討論同一件事情NodeJS的模組而且模組不能設定相對路徑之類的來安裝一定要包裝成 npm相符和格式才有辦法正確執行

82 建立 Adapter

首先需要準備兩個檔案

bull packagejson

bull plurkcoffee

71

Nodejs中文電子書

有這兩個就足以變成 NodeJS的模組了在開始 Coding之前先來設定一下 packagejson相依性

rdquonamerdquo rdquohubot-plurkrdquo

rdquoversionrdquo rdquo011rdquo

rdquomainrdquo rdquoplurkrdquo

rdquodependenciesrdquo

rdquohubotrdquo rdquogt=205rdquo

rdquooauthrdquo rdquordquo

rdquocronrdquordquordquo

這邊會使用到NodeJS的 cron模組(Module)而登入 Plurk需要OAuth標準授權才行所以也需要加載 OAuth模組

注意Hubot在讀取非內建模組時會自動在前面加上 hubot-的前置

83 建立 Robot跟 API

本篇應用基本上程式碼大部分參考 Hubot - Twitter Adapter來製作主要差異只有在使用 OAuth這個模組Twitter有 Streaming可用而 Plurk則得用 Comet方式來達到即時讀取

plurkcoffee程式碼

Robot = require(rdquohubotrdquo)robot()

Adapter = require(rdquohubtordquo)adapter()

EventEmitter = require(rdquoeventsrdquo)EventEmitter

oauth = require(rdquooauthrdquo)

cronJob = require(rdquocronrdquo)CronJob

class Plurk exntends Adapter

class PlurkStreaming exnteds EventEmitter

72 8 製作一個 Hubot的 Plurk Adapter

Nodejs中文電子書

先弄個基本架構主要 Class皆參考 Twitter Adapter命名方式接著再將 Plurk這個 Class增加幾個Method基本上只要有 run send reply就夠了而 run用來做初始化的部分

class Plurk entends Adapter

send (plurk id strings⋯) -gt

reply (plurk id strings⋯) -gt

run -gt

看起來有點東西接著來處理主要的 Plurk API結合部分

class PlurkStreaming extends EventEmitter

consuctor (options) -gt

plurk (callback) -gt

觀察河道

getChannel -gt

取得 Comet 網址

reply (plurk id message) -gt

回噗

acceptFriends -gt

接受好友

get (path callback) -gt

GET 請求

post (path body callback)-gt

POST 請求(其實是裝飾)

request (method path body callback)-gt

主要的 OAuth 請求

comet (server callback)-gt

噗浪的 Comet 傳回是 JavaScript Callback 要另外處理後才會變成 JSON

然後把注意力集中到 constructor上先把建構子弄好

constructor (options) -gt

super()

if optionskey and optionssecret and optionstoken and optionstoken secret

key = optionskey

secret = optionssecret

token = optionstoken

token secret = optionstoken secret

83 建立 Robot跟 API 73

Nodejs中文電子書

建立 OAuth 連接

consumer = new oauthOAuth(

rdquohttpwwwplurkcomOAuthrequest tokenrdquo

rdquohttpwwwplurkcomOAuthaccess tokenrdquo

key

secret

rdquo10rdquo

rdquohttpwwwplurkcomOAuthauthorizerdquo

rdquoHMAC-SHA1rdquo

)

domain = rdquowwwplurkcomrdquo

初始化取得 Comet 網址

do getChannel

else

throw new Error(rdquo 參數不足需要 Key Secret Token Token Secretrdquo)

接著來處理 request這個 method

request (method path body callback) -gt

記錄一下這次的 Request

consolelog(rdquohttpdomainpathrdquo)

Callback 這邊先不丟進去要用另一種方式處理

request = consumerget(rdquohttpdomainpathrdquo token token secret null)

requeston rdquoresponserdquo (res) -gt

reson rdquodatardquo (chunk) -gt

parseResponse(chunk+rsquorsquo callback)

reson rdquoendrdquo (data) -gt

consolelog rdquoEnd Request pathrdquo

reson rdquoerrorrdquo (data) -gt

consolelog rdquoError rdquo + data

requestend()

處理資料

parseResponse = (data callback) -gt

if datalength gt 0

用 TryCatch 避免處理 JSON 出錯導致整個中斷

try

callback null JSONparse(data)

catch err

74 8 製作一個 Hubot的 Plurk Adapter

Nodejs中文電子書

consolelog(rdquoError Parse JSONrdquo + data err)

繼續執行

callback null data ||

大致上就是這樣上面程式的架構已經將整個 Hubot Plurk Adapter完成因為在測試時竟然因為噗浪 Lag而沒讀到完整的 Comet資料然後造成程式異常為了避免這個問題發生因此需要加上為了完美呈現需要再

加上 Comet的處理所以要使用到 EventEmitter的功能

comet (server callback) -gt

在 Callback 裡面會找不到自身所以設定區域變數

self =

記錄一下這次的 Request

consolelog(rdquo[Comet] serverrdquo)

Callback 這邊先不丟進去要用另一種方式處理

request = consumerget(rdquohttpdomainpathrdquo token token secret null)

requeston rdquoresponserdquo (res) -gt

reson rdquodatardquo (chunk) -gt

parseResponse(chunk+rsquorsquo callback)

reson rdquoendrdquo (data) -gt

consolelog rdquoEnd Request pathrdquo

請求結束發出事件通知可以進行下一次請求

selfemit rdquonextPlurkrdquo

reson rdquoerrorrdquo (data) -gt

consolelog rdquoError rdquo + data

requestend()

處理資料

parseResponse = (data callback) -gt

if datalength gt 0

用 trycatch 避免失敗中斷

try

去掉 JavaScript 的 Callback

data = datamatch(CometChannelscriptCallback((+))s)

jsonData = rdquordquo

if data

83 建立 Robot跟 API 75

Nodejs中文電子書

jsonData = JSONparse(data[1])

else

如果沒有任何 Match 嘗試直接 parse

jsonData = JSONparse(data)

catch err

consolelog(rdquo[Comet] Errorrdquo data err)

用 TryCatch 避免處理 JSON 出錯導致整個中斷

try

只傳入 json 的 data 部分

callback null jsonDatadata

catch err

consolelog(rdquo[Comet]Error Parse JSONrdquo + data err)

繼續執行

callback null data ||

後面的 get跟 post就簡單多了

get (path callback) -gt

request(rdquoGETrdquo path null callback)

post (path body callback) -gt

request(rdquoPOSTrdquo path body callback)

接著處理取的 Comet網址的 getChannel

getChannel -gt

self =

get rdquoAPPRealtimegetUserChannelrdquo (error data) -gt

if error

檢查是否有 comet server

if datacomet server

selfchannel = datacomet server

如果沒有 Channel Ready 就嘗試連接會失敗

selfemit(rsquochannel readyrsquo)

那麼先來處理 Plurk Adaper好處理的部份

send (plruk id strings⋯)-gt

跟 Reply 一樣直接交給 reply 做

reply plurk id strings⋯

76 8 製作一個 Hubot的 Plurk Adapter

Nodejs中文電子書

reply (plurk id strings⋯) -gt

stringsforEach (message) =gt

botreply(plruk id message)

接著把 run處理好就可以上線運作摟

run -gt

self =

options =

key processenvHUBOT PLURK KEY

secret processenvHUBOT PLURK SECRET

token processenvHUBOT PLURK TOKEN

token secret processenvHUBOT PLURK TOKEN SECRET

創建剛剛的 API

bot = new PlurkStreaming(options)

依照 Twitter 的 new RobotTextMessage 會沒有反應所以參考 hubot-minecraft 的方式

r = robotconstructor

處理噗浪河道訊息

doPlurk = (data)-gt

檢查是否為回噗

if dataresponse

datacontent raw = dataresponsecontent raw

datauser id = dataresponseuser id

確定有噗浪 ID 跟訊息

if dataplurk id and datacontent raw

selfreceive new rTextMessage(dataplurk id datacontent raw)

取得 Comet Server 完成開始第一次 Comet 連接

boton rdquochannel readyrdquo () -gt

botplurk selfdoPlurk

上一次 Comet 完成繼續 Polling

boton rdquonextPlurkrdquo ()-gt

botplurk selfdoPlurk

定時接受好友邀請

do botacceptFriends

bot = bot

83 建立 Robot跟 API 77

Nodejs中文電子書

終於完成 AdapterHubot專案裡面的 scripts資料夾內是互動部分不需要像 Adapter如此大費周章處理只新增檔案並且設計好對白之後就會回噗了我開發用的機器人在此大家可以去跟他玩玩[httpplurkcomelct9620 bot](httpplurkcomelct9620 bot)

84 原始資料提供

bull [製 作 一 個 Hubot 的 噗 浪 Adapter](http revo-skillfrosttw blog20120318create-a-hubot-plurk-adapter)

78 8 製作一個 Hubot的 Plurk Adapter

9

Nodejs好用工具介紹

91 logio

Real-time log monitoring in your browser

httplogioorg

nodejs (gt=0412 lt=0611)

npm config set unsafe-perm true

npm install -g --prefix=usrlocal logio

logio server start

httplocalhost8998

79

Nodejs中文電子書

80 9 Nodejs好用工具介紹

10

精選文章

101 Express

bull 使用 NodeJS + Express從 GETPOST Request取值 [邀稿中]

bull link2 [邀稿中]

bull link3 [邀稿中]

81

Nodejs中文電子書

82 10 精選文章

11

參考資源

111 Nodejs書籍

bull Programming Nodejs April 2012 (est) Travis Swicegood Pragmatic Bookshelf

bull e Little Book on CoffeeScript January 2012 Alex MacCaw OrsquoReilly Media

bull Node for Front-End Developers January 2012 Garann Means OrsquoReilly Media

bull Building Hypermedia APIs with HTML5 and Node November 2011 MikeAmundsen OrsquoReilly Media

bull Node Web Development August 2011 David Herron PacktPub

bull CoffeeScript Accelerated JavaScript Development July 2011 Trevor BurnhamPragmatic Bookshelf

bull Getting Started with GEO CouchDB and Nodejs July 2011 Mick omp-son OrsquoReilly Media

bull What Is Node July 2011 Brett McLaughlin OrsquoReilly Media

bull Node Up and Running May 2011 Tom Hughes-Croucher Mike WilsonOrsquoReilly Media

83

Nodejs中文電子書

bull Hands-on Nodejs e Nodejs introduction and API reference May 2011 Pe-dro Teixeira LeanPub

bull e Node Beginner Book Manuel Kiessling

bull Mastering Nodejs visionmedia

112 Nodejs影音教學

bull e Node Sessions e Best of OSCON 2011 August 2011 OrsquoReilly Media(Video)

bull Tom Hughes-Croucher on Node March 2011 OrsquoReilly Media (Video)

113 Nodejs教學網站

bull Node Tuts

114 Nodejs課程

bull Introduction To Nodejs Van Nguyen CodeLesson

84 11 參考資源

Page 19: Nodejs Wiki

Nodejs中文電子書

var local_variable1 =2arg2 及 local_variable1 對 inner 函數來說都是本地變數

return arg1 + arg2 + free_variable + local_variable1

var a = outter(1)變數 a就是 outter函數執行後返回的 inner函數 var b =a(4)執行 inner函數執行時上下文已經在 outter函數之外但是仍然能正常執行而且可以使用定義在 outter函數裡面的 arg1及 free variable1變數 consolelog(b)結果 10

在 Javascript中scope最主要的單位是函數(另外有 global及 eval)所以有可能製造出 closure的狀況通常在形式上都是有巢狀的函數定義而且內側的函數使用到定義在外側函數裡面的變數

Closure有可能會造成記憶體洩漏主要是因為被參考的變數無法被垃圾收集機制處理造成佔用的資源無法釋放所以使用上必須考慮清楚

不要造成意外的記憶體洩漏(在上面的例子中如果 a一直未執行使用到的記憶體就不會被釋放)

跟透過函數的參數把變數傳給函數比較起來Javascript Engine會比較難對 Closure進行最佳化如果有效能上的考量這一點也需要注意

23 Callback

要介紹 Callback之前要先提到 JavaScript的特色

JavaScript是一種函數式語言(functional language)所有 Javascript語言內的函數都是高階函數 (higher order function這是數學名詞計算機用語好像是 rst class function意指函數使用沒有任何限制與其他物件一樣)也就是說函數可以作為函數的參數傳給函數也可以當作函數的返回值這個特性讓 Javascript的函數使用上非常有彈性而且功能強大

callback在形式上其實就是把函數傳給函數然後在適當的時機呼叫傳入的函數Javascript使用的事件系統通常就是使用這種形式NodeJS中有一個物件叫做 EventEmitter這是 NodeJS事件處理的核心物件所有會使用事件處理的函數都會「繼承」這個物件(這裡說的繼承實

作上應該像是 mixin)他的使用很簡單可以使用物件on(事件名稱callback

23 Callback 13

Nodejs中文電子書

函數) 或是物件addListener(事件名稱callback 函數) 把你想要處理事件的函數傳入在物件中可以使用物件emit(事件名稱 參數) 呼叫傳入的callback函數這是 Observer Pattern的簡單實作而且跟在網頁中使用 DOM的 addEventListener使用上很類似也很容易上手不過 NodeJS是大量使用非同步方式執行的應用所以程式邏輯幾乎都是寫在 callback函數中當邏輯比較複雜時大量的 callback會讓程式看起來很複雜也比較難單元測試舉例來說

var p client = new Db(lsquointegration tests 20rsquo new Server(ldquo127001rdquo 27017) lsquopkrsquoCustomPKFactory) p clientopen(function(err p client)

p clientdropDatabase(function(err done)

p clientcreateCollection(lsquotest custom keyrsquo function(err collection)

collectioninsert(lsquoarsquo1 function(err docs)

collection nd(lsquo idrsquonew ObjectID(ldquoaaaaaaaaaaaardquo) function(err cursor)

cursortoArray(function(err items) testassertEquals(1 itemslength) p clientclose()

)

)

)

)

)

)

這是在網路上看到的一段操作 mongodb的程式碼為了循序操作所以必須在一個 callback裡面呼叫下一個動作要使用的函數這個函數裡面還是會使用 callback最後就形成一個非常深的巢狀

這樣的程式碼會比較難進行單元測試有一個簡單的解決方式是

盡量不要使用匿名函數來當作 callback或是 event handler透過這樣的方式

14 2 JavaScript與 NodeJS

Nodejs中文電子書

就可以對各個 handler做單元測試了例如

var http = require(lsquohttprsquo) var tools =

cookieParser function(request response) if(requestheaders[rsquoCookiersquo]) do parsing

var server = httpcreateServer(function(request response)

thisemit(lsquoinitrsquo request response)

) serveron(lsquoinitrsquo toolscookieParser) serverlisten(8080 lsquo127001rsquo)

更進一步可以把 tools改成外部 module例如叫做 toolsjs

moduleexports = cookieParser function(request response) if(requestheaders[rsquoCookiersquo]) do parsing

然後把程式改成

var http = require(lsquohttprsquo)

var server = httpcreateServer(function(request response) thisemit(lsquoinitrsquo re-quest response)

) serveron(lsquoinitrsquo require(lsquo toolsrsquo)cookieParser) serverlisten(8080lsquo127001rsquo)

這樣就可以單元測試 cookieParser了例如使用 nodeunit時可以這樣寫

var testCase = require(lsquonodeunitrsquo)testCase moduleexports = testCase(

ldquosetUprdquo function(cb) thisrequest = headers Cookielsquoname1val1 name2val2rsquo thisresponse = thisresult= name1rsquoval1rsquoname2rsquoval2rsquo

cb()

ldquotearDownrdquo function(cb)

cb()

23 Callback 15

Nodejs中文電子書

ldquonormal caserdquo function(test)

testexpect(1) var obj = require(lsquotoolsrsquo)cookieParser(thisrequest thisresponse)testdeepEqual(obj thisresult) testdone()

)

善於利用模組可以讓程式更好維護與測試

24 CPS(Continuation-Passing Style)

cps 是 callback 使用上的特例形式上就是在函數最後呼叫 callback這樣就好像把函數執行後把結果交給 callback 繼續運行所以稱作continuation-passing style利用 cps可以在非同步執行的情況下透過傳給 callback的這個 cps callback來獲知 callback執行完畢或是取得執行結果例如

lthtmlgt ltbodygt ltdiv id=rdquopanelrdquo style=rdquovisibilityhiddenrdquogtlt divgtlt bodygt lt htmlgt ltscriptgt var request = new XMLHttpRequest() re-questopen(lsquoGETrsquo lsquotest749txt timestamp=rsquo+new Date()getTime() true) re-questaddEventListener(lsquoreadystatechangersquo function(next)

return function() if(thisreadyState===4ampampthisstatus===200) next(thisresponseText)lt== 傳入的 cps callback 在動作完成時執行並取得結果進一步處理

(function(str)lt==這個匿名函數就是 cps callback

documentgetElementById(lsquopanelrsquo)innerHTML=str docu-mentgetElementById(lsquopanelrsquo)stylevisibility = lsquovisiblersquo

) false) requestsend() ltscriptgt

進一步的應用也可以參考 2-6流程控制

16 2 JavaScript與 NodeJS

Nodejs中文電子書

25 函數返回函數與 Currying

前面的 cps範例裡面使用了函數返回函數這是為了把 cps callback傳遞給 onreadystatechange事件處理函數的方法(因為這個事件處理函數並沒有設計好會傳送接收這樣的參數)實際會執行的事件處理函數其實是內層返回的那個函數之外包覆的這個函數主要是為了利用 Closure把 next傳給內層的事件處理函數這個方法更常使用的地方是為了解決一些

scope問題例如

ltscriptgt var accu=0count=10 for(var i=0 iltcount i++)

setTimeout(

function() countndash accu+=i if(countlt=0)

consolelog(accu)

50)

ltscriptgt

最後得出的結果會是 100而不是想像中的 45這是因為等到 setTime-out指定的函數執行時變數 i已經變成 10而離開迴圈了要解決這個問題就需要透過 Closure來保存變數 i

ltscriptgt var accu=0count=10 for(var i=0 iltcount i++)

setTimeout(

function(i) return function() countndash

accu+=i if(countlt=0)

consolelog(accu)

(i)

50)

25 函數返回函數與 Currying 17

Nodejs中文電子書

淺藍色底色的部份是跟上面例子不一樣的地方 ltscriptgt

函數返回函數的另外一個用途是可以暫緩函數執行例如

function add(m n) return m+n

var a = add(20 10) consolelog(a)

add這個函數必須同時輸入兩個參數才有辦法執行如果我希望這個函數可以先給它一個參數等一些處理過後再給一個參數然後得到結

果就必須用函數返回函數的方式做修改

function add(m)

return function(n) return m+n

var wait another arg = add(20)先給一個參數 var a = function(arr)

var ret=0 for(var i=0iltarrlengthi++) ret+=arr[i] return ret

([1234])計算一下另一個參數 var b = wait another arg(a)然後再繼續執行 consolelog(b)

像這樣利用函數返回函數使得原本接受多個參數的函數可以一次

接受一個參數直到參數接收完成才執行得到結果的方式有一個學名就

叫做Currying

綜合以上許多奇技淫巧就可以透過用函數來處理函數的方式調整

程式流程接下來看看

26 流程控制

(以 sync方式使用 async函數避開巢狀 callback循序呼叫 async callback等奇技淫巧)

建議參考

bull httphowtonodeorgcontrol- ow

bull httphowtonodeorgcontrol- ow-part-ii

18 2 JavaScript與 NodeJS

Nodejs中文電子書

bull httphowtonodeorgcontrol- ow-part-iii

bull httpblogmixunet20110202essential-node-js-patterns-and-snippets

這幾篇都是非常經典的 NodeJSJavascript流程控制好文章(阿mixu是在介紹一些 pattern時提到這方面的主題)不過我還是用幾個簡單的程式介紹一下做法跟概念

並發與等待

下面的程式參考了 mixu文章中的做法

var wait = function(callbacks done) consolelog(lsquowait startrsquo) var counter = call-backslength var results = [] var next = function(result) 接收函數執行結果並判斷是否結束執行 resultspush(result) if(ndashcounter == 0) done(results)如果結束執行就把所有執行結果傳給指定的 callback處理 for(var i = 0 i lt callbackslength i++) 依次呼叫所有要執行的函數 callbacks[i](next) consolelog(lsquowait endrsquo)

wait( [ function(next) setTimeout(function() consolelog(lsquodone arsquo) var result =500 next(result) 500) function(next) setTimeout(function() con-solelog(lsquodone brsquo) var result = 1000 next(result) 1000) function(next)setTimeout(function() consolelog(lsquodone crsquo) var result = 1500 next(1500) 1500) ] function(results) var ret = 0 i=0 for( iltresultslength i++) ret+= results[i] consolelog(lsquodone all result lsquo+ret)

)

執行結果wait start wait end done a done b done c done all result 3000

可以看出來其實 wait並不是真的等到所有函數執行完才結束執行而是在所有傳給他的函數執行完畢後(不論同步非同步)才執行處理結

果的函數(也就是 done())

不過這樣的寫法還不夠實用因為沒辦法實際讓函數可以等待執行

完畢又能當作事件處理函數來實際使用上面參考到的 Tim Caswell的文

26 流程控制 19

Nodejs中文電子書

章裡面有一種解法不過還需要額外包裝(在他的例子中)NodeJS核心的 fs物件把一些函數(例如 readFile)用 Currying處理類似像這樣

var fs = require(lsquofsrsquo) var readFile = function(path)

return function(callback errback)

fsreadFile(path function(err data)

if(err) errback()

else callback(data)

)

其他部份可以參考 Tim Caswell的文章他的 Doparallel跟上面的 wait差不多意思這裡只提示一下他沒說到的地方

另外一種做法是去修飾一下 callback當他作為事件處理函數執行後再用 cps的方式取得結果

ltscriptgt function Wait(fns done)

var count = 0 var results = [] thisgetCallback = function(index)

count++ return (function(waitback)

return function() var i=0args=[]for(iltargumentslengthi++)

argspush(arguments[i])

argspush(waitback) fns[index]apply(thisargs)

)(function(result) resultspush(result) if(ndashcount == 0)

20 2 JavaScript與 NodeJS

Nodejs中文電子書

done(results)

)

var a = new Wait(

[ function(waitback) consolelog(lsquodone arsquo) var result = 500 wait-back(result) function(waitback) consolelog(lsquodone brsquo) var result =1000 waitback(result) function(waitback) consolelog(lsquodone crsquo) varresult = 1500 waitback(result) ] function(results) var ret = 0 i=0for( iltresultslength i++) ret += results[i] consolelog(lsquodone allresult lsquo+ret)

) var callbacks = [agetCallback(0)agetCallback(1)agetCallback(0)agetCallback(2)]一次取出要使用的 callbacks避免結果提早送出 setTimeout(callbacks[0]500) setTimeout(callbacks[1] 1000) setTimeout(callbacks[2] 1500) setTime-out(callbacks[3] 2000) 當所有取出的 callbacks執行完畢就呼叫 done()來處理結果 ltscriptgt

執行結果

done a done b done a done c done all result 3500

上面只是一些小實驗更成熟的作品是 Tim Caswell 的 stephttpsgithubcomcreationixstep

如果希望真正使用同步的方式寫非同步則需要使用 Promisejs這一類的 library來轉換非同步函數不過他結構比較複雜 XD(見仁見智不過有些人認為 Promise有點過頭了)httpblogsmsdncombrbucktonarchive20110815promise-js-2-0-promise-framework-for-javascriptaspx

如果想不透過其他 Library做轉換又能直接用同步方式執行非同步函數大概就要使用一些需要額外 compile原始程式碼的方法了例如 BrunoJouhier的 streamlinejshttpsgithubcomSagestreamlinejs

26 流程控制 21

Nodejs中文電子書

循序執行

循序執行可以協助把非常深的巢狀 callback結構攤平例如用這樣的簡單模組來做(serialjs)

moduleexports = function(funs) var c = 0 if(isArrayOfFunctions(funs))

throw(lsquoArgument type was not matched Should be array of func-tionsrsquo)

return function()

var args = Arrayprototypeslicecall(arguments 0) if((cgt=funslength))

c++ return funs[c-1]apply(this args)

function isArrayOfFunctions(f ) if(typeof f == lsquoobjectrsquo) return false if(flength)return false if(fconcat) return false if(fsplice) return false var i = 0 for(iltflength i++)

if(typeof f[i] == lsquofunctionrsquo) return false

return true

簡單的測試範例(testSerialjs)使用 fs 模組確定某個 path 是檔案然後讀取印出檔案內容這樣會用到兩層的 callback所以測試中有使用serial的版本與 nested callbacks的版本做對照

var serial = require(lsquoserialrsquo) fs = require(lsquofsrsquo) path = lsquodclientjsrsquo cb = serial([ func-tion(err data)

if(err)

if(dataisFile) fsreadFile(path cb)

22 2 JavaScript與 NodeJS

Nodejs中文電子書

else consolelog(err)

function(err data)

if(err) consolelog(lsquo[ attened by searial]rsquo) con-solelog(datatoString(lsquoutf8rsquo))

else consolelog(err)

]) fsstat(path cb)

fsstat(path function(err data) 第一層 callback if(err)

if(dataisFile)

fsreadFile(path function(err data) 第二層 callback if(err)

consolelog(lsquo[nested callbacks]rsquo) con-solelog(datatoString(lsquoutf8rsquo))

else consolelog(err)

)

else consolelog(err)

)

關鍵在於這些 callback的執行是有順序性的所以利用 serial返回的一個函數 cb來取代這些 callback然後在 cb中控制每次會循序呼叫的函數

26 流程控制 23

Nodejs中文電子書

就可以把巢狀的 callback攤平成循序的 function陣列(就是傳給 serial函數的參數)

測試中的dclientjs 是一個簡單的 dnode 測試程式放在跟 testSerialjs同一個目錄

var dnode = require(lsquodnodersquo)

dnodeconnect(8000 lsquolocalhostrsquo function(remote)

remoterestart(function(str) consolelog(str) processexit()

)

)

執行測試程式後出現結果

[ attened by searial] var dnode = require(lsquodnodersquo)

dnodeconnect(8000 lsquolocalhostrsquo function(remote)

remoterestart(function(str) consolelog(str) processexit()

)

)

[nested callbacks] var dnode = require(lsquodnodersquo)

dnodeconnect(8000 lsquolocalhostrsquo function(remote)

remoterestart(function(str) consolelog(str) processexit()

)

)

對照起來看兩種寫法的結果其實是一樣的但是利用 serialjs巢狀的 callback結構就會消失

不過這樣也只限於順序單純的狀況如果函數執行的順序比較複雜

(不只是一直線)還是需要用功能更完整的流程控制模組比較好例如

httpsgithubcomcaolanasync

24 2 JavaScript與 NodeJS

3

Nodejs安裝與設定

本篇將講解如何在各個不同 OS建立 NodeJS環境目前 NodeJS 048版本環境架設方式需依賴 Linux指令才可編譯完成當然在不同作業系統中也已經有 NodeJS package可以直接使用指令快速架設以下各不同作業系統解說如何安裝 NodeJS

31 Ubuntu Linux

更新推薦使用 nvm

git clone gitgithubcomcreationixnvmgit ~nvm

echo rdquo ~nvmnvmshrdquo gtgt ~bashrc

nvm install v0614

nvm alias default v0614

以 上 可 參 考 http dreamerslabcom blog tw how-to-setup-a-node-js-development-environment-on-ubuntu-11-04

使用 APT套件管理工具是常見的方法以下是使用社群提供的 PPA安裝方式

sudo apt-get install python-software-properties

sudo add-apt-repository ppachris-leanodejs-devel

25

Nodejs中文電子書

sudo apt-get update

sudo apt-get install nodejs

32 Other Linux

Linux很適合作為 NodeJS的伺服器作業系統及開發環境安裝前請先確認以下套件已正確安裝

bull curl (wget)用來下載檔案的工具

bull git先進的版本控制工具

bull g++ GNU C++軟體編譯工具

bull make GNU軟體專案建置工具

安裝指令如下如設有權限問題請在指令前面加上 sudo

git clone httpsgithubcomjoyentnodegit

cd node

git checkout v067

configure

make

sudo make install

接著測試 nodeJS是否正常執行

node --version

出現版本訊息即表示安裝成功

33 Windows

nodeJS 在 v060 版本之後開始正式支援 windows native直接使用nodeexe 就可以執行程式支援性完全與 linux 相同更棒的部份就是不需經過編譯經過下載之後簡單設定完成立即開發 node程式

26 3 Nodejs安裝與設定

Nodejs中文電子書

下載 nodejs安裝檔案 lthttpnodejsorgdownloadgt

如此完成 windows native nodeexe安裝接著可以進入 command line執行測試在 command line輸入rdquonoderdquo會出現 nodejs版本訊息畫面表示安裝完成

33 Windows 27

Nodejs中文電子書

28 3 Nodejs安裝與設定

4

Nodejs基礎

前篇文章已經由介紹安裝至設定都有完整介紹nodeJS 內部除了javascript常用的函式 (function)物件 (object)之外也有許多不同的自訂物件nodeJS預設建立這些物件為核心物件是為了要讓開發流程更為這些資料在官方文件已經具有許多具體說明接下來將會介紹在開發 nodeJS程式時常見的物件特性與使用方法

41 nodejs http伺服器建立

在 lsquonodejs官方網站 lthttpnodejsorggtlsquo裡面有舉一個最簡單的 HTTP伺服器建立一開始初步就是建立一個伺服器平台讓 nodejs可以與瀏覽器互相行為每種語言一開始的程式建立都是以 Hello world開始最初也從 Hello world帶各位進入 nodejs的世界

輸入以下程式碼儲存檔案為 node basic http hello worldjs

var server

ip = rdquo127001rdquo

port = 1337

http = require(rsquohttprsquo)

server = httpcreateServer(function (req res)

29

Nodejs中文電子書

reswriteHead(200 rsquoContent-Typersquo rsquotextplainrsquo)

resend(rsquoHello Worldnrsquo)

)

serverlisten(port ip)

consolelog(rdquoServer running at httprdquo + ip + rdquordquo + port)

程式碼解講一開始需要有幾個基本的變數

bull ip 機器本身的 ip位置因為使用本地端因此設定為 127001

bull port 需要開通的阜號通常設定為 http port 80因範例不希望與基本 port相衝隨意設定為 1337

在 nodejs 的程式中有許多預設的模組可以使用因此需要使用require方法將模組引入在這邊我們需要使用 http這個模組因此將 http載入Http模組裡面內建有許多方法可以使用這邊採用 createServer創建一個基本的 http伺服器再將 http伺服器給予一個 server變數裡面的回呼函式 (call back function)可以載入 http伺服器的資料與回應方法 (requestresponse)在程式裡面就可以看到我們直接回應給瀏覽器端所需的 Header回應內容

reswriteHead(200 rsquoContent-Typersquo rsquotextplainrsquo)

resend(rsquoHello Worldnrsquo)

Http伺服器需要設定 port ip在最後需要設定 Http監聽需要使用到listen事件監聽所有 Http伺服器行為

httplisten(port ip)

所有事情都完成之後需要確認伺服器正確執行因此使用 console在javascript裡就有這個原生物件console所印出的資料都會顯示於 nodejs伺服器頁面這邊印出的資料並不會傳送到使用者頁面上之後許多除壞

(debug)都會用到 console物件

consolelog(rdquoServer running at httprdquo + ip + rdquordquo + port)

30 4 Nodejs基礎

Nodejs中文電子書

42 nodejs http路徑建立

前面已經介紹如何建立一個簡單的 http伺服器接下來本章節將會介紹如何處理伺服器路徑 (rout)問題在 http的協定下所有從瀏覽器發出的要求 (request)都需要經過處理路徑上的建立也是如此

路徑就是指伺服器 ip位置或者是網域名稱之後對於伺服器給予的要求修改剛才的 hello world檔案修改如下

server = httpcreateServer(function (req res)

consolelog(requrl)

reswriteHead(200 rsquoContent-Typersquo rsquotextplainrsquo)

resend(rsquohello worldnrsquo)

)

重新啟動 nodejs程式後在瀏覽器端測試一下路徑行為結果如下圖

當在瀏覽器輸入 http1270011337test 在伺服器端會收到兩個要求一個是我們輸入的test要求另外一個則是faviconicotest的路徑要求http伺服器本身需要經過程式設定才有辦法回應給瀏覽器端所需要的回應在伺服器中所有的路徑要求都是需要被解析才有辦法取得資料從

42 nodejs http路徑建立 31

Nodejs中文電子書

上面解說可以了解到在 nodejs當中所有的路徑都需要經過設定未經過設定的路由會讓瀏覽器無法取得任何資料導致錯誤頁面的發生底下將會解

說如何設定路由同時避免發生錯誤情形先前 nodejs程式需要增加一些修改才能讓使用者透過瀏覽器在不同路徑時有不同的結果根據剛才

的程式做如下的修改

var server

ip = rdquo127001rdquo

port = 1337

http = require(rsquohttprsquo)

url = require(rsquourlrsquo)

path

server = httpcreateServer(function (req res)

path = urlparse(requrl)

reswriteHead(200 rsquoContent-Typersquo rsquotextplainrsquo)

switch (pathpathname)

case rdquoindexrdquo

resend(rsquoI am indexnrsquo)

break

case rdquotestrdquo

resend(rsquothis is test pagenrsquo)

break

default

resend(rsquodefault pagenrsquo)

break

)

serverlisten(port ip)

consolelog(rdquoServer running at httprdquo + ip + rdquordquo + port)

程式做了片段的修改首先載入 url模組另外增加一個 path變數url模組就跟如同他的命名一般專門處理 url字串處理裡面提供了許多方法來解決路徑上的問題因為從瀏覽器發出的要求路徑可能會帶有多種需求

或者 GET參數組合等因此我們需要將路徑單純化取用路徑部分的資料即可例如使用者可能會送出 http1270011337testsend=1如果直接

32 4 Nodejs基礎

Nodejs中文電子書

信任 requrl就會收到結果為testsend=1所以需要透過 url模組的方法將路徑資料過濾

在這邊使用 urlparse的方法裡面帶入網址格式資料會回傳路徑資料為了後需方便使用將回傳的資料設定到 path變數當中在回傳的路徑資料裡面包含資訊如下圖

這邊只需要使用單純的路徑要求直接取用 pathpathname就可以達到我們的目的

最後要做路徑的判別在不同的路徑可以指定不同的輸出在範例中

有三個可能結果第一個從瀏覽器輸入index就會顯示 index結果test 就會呈現出 test頁面最後如果都不符合預期的輸入會直接顯示 default的頁面最後的預防可以讓瀏覽器不會出現非預期結果讓程式的可靠性提昇

底下為測試結果

42 nodejs http路徑建立 33

Nodejs中文電子書

43 nodejs檔案讀取

前面已經介紹如何使用路由(rount)做出不同的回應實際應用只有在瀏覽器只有輸出幾個文字資料總是不夠的在本章節中將介紹如何使

用檔案讀取輸出檔案資料讓使用者在前端瀏覽器也可以讀取到完整的

html css javascript檔案輸出

檔案管理最重要的部分就是 lsquoFile system lthttpnodejsorgdocslatestapifshtmlgtlsquo這個模組此模組可以針對檔案做管理監控讀取等行為裡面有許多預設的方法底下是檔案輸出的基本範例底下會有兩個檔案

第一個是靜態 html檔案

ltDOCTYPE htmlgt

lthtml xmlns=rdquohttpwwww3org1999xhtmlrdquo xmllang=rdquozh-CNrdquo lang=rdquozh-CNrdquogt

ltheadgt

ltmeta http-equiv=rdquoContent-Typerdquo content=rdquotexthtml charset=UTF-7rdquo gt

lttitlegtnodejs index html filelttitlegt

ltheadgt

ltbodygt

34 4 Nodejs基礎

Nodejs中文電子書

lth1gtnodejs index html filelth1gt

ltbodygt

lthtmlgt

另一個為 nodejs程式

var fs = require(rdquofsrdquo)

filename = rdquostaticindexhtmlrdquo

encode = rdquoutf8rdquo

fsreadFile(filename encode function(err file)

consolelog(file)

)

一開始直接載入 le system模組 載入名稱為 fs讀取檔案主要使

用的方法為 readFile裡面以三個參數路徑 ( le path) 編碼方式 (encoding)

回應函式 (callback)路徑必須要設定為靜態 html所在位置才能指定到正確的檔案靜態檔案的編碼方式也必須正確這邊使用靜態檔案的編

碼為 utf8如果編碼設定錯誤nodejs讀取出來檔案結果會使用 byte raw格式輸出如果錯誤編碼格式會導致輸出資料為 byte raw

回應函式中裡面會使用兩個變數error為錯誤資訊如果讀取的檔案不存在或者發生錯誤error數值會是 true如果成功讀取資料 error將會是 falsecontent則是檔案內容資料讀取後將會把資料全數丟到 content這個變數當中

最後程式的輸出結果畫面如下

43 nodejs檔案讀取 35

Nodejs中文電子書

44 nodejs http靜態檔案輸出

前面已經了解如何讀取本地端檔案接下來將配合 http伺服器路由讓每個路由都能夠輸出相對應的靜態 html檔案首先新增加幾個靜態 html檔案

ltDOCTYPE htmlgt

lthtml xmlns=rdquohttpwwww3org1999xhtmlrdquo xmllang=rdquozh-CNrdquo lang=rdquozh-CNrdquogt

ltheadgt

ltmeta http-equiv=rdquoContent-Typerdquo content=rdquotexthtml charset=UTF-7rdquo gt

lttitlegtnodejs index html filelttitlegt

ltheadgt

ltbodygt

lth1gtnodejs index html filelth1gt

ltbodygt

lthtmlgt

ltDOCTYPE htmlgt

lthtml xmlns=rdquohttpwwww3org1999xhtmlrdquo xmllang=rdquozh-CNrdquo lang=rdquozh-CNrdquogt

ltheadgt

ltmeta http-equiv=rdquoContent-Typerdquo content=rdquotexthtml charset=UTF-7rdquo gt

lttitlegtnodejs test html filelttitlegt

ltheadgt

ltbodygt

lth1gtnodejs test html filelth1gt

ltbodygt

lthtmlgt

ltDOCTYPE htmlgt

lthtml xmlns=rdquohttpwwww3org1999xhtmlrdquo xmllang=rdquozh-CNrdquo lang=rdquozh-CNrdquogt

ltheadgt

ltmeta http-equiv=rdquoContent-Typerdquo content=rdquotexthtml charset=UTF-7rdquo gt

lttitlegtnodejs static html filelttitlegt

ltheadgt

ltbodygt

lth1gtnodejs static html filelth1gt

ltbodygt

lthtmlgt

36 4 Nodejs基礎

Nodejs中文電子書

準備一個包含基本路由功能的 http伺服器

var server

ip = rdquo127001rdquo

port = 1337

http = require(rsquohttprsquo)

url = require(rsquourlrsquo)

server = httpcreateServer(function (req res)

var path = urlparse(requrl)

)

serverlisten(port ip)

consolelog(rdquoServer running at httprdquo + ip + rdquordquo + port)

加入 le system 模組使用 readFile 的功能將這一段程式放置於

createServer的回應函式中

fsreadFile(filePath encode function(err file)

)

readFile的回應函式裡面加入頁面輸出讓瀏覽器可以正確讀到檔案在這邊我們設定讀取的檔案為 html 靜態檔案所以 Content-type 設定為texthtml讀取到檔案的內容將會正確輸出成 html靜態檔案

fsreadFile(filePath encode function(err file)

reswriteHead(200 rsquoContent-Typersquo rsquotexthtmlrsquo)

reswrite(file)

resend()

)

到這邊為止基本的程式內容都已經完成剩下一些細節的調整首先

路徑上必須做調整目前的靜態檔案全部都放置於 static資料夾底下設定一個變數來記住資料夾位置

接著將瀏覽器發出要求路徑與資料夾組合讀取正確 html靜態檔案使用者有可能會輸入錯誤路徑所以在讀取檔案的時候要加入錯誤處理

同時回應 404伺服器無法正確回應的 http header格式

加入這些細節的修改一個基本的 http 靜態 html輸出伺服器就完成了完整程式碼如下

44 nodejs http靜態檔案輸出 37

Nodejs中文電子書

var server

ip = rdquo127001rdquo

port = 1337

http = require(rsquohttprsquo)

fs = require(rdquofsrdquo)

folderPath = rdquostaticrdquo

url = require(rsquourlrsquo)

path

filePath

encode = rdquoutf8rdquo

server = httpcreateServer(function (req res)

path = urlparse(requrl)

filePath = folderPath + pathpathname

fsreadFile(filePath encode function(err file)

if (err)

reswriteHead(404 rsquoContent-Typersquo rsquotextplainrsquo)

resend()

return

reswriteHead(200 rsquoContent-Typersquo rsquotextapplicationrsquo)

reswrite(file)

resend()

)

)

serverlisten(port ip)

consolelog(rdquoServer running at httprdquo + ip + rdquordquo + port)

45 nodejs http GET資料擷取

http伺服器中除了路由之外另一個最常使用的方法就是擷取 GET資料本單元將會介紹如何透過基本 http伺服器擷取瀏覽器傳來的要求擷取 GET資料

在 http協定中GET參數都是藉由 URL從瀏覽器發出要求送至伺服器

38 4 Nodejs基礎

Nodejs中文電子書

端基本的傳送網址格式可能如下

http127001testsend=1amptest=2

上面這段網址裡面的 GET參數就是 send而這個變數的數值就為 1如果想要在 http伺服器取得 GET資料需要在瀏覽器給予的要求 (request)做處理

首先需要載入 query string這個模組這個模組主要是用來將字串資料

過濾後轉換成javascript物件

qs = require(rsquoquerystringrsquo)

接著在第一階段利用 url模組過濾瀏覽器發出的 URL資料後將回應的物件裡面的 query這個變數是一個字串值資料過濾後如下

send=1amptest=2

透過 query string使用 parse這個方法將資料轉換成 javascript物件就表示 GET的資料已經被伺服器端正式擷取下來

path = urlparse(requrl)

parameter = qsparse(pathquery)

整個 nodejs http GET參數完整擷取程式碼如下

var server

ip = rdquo127001rdquo

port = 1337

http = require(rsquohttprsquo)

qs = require(rsquoquerystringrsquo)

url = require(rsquourlrsquo)

server = httpcreateServer(function (req res)

var path = urlparse(requrl)

parameter = qsparse(pathquery)

consoledir(parameter)

reswriteHead(200 rsquoContent-Typersquo rsquotextplainrsquo)

reswrite(rsquoBrowser test GET parameternrsquo)

resend()

)

45 nodejs http GET資料擷取 39

Nodejs中文電子書

serverlisten(port ip)

consolelog(rdquoServer running at httprdquo + ip + rdquordquo + port)

程式運作之後由瀏覽器輸入要求網址之後nodejs伺服器端回應資料為

Server running at http1270011337

send rsquo1rsquo test rsquo1rsquo

46 本章結語

前面所解說的部份一大部分主要是處理 http伺服器基本問題雖然在某些部分有牽扯到 http 伺服器基本運作原理主要還是希望可以藉由這些基本範例練習 nodejs練習回應函式與語法串接的特點習慣編寫javascript風格程式當然直接這樣開發 nodejs是非常辛苦的接下來在模組實戰開發的部份將會介紹特定的模組一步一步帶領各位從無到有進行

nodejs應用程式開發

40 4 Nodejs基礎

5

NPM套件管理工具

npm全名為 Node Package Manager是 Nodejs的套件(package)管理工具類似 Perl的 ppm或 PHP的 PEAR等安裝 npm後使用npm install

module name指令即可安裝新套件維護管理套件的工作會更加輕鬆

npm可以讓Nodejs的開發者直接利用擴充線上的套件庫(packagesregistry)加速軟體專案的開發npm提供很友善的搜尋功能可以快速找到安裝需要的套件當這些套件發行新版本時npm也可以協助開發者自動更新這些套件

npm不僅可用於安裝新的套件它也支援搜尋列出已安裝模組及更新的功能

51 安裝 NPM

Nodejs在 063版本開始內建 npm讀者安裝的版本若是此版本或更新的版本就可以略過以下安裝說明

若要檢查 npm是否正確安裝可以使用以下的指令

npm -v

41

Nodejs中文電子書

執行結果說明

若 npm正確安裝執行 npm -v將會看到類似 110-2的版本訊息

若讀者安裝的 Nodejs版本比較舊或是有興趣嘗試自己動手安裝 npm工具則可以參考以下的說明

安裝於Windows系統

Nodejs for Windows 於 062 版開始內建 npm使用 nodejsorg 官方提供的安裝程式不需要進一步的設定就可以立即使用 npm指令對於Windows的開發者來說大幅降低環境設定的問題與門檻

除了使用 Nodejs內建的 npm讀者也可以從 npm官方提供的以下網址

httpnpmjsorgdist

這是由 npm提供的 Fancy Windows Install版本請下載壓縮檔(例如npm-110-3zip)並將壓縮檔內容解壓縮至 Nodejs的安裝路徑(例如CProgram Filesnodejs)

解壓縮後在 Nodejs的安裝路徑下應該有以下的檔案及資料夾

bull npmcmd(檔案)

bull node modules(資料夾)

安裝於 Linux系統

Ubuntu Linux的使用者可以加入 NPM Unoffcial PPA這個 repository即可使用 apt-get完成 npm安裝

42 5 NPM套件管理工具

Nodejs中文電子書

Ubuntu Linux使用 apt-get安裝 npm

sudo apt-get install python-software-properties

sudo add-apt-repository ppagias-kay-leenpm

sudo apt-get update

sudo apt-get install npm

npm官方提供的安裝程式 installsh可以適用於大多數的 Linux系統使用這個安裝程式請先確認

1 系統已安裝 curl工具(請使用 curl --version查看版本訊息)

2 已安裝 Nodejs並且 PATH正確設置

3 Nodejs的版本必須大於 04x

以下為 npm提供的安裝指令

curl httpnpmjsorginstallsh | sh

安裝成功會看到如下訊息

installsh安裝成功的訊息

npm10105 homeUSERNAMElocalnodelibnode_modulesnpm

It worked

安裝於Mac OS X

建議採用與 Nodejs相同的方式進行 npm的安裝例如使用MacPorts安裝 Nodejs就同樣使用MacPorts安裝 npm這樣對日後的維護才會更方便容易

使用 MacPorts安裝 npm是本書比較建議的方式它可以讓 npm的安裝移除及更新工作自動化將會幫助開發者節省寶貴時間

51 安裝 NPM 43

Nodejs中文電子書

安裝MacPorts的提示

在MacPorts網站可以取得 OS X系統版本對應的安裝程式(例如 106或 107)httpwwwmacportsorg安裝過程會詢問系統管理者密碼使用預設的選項完成安裝即可安

裝MacPorts之後在終端機執行 port -v將會看到MacPorts的版本訊息

安裝 npm之前先更新MacPorts的套件清單以確保安裝的 npm是最新版本

sudo port -d selfupdate

接著安裝 npm

sudo port install npm

若讀者的 Nodejs並非使用MacPorts安裝則不建議使用MacPorts安裝npm因為 MacPorts會自動檢查並安裝相依套件而 npm相依 nodejs所以MacPorts也會一併將 nodejs套件安裝造成先前讀者使用其它方式安裝的 nodejs被覆蓋

讀者可以先使用 MacPorts安裝 curl(sudo port install curl)

再參考 Linux的 installsh安裝方式即可使用 npm官方提供的安裝程式

NPM安裝後測試

npm是指令列工具(command-line tool)使用時請先打開系統的文字終端機工具

測試 npm安裝與設定是否正確請輸入指令如下

npm -v

或是

npm --version

如果 npm已經正確安裝設定就會顯示版本訊息

44 5 NPM套件管理工具

Nodejs中文電子書

執行結果(範例)

110-2

52 使用 NPM安裝套件

npm目前擁有超過 6000種套件(packages)可以在 npm registry使用關鍵字搜尋套件

httpsearchnpmjsorg

舉例來說在關鍵字欄位輸入「coffee-script」下方的清單就會自動列出包含 coffee-script關鍵字的套件

接著我們回到終端機模式的操作npm的指令工具本身就可以完成套

件搜尋的任務

例如以下的指令同樣可以找出 coffee-script相關套件

npm search coffee-script

以下是搜尋結果的參考畫面

52 使用 NPM安裝套件 45

Nodejs中文電子書

找到需要的套件後(例如 express)即可使用以下指令安裝

npm install coffee-script

值得注意的一點是使用 npm install會將指定的套件安裝在工

作目錄(Working Directory)的 node modules資料夾下

以Windows為例如果執行 npm install的目錄位於

Cproject1

那麼 npm將會自動建立一個 node modules的子目錄(如果不存在)

Cproject1node modules

並且將下載的套件放置於這個子目錄例如

Cproject1node modulescoffee-script

這個設計讓專案可以個別管理相依的套件並且可以在專案佈署或發

行時將這些套件(位於 node modules)一併打包方便其它專案的使用者不必再重新下載套件

這個 npm install的預設安裝模式為 local(本地)只會變更當前專案的資料夾不會影響系統

另一種安裝模式稱為 global(全域)這種模式會將套件安裝到系統資

料夾也就是 npm安裝路徑的 node modules資料夾例如

CProgram Filesnodejsnode modules

46 5 NPM套件管理工具

Nodejs中文電子書

是否要使用全域安裝可以依照套件是否提供新指令來判斷舉例來說express 套件提供 express 這個指令而 coffee-script 則提供 coffee

指令

在 local 安 裝 模 式 中這 些 指 令 的 程 式 檔 案會 被 安 裝 到node modules的 bin這個隱藏資料夾下除非將bin的路徑加入 PATH環境變數否則要執行這些指令將會相當不便

為了方便指令的執行我們可以在 npm install 加上 -g 或 --

global參數啟用 global安裝模式例如

npm install -g coffee-script

npm install -g express

使用 global安裝模式需要注意執行權限的問題若權限不足可能會出現類似以下的錯誤訊息

npm ERR Error EACCES permission denied rsquorsquo

npm ERR

npm ERR Please try running this command again as rootAdministrator

要獲得足夠得執行權限請參考以下說明

bull Windows 7 或 2008 以上在「命令提示字元」的捷徑按右鍵選擇「以系統管理員身分執行」執行 npm指令時就會具有 Administrator身分

bull Mac OS X或 Linux系統可以使用 sudo指令例如

sudo npm install -g express

bull Linux系統可以使用 root權限登入或是以「sudo su -」切換成 root身分(使用 root權限操作系統相當危險因此並不建議使用這種方式)

若加上 -g參數使用 npm install -g coffee-script完成安裝

後就可以在終端機執行 coffee指令例如

coffee -v

52 使用 NPM安裝套件 47

Nodejs中文電子書

執行結果(範例)

CoffeeScript version 120

53 套件的更新及維護

除了前一節說明的 search 及 install 用法npm 還提供其他許多指令(commands)

使用 npm help可以查詢可用的指令

npm help

執行結果(部分)

where ltcommandgt is one of

adduser apihelp author bin bugs c cache completion

config deprecate docs edit explore faq find get

help help-search home i info init install la link

list ll ln login ls outdated owner pack prefix

prune publish r rb rebuild remove restart rm root

run-script s se search set show star start stop

submodule tag test un uninstall unlink unpublish

unstar up update version view whoami

使用 npm help command可以查詢指令的詳細用法例如

npm help list

接下來本節要介紹開發過程常用的 npm指令

使用 list可以列出已安裝套件

npm list

48 5 NPM套件管理工具

Nodejs中文電子書

執行結果(範例)

-- coffee-script120

- express256

- connect185

| -- formidable108

-- mime124

-- mkdirp007

-- qs041

檢視某個套件的詳細資訊例如

npm show express

升級所有套件(如果該套件已發佈更新版本)

npm update

升級指定的套件

npm update express

移除指定的套件

npm uninstall express

54 使用 packagejson

對於正式的 Nodejs專案可以建立一個命名為 packagejson的設

定檔(純文字格式)檔案內容參考範例如下

54 使用 packagejson 49

Nodejs中文電子書

packagejson(範例)

rdquonamerdquo rdquoapplication-namerdquo

rdquoversionrdquo rdquo001rdquo

rdquoprivaterdquo true

rdquodependenciesrdquo

rdquoexpressrdquo rdquo255rdquo

rdquocoffee-scriptrdquo rdquolatestrdquo

rdquomongooserdquo rdquogt= 253rdquo

其中 name與 version依照專案的需求設置

需要注意的是 dependencies的設定它用於指定專案相依的套件名

稱及版本

bull rdquoexpressrdquo rdquo255rdquo

代表此專案相依版本 255的 express套件

bull rdquocoffee-scriptrdquo rdquolatestrdquo

使用最新版的 coffee-script套件(每次更新都會檢查新版)

bull rdquomongooserdquo rdquogt= 253rdquo

使用版本大於 253的 mongoose套件

假設某個套件的新版可能造成專案無法正常運作就必須指定套件的

版本避免專案的程式碼來不及更新以相容新版套件通常在開發初期的

專案需要盡可能維持新套件的相容性(以取得套件的更新或修正)可以

用「gt=」設定最低相容的版本或是使用「latest」設定永遠保持最新

套件

50 5 NPM套件管理工具

6

Express介紹

在前面的 nodejs基礎當中介紹許多許多開設 http的使用方法及介紹以及許多基本的 nodejs基本應用

接下來要介紹一個套件稱為 express [Express](httpexpressjscom)這個套件主要幫忙解決許多 nodejs http server所需要的基本服務讓開發 httpservice變得更為容易不需要像之前需要透過層層模組(module)才有辦法開始編寫自己的程式

這個套件是由 TJ Holowaychuk 製作而成的套件裡面包含基本的路由處理 (route)http資料處理(GETPOSTPUT)另外還與樣板套件(jshtml template engine)搭配同時也可以處理許多複雜化的問題

61 Express安裝

安裝方式十分簡單只要透過之前介紹的 NPM就可以使用簡單的指令安裝指令如下

這邊建議需要將此套件安裝成為全域模組方便日後使用

51

Nodejs中文電子書

62 Express基本操作

express的使用也十分簡單先來建立一個基本的 hello world

var app = require(rsquoexpressrsquo)createServer()

port = 1337

applisten(port)

appget(rsquorsquo function(req res)

ressend(rsquohello worldrsquo)

)

consolelog(rsquostart express servernrsquo)

可以從上面的程式碼發現基本操作與 nodejs http的建立方式沒有太大差異主要差在當我們設定路由時可以直接透過 appget方式設定回應與接受方式

63 Express路由處理

Express對於 http服務上有許多包裝讓開發者使用及設定上更為方便例如有幾個路由設定那我們就統一藉由 appget來處理

Create http server

appget(rsquorsquo function(req res)

ressend(rsquohello worldrsquo)

)

appget(rsquotestrsquo function(req res)

ressend(rsquotest renderrsquo)

)

appget(rsquouserrsquo function(req res)

ressend(rsquouser pagersquo)

)

52 6 Express介紹

Nodejs中文電子書

如上面的程式碼所表示appget可以帶入兩個參數第一個是路徑名稱設定第二個為回應函式 (call back function)回應函式裡面就如同之前的 createServer方法裡面包含 requestresponse兩個物件可供使用使用者就可以透過瀏覽器輸入不同的 url切換到不同的頁面顯示不同的結果

路由設定上也有基本的配對方式讓使用者從瀏覽器輸入的網址可以

是一個變數只要符合型態就可以有對應的頁面產出例如

Create http server

appget(rsquouseridrsquo function(req res)

ressend(rsquouser rsquo + reqparamsid)

)

appget(rsquonumberrsquo function(req res)

ressend(rsquonumber rsquo + reqparamsnumber)

)

裡 面 使 用 到number 從 網 址 輸 入 之 後 就 可 以 直 接 使 用reqparamsnumber 取得所輸入的資料變成 url 參數使用當然前面也是可以加上路徑的設定userid在瀏覽器上路徑必須符合userxxx透

過 reqparamsid就可以取到 xxx這個字串值

另外express參數處理也提供了路由參數配對處理也可以透過正規表示法作為參數設定

var app = require(rsquoexpressrsquo)createServer()

port = 1337

applisten(port)

appget(^ip((d23)((d23))((d23))((d23))) function(req res)

ressend(reqparams)

)

上面程式碼可以發現後面路由設定的型態是正規表示法裡面設定

格式為ip之後必須要加上 ip型態才會符合資料格式同時取得 ip資料已經由正規表示法將資料做分群因此可以取得 ip的四個數字

此程式執行之後可以透過瀏覽器測試輸入網址為 localhost3000ip25525510010可以從頁面獲得資料

63 Express路由處理 53

Nodejs中文電子書

此章節全部範例程式碼如下

overview

author Caesar Chi

blog clonnblogspotcom

version 20120226

create server

var app = require(rsquoexpressrsquo)createServer()

port = 1337

applisten(port)

normal style

appget(rsquorsquo function(req res)

ressend(rsquohello worldrsquo)

)

appget(rsquotestrsquo function(req res)

ressend(rsquotest renderrsquo)

)

parameter style

appget(rsquouseridrsquo function(req res)

ressend(rsquouser rsquo + reqparamsid)

)

appget(rsquonumberrsquo function(req res)

ressend(rsquonumber rsquo + reqparamsnumber)

)

REGX style

appget(^ip((d23)((d23))((d23))) function(req res)

ressend(reqparams)

)

appget(rsquorsquo function(req res)

ressend(rsquoPage not foundrsquo 404)

54 6 Express介紹

Nodejs中文電子書

)

consolelog(rsquostart express servernrsquo)

64 Express middleware

Express 裡面有一個十分好用的應用概念稱為 middleware可以透過middleware做出複雜的效果同時上面也有介紹 next方法參數傳遞就是靠 middleware的概念來傳遞參數讓開發者可以明確的控制程式邏輯

create http server

appuse(expressbodyParser())

appuse(expressmethodOverride())

appuse(expresssession())

上面都是一種 middleware的使用方式透過 appuse方式裡面載入函式執行方法回應函式會包含三個基本參數responserequestnext其中next表示下一個 middleware執行函式同時會自動將預設三個參數繼續帶往下個函式執行底下有個實驗

上面的片段程式執行後開啟瀏覽器連結上 localhost1337會發現伺服器回應結果順序如下

first middle ware

second middle ware

execute middle ware

end middleware function

從上面的結果可以得知剛才設定的 middleware都生效了在 appuse設定的 middleware是所有 url皆會執行方法如果有指定特定方法就可以使用 appget的 middleware設定在 appget函式的第二個參數就可以帶入函式或者是匿名函式只要函式裡面最後會接受 request response next這三個參數同時也有正確指定 next函式的執行時機最後都會執行到最後一個方法當然開發者也可以評估程式邏輯要執行到哪一個階段讓邏輯

可以更為分明

64 Express middleware 55

Nodejs中文電子書

65 Express路由應用

在實際開發上可能會遇到需要使用參數等方式混和變數一起使用

express裡面提供了一個很棒的處理方法 appall這個方式可以先採用基本路由配對再將設定為每個不同的處理方式開發者可以透過這個方式簡

化自己的程式邏輯

overview

author

version 20120226

create server

var app = require(rsquoexpressrsquo)createServer()

port = 1337

users = [

name rsquoClonnrsquo

name rsquoChirsquo

]

applisten(port)

appall(rsquouseridoprsquo function(req res next)

requser = users[reqparamsid]

if (requser)

next()

else

next(new Error(rsquocannot find user rsquo + reqparamsid))

)

appget(rsquouseridrsquo function(req res)

ressend(rsquoviewing rsquo + requsername)

)

appget(rsquouserideditrsquo function(req res)

ressend(rsquoediting rsquo + requsername)

)

56 6 Express介紹

Nodejs中文電子書

appget(rsquouseriddeletersquo function(req res)

ressend(rsquodeleting rsquo + requsername)

)

appget(rsquorsquo function(req res)

ressend(rsquoPage not foundrsquo 404)

)

consolelog(rsquostart express servernrsquo)

內部宣告一組預設的使用者分別給予名稱設定藉由 appall這個方法可以先將路由雛形建立再接下來設定 appget的路徑格式只要符合格式就會分配進入對應的方法中像上面的程式當中如果使用者輸入路徑為user0除了執行 appall程式之後執行 next方法就會對應到路徑設定為userid的這個方法當中如果使用者輸入路徑為user0edit就會執行到useridedit的對應方法

66 Express GET應用範例

我們準備一個使用 GET方法傳送資料的表單

ltDOCTYPE htmlgt

lthtmlgt

ltheadgt

ltmeta http-equiv=rdquoContent-Typerdquo content=rdquotexthtml charset=utf8rdquogt

lttitlegtNodejs 菜鳥筆記 (1)lttitlegt

ltlink rel=rdquostylesheetrdquo href=rdquocssstylecssrdquo type=rdquotextcssrdquo media=rdquoallrdquo gt

ltheadgt

ltbodygt

lth1gtNodejs 菜鳥筆記-註冊lth1gt

ltform id=rdquosignuprdquo method=rdquoGETrdquo action=rdquohttplocalhost3000Signuprdquogt

ltlabelgt 使用者名稱ltlabelgtltinput type=rdquotextrdquo id=rdquousernamerdquo name=rdquousernamerdquo gtltbrgt

ltlabelgt 電子郵件ltlabelgtltinput type=rdquotextrdquo id=rdquoemailrdquo name=rdquoemailrdquo gtltbrgt

ltinput type=rdquosubmitrdquo value=rdquo 註冊我的帳號rdquo gtltbrgt

ltformgt

ltbodygt

lthtmlgt

66 Express GET應用範例 57

Nodejs中文電子書

這個表單沒有什麼特別的地方我們只需要看第 9 行form 使用的method是 GET然後 action是rdquohttplocalhost3000Signupldquo等一下我們要來撰寫Signup這個 URL Path的處理程式

處理 Signup行為

我們知道所謂的 GET方法會透過 URL來把表單的值給帶過去以上面的表單來說到時候 URL會以這樣的形式傳遞

httplocalhost3000Signupusername=xxxampemail=xxx

所以要能處理這樣的資料必須有以下功能

bull 解析 URL

bull 辨別動作是 Signup

bull 解析出 username和 email

一旦能取得 username和 email的值程式就能加以應用了

處理 Signup的程式碼雛形

load module

var url = require(rsquourlrsquo)

urlData = urlparse(requrltrue)

action = urlDatapathname

reswriteHead(200 rdquoContent-Typerdquordquotexthtml charset=utf-8rdquo)

if (action === rdquoSignuprdquo)

user = urlDataquery

resend(rdquolth1gtrdquo + userusername + rdquo 歡迎您的加入 lth1gtltpgt 我們已經將會員啟用信寄至rdquo + useremail + rdquoltpgtrdquo)

首先需要加載 url module它是用來協助我們解析 URL的模組接著使用 urlparse方法第一個傳入 url字串變數也就是 requrl另外第二個參數的用意是設為 ture則引進 querystring模組來協助處理預設是 false它影響到的是 urlDataquery設為 true會傳回物件不然就只是一般的字串urlparse會將字串內容整理成一個物件我們把它指定給 urlData

action變數作為記錄 pathname這是我們稍後要來判斷目前網頁的動作是什麼接著先將 html表頭資訊 (Header)準備好再來判斷路徑邏輯如

58 6 Express介紹

Nodejs中文電子書

果是 Signup這個動作就把 urlDataquery裡的資料指定給 user然後輸出userusername和 useremail把使用者從表單註冊的資料顯示於頁面中

最後進行程式測試啟動 nodejs主程式之後開啟瀏覽器就會看到表單填寫完畢按下送出就可以看到結果了

完整 nodejs程式碼如下

var http = require(rsquohttprsquo)

url = require(rsquourlrsquo)

fs = require(rdquofsrdquo)

server

server = httpcreateServer(function (reqres)

var urlData

encode = rdquoutf8rdquo

filePath = rdquoviewexpress_get_example_formhtmlrdquo

action

urlData = urlparse(requrltrue)

action = urlDatapathname

reswriteHead(200 rdquoContent-Typerdquordquotexthtml charset=utf-8rdquo)

if (action === rdquoSignuprdquo)

user = urlDataquery

resend(rdquolth1gtrdquo + userusername + rdquo 歡迎您的加入 lth1gtltpgt 我們已經將會員啟用信寄至rdquo + useremail + rdquoltpgtrdquo)

else

fsreadFile(filePath encode function(err file)

reswrite(file)

resend()

)

)

serverlisten(3000)

consolelog(rsquoServer 跑起來了現在時間是rsquo + new Date())

66 Express GET應用範例 59

Nodejs中文電子書

67 Express POST應用範例

一開始準備基本的 html表單傳送內容以 POST方式form的 action屬性設定為 POST其餘 html內容與前一個範例應用相同

ltDOCTYPE htmlgt

lthtmlgt

ltheadgt

ltmeta http-equiv=rdquoContent-Typerdquo content=rdquotexthtml charset=utf8rdquogt

lttitlegtNodejs 菜鳥筆記 (1)lttitlegt

ltlink rel=rdquostylesheetrdquo href=rdquocssstylecssrdquo type=rdquotextcssrdquo media=rdquoallrdquo gt

ltheadgt

ltbodygt

lth1gtNodejs 菜鳥筆記-註冊lth1gt

ltform id=rdquosignuprdquo method=rdquoPOSTrdquo action=rdquohttplocalhost3000Signuprdquogt

ltlabelgt 使用者名稱ltlabelgtltinput type=rdquotextrdquo id=rdquousernamerdquo name=rdquousernamerdquo gtltbrgt

ltlabelgt 電子郵件ltlabelgtltinput type=rdquotextrdquo id=rdquoemailrdquo name=rdquoemailrdquo gtltbrgt

ltinput type=rdquosubmitrdquo value=rdquo 註冊我的帳號rdquo gtltbrgt

ltformgt

ltbodygt

lthtmlgt

nodejs的程式處理邏輯與前面 GET範例類似部分程式碼如下

qs = require(rsquoquerystringrsquo)

if (action === rdquoSignuprdquo)

formData = rsquorsquo

reqon(rdquodatardquo function (data)

formData += data

)

reqon(rdquoendrdquo function ()

user = qsparse(formData)

resend(rdquolth1gtrdquo + userusername + rdquo 歡迎您的加入 lth1gtltpgt 我們已經將會員啟用信寄至rdquo + useremail + rdquoltpgtrdquo)

)

60 6 Express介紹

Nodejs中文電子書

主要加入了rsquoquerystringrsquo這個moduel方便我們等一下解析由表單 POST回來的資料另外加入一個 formData的變數用來搜集待等一下表單回傳的資料前面的 GET範例我們只從 req拿出 url的資料這次要在利用req身上的事件處理

JavaScript 在訂閱事件時使用 addEventListener而 nodejs 使用的則是on這邊加上了監聽 data的事件會在瀏覽器傳送資料到Web Server時被執行參數是它所接收到的資料型態是字串

接著再增加 end的事件當瀏覽器的請求事件結束時它就會動作

由於瀏覽器使用 POST在上傳資料時會將資料一塊塊地上傳因為我們在監聽 data事件時透過 formData變數將它累加起來lt不過由於我們

上傳的資料很少一次就結束不過如果日後需要傳的是資料比較大的檔

案這個累加動作就很重要

當資料傳完就進到 end 事件中會用到 qsparse 來解析 formDataformData的內容是字串內容是

username=wordsmithampemail=wordsmith40somewhere

而 qsparse可以幫我們把這個 querystring轉成物件的格式也就是

username=wordsmithampemail=wordsmith40somewhere

一旦轉成物件並指定給 user之後其他的事情就和 GET方法時操作的一樣寫 response的表頭將內容回傳並將 userusername和 useremail代入到內容中

修改完成後接著執行 nodejs程式啟動 web server開啟瀏覽器進入表單測試看看POST的方式能否順利運作

完整程式碼如下

var http = require(rsquohttprsquo)

url = require(rsquourlrsquo)

fs = require(rdquofsrdquo)

qs = require(rsquoquerystringrsquo)

server

server = httpcreateServer(function (reqres)

var urlData

67 Express POST應用範例 61

Nodejs中文電子書

encode = rdquoutf8rdquo

filePath = rdquoviewexpress_post_example_formhtmlrdquo

formData

action

urlData = urlparse(requrltrue)

action = urlDatapathname

reswriteHead(200 rdquoContent-Typerdquordquotexthtml charset=utf-8rdquo)

if (action === rdquoSignuprdquo)

formData = rsquorsquo

reqon(rdquodatardquo function (data)

formData += data

)

reqon(rdquoendrdquo function ()

user = qsparse(formData)

resend(rdquolth1gtrdquo + userusername + rdquo 歡迎您的加入 lth1gtltpgt 我們已經將會員啟用信寄至rdquo + useremail + rdquoltpgtrdquo)

)

else

fsreadFile(filePath encode function(err file)

reswrite(file)

resend()

)

)

serverlisten(3000)

consolelog(rsquoServer 跑起來了現在時間是rsquo + new Date())

62 6 Express介紹

Nodejs中文電子書

68 Express AJAX應用範例

在 Nodejs要使用 Ajax傳送資料並且與之互動在接受資料的部份沒有太大的差別client端不是用 GET就是用 POST來傳資料重點在處理完後用 JSON格式回傳當然 Ajax不見得只傳 JSON格式有時是回傳一段 HTML碼不過後者對伺服器來說基本上就和前兩篇沒有差別了所以我們還是以回傳 JSON做為這一回的主題

這一回其實大多數的工作都會落在前端 Ajax上面前端要負責發送與接收資料並在接收資料後撤掉原先發送資料的表單並將取得的資料

改成 HTML格式之後放上頁面

首先先準備 HTML靜態頁面資料

ltDOCTYPE htmlgt

lthtmlgt

ltheadgt

lttitlegtNodejs 菜鳥筆記 (3)lttitlegt

ltlink rel=rdquostylesheetrdquo href=rdquocssstylecssrdquo type=rdquotextcssrdquo media=rdquoallrdquo gt

ltscript src=rdquohttpsajaxgoogleapiscomajaxlibsjquery171jqueryminjsrdquogtltscriptgt

ltheadgt

ltbodygt

lth1gtNodejs 菜鳥筆記-註冊 (用 Ajax)lth1gt

ltform id=rdquosignuprdquo action=rdquoSignuprdquo method=rdquoPOSTrdquogt

ltlabelgt 使用者名稱ltlabelgtltinput type=rdquotextrdquo id=rdquousernamerdquo name=rdquousernamerdquo gtltbrgt

ltlabelgt 電子郵件ltlabelgtltinput type=rdquotextrdquo id=rdquoemailrdquo name=rdquoemailrdquo gtltbrgt

ltinput type=rdquosubmitrdquo value=rdquo 註冊我的帳號rdquo gtltbrgt

ltformgt

ltdiv id=rdquoresultrdquogt

ltdivgt

ltscript type=rdquotextjavascriptrdquogt

$(function()$(rsquosignup input[type=rdquosubmitrdquo]rsquo)click(function(e)

var user =

userusername = $(rdquousernamerdquo)val()useremail = $(rdquoemailrdquo)val()

$post(rdquoSignuprdquouserfunction(data)greet(data)

)

68 Express AJAX應用範例 63

Nodejs中文電子書

epreventDefault()

)

var greet = function(msg)

var resultNode = $(rsquoresultrsquo)greeting = $(rsquolth2gtHirsquo+msgusername+rsquo 你的會員 id 是rsquo+ msgid +rsquo 我們會將會員啟動信寄至rsquo+msgemail+rsquolth2gtrsquo)

$(rsquosignuprsquo)hide()resultNodehtml(rdquordquo)

resultNodeappend(greeting)

)

ltscriptgt

ltbodygt

lthtmlgt

HTML頁面上準備了一個表單用來傳送註冊資料接著直接引用了Google CDN來載入 jQuery用來幫我們處理 Ajax的工作這次要傳送和接收的工作很大的變動都在 HTML頁面上的 JavaScript當中我們要做的事有 (相關 jQuery處理這邊不多做贅述指提起主要功能解說)

bull 用 jQUery取得 submit按鈕綁定它的 click動作

bull 取得表單 username和 email的值存放在 user這個物件中

bull 用 jQuery的 $post方法將 user的資料傳到 Server

bull 一旦成功取得資料後透過 greet這個 function組成回報給 user的訊息

bull 清空原本給使用者填資料的表單

bull 將 Server回傳的 usernameemail和 id這 3個資料組成回應的訊息

bull 將訊息放到原本表單的位置

經過以上的處理後一個 Ajax的表單的基本功能已經完成

接著進行 nodejs主要程式的編輯部分程式碼如下

var fs = require(rdquofsrdquo)

64 6 Express介紹

Nodejs中文電子書

qs = require(rsquoquerystringrsquo)

if (action === rdquoSignuprdquo)

formData = rsquorsquo

reqon(rdquodatardquo function (data)

formData += data

)

reqon(rdquoendrdquo function ()

var msg

user = qsparse(formData)

userid = rdquo123456rdquo

msg = JSONstringify(user)

reswriteHead(200 rdquoContent-Typerdquordquoapplicationjson charset=utf-8rdquordquoContent-Lengthrdquomsglength)

resend(msg)

)

這裡的程式和前面 POST範例基本上大同小異差別在

bull 幫 user的資料加上 id隨意存放一些文字進去讓 Server回傳的資料多於 Client端傳上來的不然會覺得 Server都沒做事

bull 增加了 msg 這個變數存放將 user 物件 JSON 文字化的結果JSONstringify這個轉換函式是 V8引擎所提供的如果你好奇的話

bull 大重點來了我們要告訴 Client端這次回傳的資料格式是 JSON所在 Content-type和 Content-Length要提供給 Client

Server很輕鬆就完成任務了最後進行程式測試啟動 nodejs主程式之後開啟瀏覽器就會看到表單填寫完畢按下送出就可以看到結果了

最後 nodejs本篇範例程式碼如下

var http = require(rsquohttprsquo)

url = require(rsquourlrsquo)

fs = require(rdquofsrdquo)

qs = require(rsquoquerystringrsquo)

server

68 Express AJAX應用範例 65

Nodejs中文電子書

server = httpcreateServer(function (reqres)

var urlData

encode = rdquoutf8rdquo

filePath = rdquoviewexpress_ajax_example_formhtmlrdquo

formData

action

urlData = urlparse(requrltrue)

action = urlDatapathname

if (action === rdquoSignuprdquo)

formData = rsquorsquo

reqon(rdquodatardquo function (data)

formData += data

)

reqon(rdquoendrdquo function ()

var msg

user = qsparse(formData)

userid = rdquo123456rdquo

msg = JSONstringify(user)

reswriteHead(200 rdquoContent-TyperdquordquoapplicationjsonrdquordquoContent-Lengthrdquomsglength)

resend(msg)

)

else

fsreadFile(filePath encode function(err file)

reswriteHead(200 rdquoContent-Typerdquordquotexthtml charset=utf-8rdquo)

reswrite(file)

resend()

)

)

serverlisten(3000)

66 6 Express介紹

Nodejs中文電子書

consolelog(rsquoServer 跑起來了現在時間是rsquo + new Date())

69 原始資料提供

bull [NodeJS初學者筆記 (1)-用 GET傳送資料] (httpithelpithomecomtwquestion10087402)

bull [NodeJS初學者筆記 (2)-用 POST傳送資料] (httpithelpithomecomtwquestion10087489)

bull [NodeJS 初學者筆記 (3)-用 Ajax 傳送資料] (httpithelpithomecomtwquestion10087627)

69 原始資料提供 67

Nodejs中文電子書

68 6 Express介紹

7

CoffeeScript

bull Spine

bull Hem

bull Spineapp

Letrsquos Have a Cup of CoffeeScript

bull es5-shim ECMAScript 5 compatibility shims for legacy JavaScript engines

ESnext

node-iform - A middleware for node-validator httpsgithubcomguileennode-iform

69

Nodejs中文電子書

70 7 CoffeeScript

8

製作一個 Hubot的 Plurk Adapter

81 應用事項提醒

此應用範例以CoffeeScript編寫主要程式架構採用Github釋放的Hubot專案以下有幾個主要注意事項請讀者注意

bull Hubot 一開始的架構除了內建的兩個 Adapter 之外其餘都要以Nodejs的Module方式才能運作

bull Hubot的 binhubot裡面寫著 npm install所以不管你怎麼改原始碼也不能改變第一點的狀況

其實上述都在討論同一件事情NodeJS的模組而且模組不能設定相對路徑之類的來安裝一定要包裝成 npm相符和格式才有辦法正確執行

82 建立 Adapter

首先需要準備兩個檔案

bull packagejson

bull plurkcoffee

71

Nodejs中文電子書

有這兩個就足以變成 NodeJS的模組了在開始 Coding之前先來設定一下 packagejson相依性

rdquonamerdquo rdquohubot-plurkrdquo

rdquoversionrdquo rdquo011rdquo

rdquomainrdquo rdquoplurkrdquo

rdquodependenciesrdquo

rdquohubotrdquo rdquogt=205rdquo

rdquooauthrdquo rdquordquo

rdquocronrdquordquordquo

這邊會使用到NodeJS的 cron模組(Module)而登入 Plurk需要OAuth標準授權才行所以也需要加載 OAuth模組

注意Hubot在讀取非內建模組時會自動在前面加上 hubot-的前置

83 建立 Robot跟 API

本篇應用基本上程式碼大部分參考 Hubot - Twitter Adapter來製作主要差異只有在使用 OAuth這個模組Twitter有 Streaming可用而 Plurk則得用 Comet方式來達到即時讀取

plurkcoffee程式碼

Robot = require(rdquohubotrdquo)robot()

Adapter = require(rdquohubtordquo)adapter()

EventEmitter = require(rdquoeventsrdquo)EventEmitter

oauth = require(rdquooauthrdquo)

cronJob = require(rdquocronrdquo)CronJob

class Plurk exntends Adapter

class PlurkStreaming exnteds EventEmitter

72 8 製作一個 Hubot的 Plurk Adapter

Nodejs中文電子書

先弄個基本架構主要 Class皆參考 Twitter Adapter命名方式接著再將 Plurk這個 Class增加幾個Method基本上只要有 run send reply就夠了而 run用來做初始化的部分

class Plurk entends Adapter

send (plurk id strings⋯) -gt

reply (plurk id strings⋯) -gt

run -gt

看起來有點東西接著來處理主要的 Plurk API結合部分

class PlurkStreaming extends EventEmitter

consuctor (options) -gt

plurk (callback) -gt

觀察河道

getChannel -gt

取得 Comet 網址

reply (plurk id message) -gt

回噗

acceptFriends -gt

接受好友

get (path callback) -gt

GET 請求

post (path body callback)-gt

POST 請求(其實是裝飾)

request (method path body callback)-gt

主要的 OAuth 請求

comet (server callback)-gt

噗浪的 Comet 傳回是 JavaScript Callback 要另外處理後才會變成 JSON

然後把注意力集中到 constructor上先把建構子弄好

constructor (options) -gt

super()

if optionskey and optionssecret and optionstoken and optionstoken secret

key = optionskey

secret = optionssecret

token = optionstoken

token secret = optionstoken secret

83 建立 Robot跟 API 73

Nodejs中文電子書

建立 OAuth 連接

consumer = new oauthOAuth(

rdquohttpwwwplurkcomOAuthrequest tokenrdquo

rdquohttpwwwplurkcomOAuthaccess tokenrdquo

key

secret

rdquo10rdquo

rdquohttpwwwplurkcomOAuthauthorizerdquo

rdquoHMAC-SHA1rdquo

)

domain = rdquowwwplurkcomrdquo

初始化取得 Comet 網址

do getChannel

else

throw new Error(rdquo 參數不足需要 Key Secret Token Token Secretrdquo)

接著來處理 request這個 method

request (method path body callback) -gt

記錄一下這次的 Request

consolelog(rdquohttpdomainpathrdquo)

Callback 這邊先不丟進去要用另一種方式處理

request = consumerget(rdquohttpdomainpathrdquo token token secret null)

requeston rdquoresponserdquo (res) -gt

reson rdquodatardquo (chunk) -gt

parseResponse(chunk+rsquorsquo callback)

reson rdquoendrdquo (data) -gt

consolelog rdquoEnd Request pathrdquo

reson rdquoerrorrdquo (data) -gt

consolelog rdquoError rdquo + data

requestend()

處理資料

parseResponse = (data callback) -gt

if datalength gt 0

用 TryCatch 避免處理 JSON 出錯導致整個中斷

try

callback null JSONparse(data)

catch err

74 8 製作一個 Hubot的 Plurk Adapter

Nodejs中文電子書

consolelog(rdquoError Parse JSONrdquo + data err)

繼續執行

callback null data ||

大致上就是這樣上面程式的架構已經將整個 Hubot Plurk Adapter完成因為在測試時竟然因為噗浪 Lag而沒讀到完整的 Comet資料然後造成程式異常為了避免這個問題發生因此需要加上為了完美呈現需要再

加上 Comet的處理所以要使用到 EventEmitter的功能

comet (server callback) -gt

在 Callback 裡面會找不到自身所以設定區域變數

self =

記錄一下這次的 Request

consolelog(rdquo[Comet] serverrdquo)

Callback 這邊先不丟進去要用另一種方式處理

request = consumerget(rdquohttpdomainpathrdquo token token secret null)

requeston rdquoresponserdquo (res) -gt

reson rdquodatardquo (chunk) -gt

parseResponse(chunk+rsquorsquo callback)

reson rdquoendrdquo (data) -gt

consolelog rdquoEnd Request pathrdquo

請求結束發出事件通知可以進行下一次請求

selfemit rdquonextPlurkrdquo

reson rdquoerrorrdquo (data) -gt

consolelog rdquoError rdquo + data

requestend()

處理資料

parseResponse = (data callback) -gt

if datalength gt 0

用 trycatch 避免失敗中斷

try

去掉 JavaScript 的 Callback

data = datamatch(CometChannelscriptCallback((+))s)

jsonData = rdquordquo

if data

83 建立 Robot跟 API 75

Nodejs中文電子書

jsonData = JSONparse(data[1])

else

如果沒有任何 Match 嘗試直接 parse

jsonData = JSONparse(data)

catch err

consolelog(rdquo[Comet] Errorrdquo data err)

用 TryCatch 避免處理 JSON 出錯導致整個中斷

try

只傳入 json 的 data 部分

callback null jsonDatadata

catch err

consolelog(rdquo[Comet]Error Parse JSONrdquo + data err)

繼續執行

callback null data ||

後面的 get跟 post就簡單多了

get (path callback) -gt

request(rdquoGETrdquo path null callback)

post (path body callback) -gt

request(rdquoPOSTrdquo path body callback)

接著處理取的 Comet網址的 getChannel

getChannel -gt

self =

get rdquoAPPRealtimegetUserChannelrdquo (error data) -gt

if error

檢查是否有 comet server

if datacomet server

selfchannel = datacomet server

如果沒有 Channel Ready 就嘗試連接會失敗

selfemit(rsquochannel readyrsquo)

那麼先來處理 Plurk Adaper好處理的部份

send (plruk id strings⋯)-gt

跟 Reply 一樣直接交給 reply 做

reply plurk id strings⋯

76 8 製作一個 Hubot的 Plurk Adapter

Nodejs中文電子書

reply (plurk id strings⋯) -gt

stringsforEach (message) =gt

botreply(plruk id message)

接著把 run處理好就可以上線運作摟

run -gt

self =

options =

key processenvHUBOT PLURK KEY

secret processenvHUBOT PLURK SECRET

token processenvHUBOT PLURK TOKEN

token secret processenvHUBOT PLURK TOKEN SECRET

創建剛剛的 API

bot = new PlurkStreaming(options)

依照 Twitter 的 new RobotTextMessage 會沒有反應所以參考 hubot-minecraft 的方式

r = robotconstructor

處理噗浪河道訊息

doPlurk = (data)-gt

檢查是否為回噗

if dataresponse

datacontent raw = dataresponsecontent raw

datauser id = dataresponseuser id

確定有噗浪 ID 跟訊息

if dataplurk id and datacontent raw

selfreceive new rTextMessage(dataplurk id datacontent raw)

取得 Comet Server 完成開始第一次 Comet 連接

boton rdquochannel readyrdquo () -gt

botplurk selfdoPlurk

上一次 Comet 完成繼續 Polling

boton rdquonextPlurkrdquo ()-gt

botplurk selfdoPlurk

定時接受好友邀請

do botacceptFriends

bot = bot

83 建立 Robot跟 API 77

Nodejs中文電子書

終於完成 AdapterHubot專案裡面的 scripts資料夾內是互動部分不需要像 Adapter如此大費周章處理只新增檔案並且設計好對白之後就會回噗了我開發用的機器人在此大家可以去跟他玩玩[httpplurkcomelct9620 bot](httpplurkcomelct9620 bot)

84 原始資料提供

bull [製 作 一 個 Hubot 的 噗 浪 Adapter](http revo-skillfrosttw blog20120318create-a-hubot-plurk-adapter)

78 8 製作一個 Hubot的 Plurk Adapter

9

Nodejs好用工具介紹

91 logio

Real-time log monitoring in your browser

httplogioorg

nodejs (gt=0412 lt=0611)

npm config set unsafe-perm true

npm install -g --prefix=usrlocal logio

logio server start

httplocalhost8998

79

Nodejs中文電子書

80 9 Nodejs好用工具介紹

10

精選文章

101 Express

bull 使用 NodeJS + Express從 GETPOST Request取值 [邀稿中]

bull link2 [邀稿中]

bull link3 [邀稿中]

81

Nodejs中文電子書

82 10 精選文章

11

參考資源

111 Nodejs書籍

bull Programming Nodejs April 2012 (est) Travis Swicegood Pragmatic Bookshelf

bull e Little Book on CoffeeScript January 2012 Alex MacCaw OrsquoReilly Media

bull Node for Front-End Developers January 2012 Garann Means OrsquoReilly Media

bull Building Hypermedia APIs with HTML5 and Node November 2011 MikeAmundsen OrsquoReilly Media

bull Node Web Development August 2011 David Herron PacktPub

bull CoffeeScript Accelerated JavaScript Development July 2011 Trevor BurnhamPragmatic Bookshelf

bull Getting Started with GEO CouchDB and Nodejs July 2011 Mick omp-son OrsquoReilly Media

bull What Is Node July 2011 Brett McLaughlin OrsquoReilly Media

bull Node Up and Running May 2011 Tom Hughes-Croucher Mike WilsonOrsquoReilly Media

83

Nodejs中文電子書

bull Hands-on Nodejs e Nodejs introduction and API reference May 2011 Pe-dro Teixeira LeanPub

bull e Node Beginner Book Manuel Kiessling

bull Mastering Nodejs visionmedia

112 Nodejs影音教學

bull e Node Sessions e Best of OSCON 2011 August 2011 OrsquoReilly Media(Video)

bull Tom Hughes-Croucher on Node March 2011 OrsquoReilly Media (Video)

113 Nodejs教學網站

bull Node Tuts

114 Nodejs課程

bull Introduction To Nodejs Van Nguyen CodeLesson

84 11 參考資源

Page 20: Nodejs Wiki

Nodejs中文電子書

函數) 或是物件addListener(事件名稱callback 函數) 把你想要處理事件的函數傳入在物件中可以使用物件emit(事件名稱 參數) 呼叫傳入的callback函數這是 Observer Pattern的簡單實作而且跟在網頁中使用 DOM的 addEventListener使用上很類似也很容易上手不過 NodeJS是大量使用非同步方式執行的應用所以程式邏輯幾乎都是寫在 callback函數中當邏輯比較複雜時大量的 callback會讓程式看起來很複雜也比較難單元測試舉例來說

var p client = new Db(lsquointegration tests 20rsquo new Server(ldquo127001rdquo 27017) lsquopkrsquoCustomPKFactory) p clientopen(function(err p client)

p clientdropDatabase(function(err done)

p clientcreateCollection(lsquotest custom keyrsquo function(err collection)

collectioninsert(lsquoarsquo1 function(err docs)

collection nd(lsquo idrsquonew ObjectID(ldquoaaaaaaaaaaaardquo) function(err cursor)

cursortoArray(function(err items) testassertEquals(1 itemslength) p clientclose()

)

)

)

)

)

)

這是在網路上看到的一段操作 mongodb的程式碼為了循序操作所以必須在一個 callback裡面呼叫下一個動作要使用的函數這個函數裡面還是會使用 callback最後就形成一個非常深的巢狀

這樣的程式碼會比較難進行單元測試有一個簡單的解決方式是

盡量不要使用匿名函數來當作 callback或是 event handler透過這樣的方式

14 2 JavaScript與 NodeJS

Nodejs中文電子書

就可以對各個 handler做單元測試了例如

var http = require(lsquohttprsquo) var tools =

cookieParser function(request response) if(requestheaders[rsquoCookiersquo]) do parsing

var server = httpcreateServer(function(request response)

thisemit(lsquoinitrsquo request response)

) serveron(lsquoinitrsquo toolscookieParser) serverlisten(8080 lsquo127001rsquo)

更進一步可以把 tools改成外部 module例如叫做 toolsjs

moduleexports = cookieParser function(request response) if(requestheaders[rsquoCookiersquo]) do parsing

然後把程式改成

var http = require(lsquohttprsquo)

var server = httpcreateServer(function(request response) thisemit(lsquoinitrsquo re-quest response)

) serveron(lsquoinitrsquo require(lsquo toolsrsquo)cookieParser) serverlisten(8080lsquo127001rsquo)

這樣就可以單元測試 cookieParser了例如使用 nodeunit時可以這樣寫

var testCase = require(lsquonodeunitrsquo)testCase moduleexports = testCase(

ldquosetUprdquo function(cb) thisrequest = headers Cookielsquoname1val1 name2val2rsquo thisresponse = thisresult= name1rsquoval1rsquoname2rsquoval2rsquo

cb()

ldquotearDownrdquo function(cb)

cb()

23 Callback 15

Nodejs中文電子書

ldquonormal caserdquo function(test)

testexpect(1) var obj = require(lsquotoolsrsquo)cookieParser(thisrequest thisresponse)testdeepEqual(obj thisresult) testdone()

)

善於利用模組可以讓程式更好維護與測試

24 CPS(Continuation-Passing Style)

cps 是 callback 使用上的特例形式上就是在函數最後呼叫 callback這樣就好像把函數執行後把結果交給 callback 繼續運行所以稱作continuation-passing style利用 cps可以在非同步執行的情況下透過傳給 callback的這個 cps callback來獲知 callback執行完畢或是取得執行結果例如

lthtmlgt ltbodygt ltdiv id=rdquopanelrdquo style=rdquovisibilityhiddenrdquogtlt divgtlt bodygt lt htmlgt ltscriptgt var request = new XMLHttpRequest() re-questopen(lsquoGETrsquo lsquotest749txt timestamp=rsquo+new Date()getTime() true) re-questaddEventListener(lsquoreadystatechangersquo function(next)

return function() if(thisreadyState===4ampampthisstatus===200) next(thisresponseText)lt== 傳入的 cps callback 在動作完成時執行並取得結果進一步處理

(function(str)lt==這個匿名函數就是 cps callback

documentgetElementById(lsquopanelrsquo)innerHTML=str docu-mentgetElementById(lsquopanelrsquo)stylevisibility = lsquovisiblersquo

) false) requestsend() ltscriptgt

進一步的應用也可以參考 2-6流程控制

16 2 JavaScript與 NodeJS

Nodejs中文電子書

25 函數返回函數與 Currying

前面的 cps範例裡面使用了函數返回函數這是為了把 cps callback傳遞給 onreadystatechange事件處理函數的方法(因為這個事件處理函數並沒有設計好會傳送接收這樣的參數)實際會執行的事件處理函數其實是內層返回的那個函數之外包覆的這個函數主要是為了利用 Closure把 next傳給內層的事件處理函數這個方法更常使用的地方是為了解決一些

scope問題例如

ltscriptgt var accu=0count=10 for(var i=0 iltcount i++)

setTimeout(

function() countndash accu+=i if(countlt=0)

consolelog(accu)

50)

ltscriptgt

最後得出的結果會是 100而不是想像中的 45這是因為等到 setTime-out指定的函數執行時變數 i已經變成 10而離開迴圈了要解決這個問題就需要透過 Closure來保存變數 i

ltscriptgt var accu=0count=10 for(var i=0 iltcount i++)

setTimeout(

function(i) return function() countndash

accu+=i if(countlt=0)

consolelog(accu)

(i)

50)

25 函數返回函數與 Currying 17

Nodejs中文電子書

淺藍色底色的部份是跟上面例子不一樣的地方 ltscriptgt

函數返回函數的另外一個用途是可以暫緩函數執行例如

function add(m n) return m+n

var a = add(20 10) consolelog(a)

add這個函數必須同時輸入兩個參數才有辦法執行如果我希望這個函數可以先給它一個參數等一些處理過後再給一個參數然後得到結

果就必須用函數返回函數的方式做修改

function add(m)

return function(n) return m+n

var wait another arg = add(20)先給一個參數 var a = function(arr)

var ret=0 for(var i=0iltarrlengthi++) ret+=arr[i] return ret

([1234])計算一下另一個參數 var b = wait another arg(a)然後再繼續執行 consolelog(b)

像這樣利用函數返回函數使得原本接受多個參數的函數可以一次

接受一個參數直到參數接收完成才執行得到結果的方式有一個學名就

叫做Currying

綜合以上許多奇技淫巧就可以透過用函數來處理函數的方式調整

程式流程接下來看看

26 流程控制

(以 sync方式使用 async函數避開巢狀 callback循序呼叫 async callback等奇技淫巧)

建議參考

bull httphowtonodeorgcontrol- ow

bull httphowtonodeorgcontrol- ow-part-ii

18 2 JavaScript與 NodeJS

Nodejs中文電子書

bull httphowtonodeorgcontrol- ow-part-iii

bull httpblogmixunet20110202essential-node-js-patterns-and-snippets

這幾篇都是非常經典的 NodeJSJavascript流程控制好文章(阿mixu是在介紹一些 pattern時提到這方面的主題)不過我還是用幾個簡單的程式介紹一下做法跟概念

並發與等待

下面的程式參考了 mixu文章中的做法

var wait = function(callbacks done) consolelog(lsquowait startrsquo) var counter = call-backslength var results = [] var next = function(result) 接收函數執行結果並判斷是否結束執行 resultspush(result) if(ndashcounter == 0) done(results)如果結束執行就把所有執行結果傳給指定的 callback處理 for(var i = 0 i lt callbackslength i++) 依次呼叫所有要執行的函數 callbacks[i](next) consolelog(lsquowait endrsquo)

wait( [ function(next) setTimeout(function() consolelog(lsquodone arsquo) var result =500 next(result) 500) function(next) setTimeout(function() con-solelog(lsquodone brsquo) var result = 1000 next(result) 1000) function(next)setTimeout(function() consolelog(lsquodone crsquo) var result = 1500 next(1500) 1500) ] function(results) var ret = 0 i=0 for( iltresultslength i++) ret+= results[i] consolelog(lsquodone all result lsquo+ret)

)

執行結果wait start wait end done a done b done c done all result 3000

可以看出來其實 wait並不是真的等到所有函數執行完才結束執行而是在所有傳給他的函數執行完畢後(不論同步非同步)才執行處理結

果的函數(也就是 done())

不過這樣的寫法還不夠實用因為沒辦法實際讓函數可以等待執行

完畢又能當作事件處理函數來實際使用上面參考到的 Tim Caswell的文

26 流程控制 19

Nodejs中文電子書

章裡面有一種解法不過還需要額外包裝(在他的例子中)NodeJS核心的 fs物件把一些函數(例如 readFile)用 Currying處理類似像這樣

var fs = require(lsquofsrsquo) var readFile = function(path)

return function(callback errback)

fsreadFile(path function(err data)

if(err) errback()

else callback(data)

)

其他部份可以參考 Tim Caswell的文章他的 Doparallel跟上面的 wait差不多意思這裡只提示一下他沒說到的地方

另外一種做法是去修飾一下 callback當他作為事件處理函數執行後再用 cps的方式取得結果

ltscriptgt function Wait(fns done)

var count = 0 var results = [] thisgetCallback = function(index)

count++ return (function(waitback)

return function() var i=0args=[]for(iltargumentslengthi++)

argspush(arguments[i])

argspush(waitback) fns[index]apply(thisargs)

)(function(result) resultspush(result) if(ndashcount == 0)

20 2 JavaScript與 NodeJS

Nodejs中文電子書

done(results)

)

var a = new Wait(

[ function(waitback) consolelog(lsquodone arsquo) var result = 500 wait-back(result) function(waitback) consolelog(lsquodone brsquo) var result =1000 waitback(result) function(waitback) consolelog(lsquodone crsquo) varresult = 1500 waitback(result) ] function(results) var ret = 0 i=0for( iltresultslength i++) ret += results[i] consolelog(lsquodone allresult lsquo+ret)

) var callbacks = [agetCallback(0)agetCallback(1)agetCallback(0)agetCallback(2)]一次取出要使用的 callbacks避免結果提早送出 setTimeout(callbacks[0]500) setTimeout(callbacks[1] 1000) setTimeout(callbacks[2] 1500) setTime-out(callbacks[3] 2000) 當所有取出的 callbacks執行完畢就呼叫 done()來處理結果 ltscriptgt

執行結果

done a done b done a done c done all result 3500

上面只是一些小實驗更成熟的作品是 Tim Caswell 的 stephttpsgithubcomcreationixstep

如果希望真正使用同步的方式寫非同步則需要使用 Promisejs這一類的 library來轉換非同步函數不過他結構比較複雜 XD(見仁見智不過有些人認為 Promise有點過頭了)httpblogsmsdncombrbucktonarchive20110815promise-js-2-0-promise-framework-for-javascriptaspx

如果想不透過其他 Library做轉換又能直接用同步方式執行非同步函數大概就要使用一些需要額外 compile原始程式碼的方法了例如 BrunoJouhier的 streamlinejshttpsgithubcomSagestreamlinejs

26 流程控制 21

Nodejs中文電子書

循序執行

循序執行可以協助把非常深的巢狀 callback結構攤平例如用這樣的簡單模組來做(serialjs)

moduleexports = function(funs) var c = 0 if(isArrayOfFunctions(funs))

throw(lsquoArgument type was not matched Should be array of func-tionsrsquo)

return function()

var args = Arrayprototypeslicecall(arguments 0) if((cgt=funslength))

c++ return funs[c-1]apply(this args)

function isArrayOfFunctions(f ) if(typeof f == lsquoobjectrsquo) return false if(flength)return false if(fconcat) return false if(fsplice) return false var i = 0 for(iltflength i++)

if(typeof f[i] == lsquofunctionrsquo) return false

return true

簡單的測試範例(testSerialjs)使用 fs 模組確定某個 path 是檔案然後讀取印出檔案內容這樣會用到兩層的 callback所以測試中有使用serial的版本與 nested callbacks的版本做對照

var serial = require(lsquoserialrsquo) fs = require(lsquofsrsquo) path = lsquodclientjsrsquo cb = serial([ func-tion(err data)

if(err)

if(dataisFile) fsreadFile(path cb)

22 2 JavaScript與 NodeJS

Nodejs中文電子書

else consolelog(err)

function(err data)

if(err) consolelog(lsquo[ attened by searial]rsquo) con-solelog(datatoString(lsquoutf8rsquo))

else consolelog(err)

]) fsstat(path cb)

fsstat(path function(err data) 第一層 callback if(err)

if(dataisFile)

fsreadFile(path function(err data) 第二層 callback if(err)

consolelog(lsquo[nested callbacks]rsquo) con-solelog(datatoString(lsquoutf8rsquo))

else consolelog(err)

)

else consolelog(err)

)

關鍵在於這些 callback的執行是有順序性的所以利用 serial返回的一個函數 cb來取代這些 callback然後在 cb中控制每次會循序呼叫的函數

26 流程控制 23

Nodejs中文電子書

就可以把巢狀的 callback攤平成循序的 function陣列(就是傳給 serial函數的參數)

測試中的dclientjs 是一個簡單的 dnode 測試程式放在跟 testSerialjs同一個目錄

var dnode = require(lsquodnodersquo)

dnodeconnect(8000 lsquolocalhostrsquo function(remote)

remoterestart(function(str) consolelog(str) processexit()

)

)

執行測試程式後出現結果

[ attened by searial] var dnode = require(lsquodnodersquo)

dnodeconnect(8000 lsquolocalhostrsquo function(remote)

remoterestart(function(str) consolelog(str) processexit()

)

)

[nested callbacks] var dnode = require(lsquodnodersquo)

dnodeconnect(8000 lsquolocalhostrsquo function(remote)

remoterestart(function(str) consolelog(str) processexit()

)

)

對照起來看兩種寫法的結果其實是一樣的但是利用 serialjs巢狀的 callback結構就會消失

不過這樣也只限於順序單純的狀況如果函數執行的順序比較複雜

(不只是一直線)還是需要用功能更完整的流程控制模組比較好例如

httpsgithubcomcaolanasync

24 2 JavaScript與 NodeJS

3

Nodejs安裝與設定

本篇將講解如何在各個不同 OS建立 NodeJS環境目前 NodeJS 048版本環境架設方式需依賴 Linux指令才可編譯完成當然在不同作業系統中也已經有 NodeJS package可以直接使用指令快速架設以下各不同作業系統解說如何安裝 NodeJS

31 Ubuntu Linux

更新推薦使用 nvm

git clone gitgithubcomcreationixnvmgit ~nvm

echo rdquo ~nvmnvmshrdquo gtgt ~bashrc

nvm install v0614

nvm alias default v0614

以 上 可 參 考 http dreamerslabcom blog tw how-to-setup-a-node-js-development-environment-on-ubuntu-11-04

使用 APT套件管理工具是常見的方法以下是使用社群提供的 PPA安裝方式

sudo apt-get install python-software-properties

sudo add-apt-repository ppachris-leanodejs-devel

25

Nodejs中文電子書

sudo apt-get update

sudo apt-get install nodejs

32 Other Linux

Linux很適合作為 NodeJS的伺服器作業系統及開發環境安裝前請先確認以下套件已正確安裝

bull curl (wget)用來下載檔案的工具

bull git先進的版本控制工具

bull g++ GNU C++軟體編譯工具

bull make GNU軟體專案建置工具

安裝指令如下如設有權限問題請在指令前面加上 sudo

git clone httpsgithubcomjoyentnodegit

cd node

git checkout v067

configure

make

sudo make install

接著測試 nodeJS是否正常執行

node --version

出現版本訊息即表示安裝成功

33 Windows

nodeJS 在 v060 版本之後開始正式支援 windows native直接使用nodeexe 就可以執行程式支援性完全與 linux 相同更棒的部份就是不需經過編譯經過下載之後簡單設定完成立即開發 node程式

26 3 Nodejs安裝與設定

Nodejs中文電子書

下載 nodejs安裝檔案 lthttpnodejsorgdownloadgt

如此完成 windows native nodeexe安裝接著可以進入 command line執行測試在 command line輸入rdquonoderdquo會出現 nodejs版本訊息畫面表示安裝完成

33 Windows 27

Nodejs中文電子書

28 3 Nodejs安裝與設定

4

Nodejs基礎

前篇文章已經由介紹安裝至設定都有完整介紹nodeJS 內部除了javascript常用的函式 (function)物件 (object)之外也有許多不同的自訂物件nodeJS預設建立這些物件為核心物件是為了要讓開發流程更為這些資料在官方文件已經具有許多具體說明接下來將會介紹在開發 nodeJS程式時常見的物件特性與使用方法

41 nodejs http伺服器建立

在 lsquonodejs官方網站 lthttpnodejsorggtlsquo裡面有舉一個最簡單的 HTTP伺服器建立一開始初步就是建立一個伺服器平台讓 nodejs可以與瀏覽器互相行為每種語言一開始的程式建立都是以 Hello world開始最初也從 Hello world帶各位進入 nodejs的世界

輸入以下程式碼儲存檔案為 node basic http hello worldjs

var server

ip = rdquo127001rdquo

port = 1337

http = require(rsquohttprsquo)

server = httpcreateServer(function (req res)

29

Nodejs中文電子書

reswriteHead(200 rsquoContent-Typersquo rsquotextplainrsquo)

resend(rsquoHello Worldnrsquo)

)

serverlisten(port ip)

consolelog(rdquoServer running at httprdquo + ip + rdquordquo + port)

程式碼解講一開始需要有幾個基本的變數

bull ip 機器本身的 ip位置因為使用本地端因此設定為 127001

bull port 需要開通的阜號通常設定為 http port 80因範例不希望與基本 port相衝隨意設定為 1337

在 nodejs 的程式中有許多預設的模組可以使用因此需要使用require方法將模組引入在這邊我們需要使用 http這個模組因此將 http載入Http模組裡面內建有許多方法可以使用這邊採用 createServer創建一個基本的 http伺服器再將 http伺服器給予一個 server變數裡面的回呼函式 (call back function)可以載入 http伺服器的資料與回應方法 (requestresponse)在程式裡面就可以看到我們直接回應給瀏覽器端所需的 Header回應內容

reswriteHead(200 rsquoContent-Typersquo rsquotextplainrsquo)

resend(rsquoHello Worldnrsquo)

Http伺服器需要設定 port ip在最後需要設定 Http監聽需要使用到listen事件監聽所有 Http伺服器行為

httplisten(port ip)

所有事情都完成之後需要確認伺服器正確執行因此使用 console在javascript裡就有這個原生物件console所印出的資料都會顯示於 nodejs伺服器頁面這邊印出的資料並不會傳送到使用者頁面上之後許多除壞

(debug)都會用到 console物件

consolelog(rdquoServer running at httprdquo + ip + rdquordquo + port)

30 4 Nodejs基礎

Nodejs中文電子書

42 nodejs http路徑建立

前面已經介紹如何建立一個簡單的 http伺服器接下來本章節將會介紹如何處理伺服器路徑 (rout)問題在 http的協定下所有從瀏覽器發出的要求 (request)都需要經過處理路徑上的建立也是如此

路徑就是指伺服器 ip位置或者是網域名稱之後對於伺服器給予的要求修改剛才的 hello world檔案修改如下

server = httpcreateServer(function (req res)

consolelog(requrl)

reswriteHead(200 rsquoContent-Typersquo rsquotextplainrsquo)

resend(rsquohello worldnrsquo)

)

重新啟動 nodejs程式後在瀏覽器端測試一下路徑行為結果如下圖

當在瀏覽器輸入 http1270011337test 在伺服器端會收到兩個要求一個是我們輸入的test要求另外一個則是faviconicotest的路徑要求http伺服器本身需要經過程式設定才有辦法回應給瀏覽器端所需要的回應在伺服器中所有的路徑要求都是需要被解析才有辦法取得資料從

42 nodejs http路徑建立 31

Nodejs中文電子書

上面解說可以了解到在 nodejs當中所有的路徑都需要經過設定未經過設定的路由會讓瀏覽器無法取得任何資料導致錯誤頁面的發生底下將會解

說如何設定路由同時避免發生錯誤情形先前 nodejs程式需要增加一些修改才能讓使用者透過瀏覽器在不同路徑時有不同的結果根據剛才

的程式做如下的修改

var server

ip = rdquo127001rdquo

port = 1337

http = require(rsquohttprsquo)

url = require(rsquourlrsquo)

path

server = httpcreateServer(function (req res)

path = urlparse(requrl)

reswriteHead(200 rsquoContent-Typersquo rsquotextplainrsquo)

switch (pathpathname)

case rdquoindexrdquo

resend(rsquoI am indexnrsquo)

break

case rdquotestrdquo

resend(rsquothis is test pagenrsquo)

break

default

resend(rsquodefault pagenrsquo)

break

)

serverlisten(port ip)

consolelog(rdquoServer running at httprdquo + ip + rdquordquo + port)

程式做了片段的修改首先載入 url模組另外增加一個 path變數url模組就跟如同他的命名一般專門處理 url字串處理裡面提供了許多方法來解決路徑上的問題因為從瀏覽器發出的要求路徑可能會帶有多種需求

或者 GET參數組合等因此我們需要將路徑單純化取用路徑部分的資料即可例如使用者可能會送出 http1270011337testsend=1如果直接

32 4 Nodejs基礎

Nodejs中文電子書

信任 requrl就會收到結果為testsend=1所以需要透過 url模組的方法將路徑資料過濾

在這邊使用 urlparse的方法裡面帶入網址格式資料會回傳路徑資料為了後需方便使用將回傳的資料設定到 path變數當中在回傳的路徑資料裡面包含資訊如下圖

這邊只需要使用單純的路徑要求直接取用 pathpathname就可以達到我們的目的

最後要做路徑的判別在不同的路徑可以指定不同的輸出在範例中

有三個可能結果第一個從瀏覽器輸入index就會顯示 index結果test 就會呈現出 test頁面最後如果都不符合預期的輸入會直接顯示 default的頁面最後的預防可以讓瀏覽器不會出現非預期結果讓程式的可靠性提昇

底下為測試結果

42 nodejs http路徑建立 33

Nodejs中文電子書

43 nodejs檔案讀取

前面已經介紹如何使用路由(rount)做出不同的回應實際應用只有在瀏覽器只有輸出幾個文字資料總是不夠的在本章節中將介紹如何使

用檔案讀取輸出檔案資料讓使用者在前端瀏覽器也可以讀取到完整的

html css javascript檔案輸出

檔案管理最重要的部分就是 lsquoFile system lthttpnodejsorgdocslatestapifshtmlgtlsquo這個模組此模組可以針對檔案做管理監控讀取等行為裡面有許多預設的方法底下是檔案輸出的基本範例底下會有兩個檔案

第一個是靜態 html檔案

ltDOCTYPE htmlgt

lthtml xmlns=rdquohttpwwww3org1999xhtmlrdquo xmllang=rdquozh-CNrdquo lang=rdquozh-CNrdquogt

ltheadgt

ltmeta http-equiv=rdquoContent-Typerdquo content=rdquotexthtml charset=UTF-7rdquo gt

lttitlegtnodejs index html filelttitlegt

ltheadgt

ltbodygt

34 4 Nodejs基礎

Nodejs中文電子書

lth1gtnodejs index html filelth1gt

ltbodygt

lthtmlgt

另一個為 nodejs程式

var fs = require(rdquofsrdquo)

filename = rdquostaticindexhtmlrdquo

encode = rdquoutf8rdquo

fsreadFile(filename encode function(err file)

consolelog(file)

)

一開始直接載入 le system模組 載入名稱為 fs讀取檔案主要使

用的方法為 readFile裡面以三個參數路徑 ( le path) 編碼方式 (encoding)

回應函式 (callback)路徑必須要設定為靜態 html所在位置才能指定到正確的檔案靜態檔案的編碼方式也必須正確這邊使用靜態檔案的編

碼為 utf8如果編碼設定錯誤nodejs讀取出來檔案結果會使用 byte raw格式輸出如果錯誤編碼格式會導致輸出資料為 byte raw

回應函式中裡面會使用兩個變數error為錯誤資訊如果讀取的檔案不存在或者發生錯誤error數值會是 true如果成功讀取資料 error將會是 falsecontent則是檔案內容資料讀取後將會把資料全數丟到 content這個變數當中

最後程式的輸出結果畫面如下

43 nodejs檔案讀取 35

Nodejs中文電子書

44 nodejs http靜態檔案輸出

前面已經了解如何讀取本地端檔案接下來將配合 http伺服器路由讓每個路由都能夠輸出相對應的靜態 html檔案首先新增加幾個靜態 html檔案

ltDOCTYPE htmlgt

lthtml xmlns=rdquohttpwwww3org1999xhtmlrdquo xmllang=rdquozh-CNrdquo lang=rdquozh-CNrdquogt

ltheadgt

ltmeta http-equiv=rdquoContent-Typerdquo content=rdquotexthtml charset=UTF-7rdquo gt

lttitlegtnodejs index html filelttitlegt

ltheadgt

ltbodygt

lth1gtnodejs index html filelth1gt

ltbodygt

lthtmlgt

ltDOCTYPE htmlgt

lthtml xmlns=rdquohttpwwww3org1999xhtmlrdquo xmllang=rdquozh-CNrdquo lang=rdquozh-CNrdquogt

ltheadgt

ltmeta http-equiv=rdquoContent-Typerdquo content=rdquotexthtml charset=UTF-7rdquo gt

lttitlegtnodejs test html filelttitlegt

ltheadgt

ltbodygt

lth1gtnodejs test html filelth1gt

ltbodygt

lthtmlgt

ltDOCTYPE htmlgt

lthtml xmlns=rdquohttpwwww3org1999xhtmlrdquo xmllang=rdquozh-CNrdquo lang=rdquozh-CNrdquogt

ltheadgt

ltmeta http-equiv=rdquoContent-Typerdquo content=rdquotexthtml charset=UTF-7rdquo gt

lttitlegtnodejs static html filelttitlegt

ltheadgt

ltbodygt

lth1gtnodejs static html filelth1gt

ltbodygt

lthtmlgt

36 4 Nodejs基礎

Nodejs中文電子書

準備一個包含基本路由功能的 http伺服器

var server

ip = rdquo127001rdquo

port = 1337

http = require(rsquohttprsquo)

url = require(rsquourlrsquo)

server = httpcreateServer(function (req res)

var path = urlparse(requrl)

)

serverlisten(port ip)

consolelog(rdquoServer running at httprdquo + ip + rdquordquo + port)

加入 le system 模組使用 readFile 的功能將這一段程式放置於

createServer的回應函式中

fsreadFile(filePath encode function(err file)

)

readFile的回應函式裡面加入頁面輸出讓瀏覽器可以正確讀到檔案在這邊我們設定讀取的檔案為 html 靜態檔案所以 Content-type 設定為texthtml讀取到檔案的內容將會正確輸出成 html靜態檔案

fsreadFile(filePath encode function(err file)

reswriteHead(200 rsquoContent-Typersquo rsquotexthtmlrsquo)

reswrite(file)

resend()

)

到這邊為止基本的程式內容都已經完成剩下一些細節的調整首先

路徑上必須做調整目前的靜態檔案全部都放置於 static資料夾底下設定一個變數來記住資料夾位置

接著將瀏覽器發出要求路徑與資料夾組合讀取正確 html靜態檔案使用者有可能會輸入錯誤路徑所以在讀取檔案的時候要加入錯誤處理

同時回應 404伺服器無法正確回應的 http header格式

加入這些細節的修改一個基本的 http 靜態 html輸出伺服器就完成了完整程式碼如下

44 nodejs http靜態檔案輸出 37

Nodejs中文電子書

var server

ip = rdquo127001rdquo

port = 1337

http = require(rsquohttprsquo)

fs = require(rdquofsrdquo)

folderPath = rdquostaticrdquo

url = require(rsquourlrsquo)

path

filePath

encode = rdquoutf8rdquo

server = httpcreateServer(function (req res)

path = urlparse(requrl)

filePath = folderPath + pathpathname

fsreadFile(filePath encode function(err file)

if (err)

reswriteHead(404 rsquoContent-Typersquo rsquotextplainrsquo)

resend()

return

reswriteHead(200 rsquoContent-Typersquo rsquotextapplicationrsquo)

reswrite(file)

resend()

)

)

serverlisten(port ip)

consolelog(rdquoServer running at httprdquo + ip + rdquordquo + port)

45 nodejs http GET資料擷取

http伺服器中除了路由之外另一個最常使用的方法就是擷取 GET資料本單元將會介紹如何透過基本 http伺服器擷取瀏覽器傳來的要求擷取 GET資料

在 http協定中GET參數都是藉由 URL從瀏覽器發出要求送至伺服器

38 4 Nodejs基礎

Nodejs中文電子書

端基本的傳送網址格式可能如下

http127001testsend=1amptest=2

上面這段網址裡面的 GET參數就是 send而這個變數的數值就為 1如果想要在 http伺服器取得 GET資料需要在瀏覽器給予的要求 (request)做處理

首先需要載入 query string這個模組這個模組主要是用來將字串資料

過濾後轉換成javascript物件

qs = require(rsquoquerystringrsquo)

接著在第一階段利用 url模組過濾瀏覽器發出的 URL資料後將回應的物件裡面的 query這個變數是一個字串值資料過濾後如下

send=1amptest=2

透過 query string使用 parse這個方法將資料轉換成 javascript物件就表示 GET的資料已經被伺服器端正式擷取下來

path = urlparse(requrl)

parameter = qsparse(pathquery)

整個 nodejs http GET參數完整擷取程式碼如下

var server

ip = rdquo127001rdquo

port = 1337

http = require(rsquohttprsquo)

qs = require(rsquoquerystringrsquo)

url = require(rsquourlrsquo)

server = httpcreateServer(function (req res)

var path = urlparse(requrl)

parameter = qsparse(pathquery)

consoledir(parameter)

reswriteHead(200 rsquoContent-Typersquo rsquotextplainrsquo)

reswrite(rsquoBrowser test GET parameternrsquo)

resend()

)

45 nodejs http GET資料擷取 39

Nodejs中文電子書

serverlisten(port ip)

consolelog(rdquoServer running at httprdquo + ip + rdquordquo + port)

程式運作之後由瀏覽器輸入要求網址之後nodejs伺服器端回應資料為

Server running at http1270011337

send rsquo1rsquo test rsquo1rsquo

46 本章結語

前面所解說的部份一大部分主要是處理 http伺服器基本問題雖然在某些部分有牽扯到 http 伺服器基本運作原理主要還是希望可以藉由這些基本範例練習 nodejs練習回應函式與語法串接的特點習慣編寫javascript風格程式當然直接這樣開發 nodejs是非常辛苦的接下來在模組實戰開發的部份將會介紹特定的模組一步一步帶領各位從無到有進行

nodejs應用程式開發

40 4 Nodejs基礎

5

NPM套件管理工具

npm全名為 Node Package Manager是 Nodejs的套件(package)管理工具類似 Perl的 ppm或 PHP的 PEAR等安裝 npm後使用npm install

module name指令即可安裝新套件維護管理套件的工作會更加輕鬆

npm可以讓Nodejs的開發者直接利用擴充線上的套件庫(packagesregistry)加速軟體專案的開發npm提供很友善的搜尋功能可以快速找到安裝需要的套件當這些套件發行新版本時npm也可以協助開發者自動更新這些套件

npm不僅可用於安裝新的套件它也支援搜尋列出已安裝模組及更新的功能

51 安裝 NPM

Nodejs在 063版本開始內建 npm讀者安裝的版本若是此版本或更新的版本就可以略過以下安裝說明

若要檢查 npm是否正確安裝可以使用以下的指令

npm -v

41

Nodejs中文電子書

執行結果說明

若 npm正確安裝執行 npm -v將會看到類似 110-2的版本訊息

若讀者安裝的 Nodejs版本比較舊或是有興趣嘗試自己動手安裝 npm工具則可以參考以下的說明

安裝於Windows系統

Nodejs for Windows 於 062 版開始內建 npm使用 nodejsorg 官方提供的安裝程式不需要進一步的設定就可以立即使用 npm指令對於Windows的開發者來說大幅降低環境設定的問題與門檻

除了使用 Nodejs內建的 npm讀者也可以從 npm官方提供的以下網址

httpnpmjsorgdist

這是由 npm提供的 Fancy Windows Install版本請下載壓縮檔(例如npm-110-3zip)並將壓縮檔內容解壓縮至 Nodejs的安裝路徑(例如CProgram Filesnodejs)

解壓縮後在 Nodejs的安裝路徑下應該有以下的檔案及資料夾

bull npmcmd(檔案)

bull node modules(資料夾)

安裝於 Linux系統

Ubuntu Linux的使用者可以加入 NPM Unoffcial PPA這個 repository即可使用 apt-get完成 npm安裝

42 5 NPM套件管理工具

Nodejs中文電子書

Ubuntu Linux使用 apt-get安裝 npm

sudo apt-get install python-software-properties

sudo add-apt-repository ppagias-kay-leenpm

sudo apt-get update

sudo apt-get install npm

npm官方提供的安裝程式 installsh可以適用於大多數的 Linux系統使用這個安裝程式請先確認

1 系統已安裝 curl工具(請使用 curl --version查看版本訊息)

2 已安裝 Nodejs並且 PATH正確設置

3 Nodejs的版本必須大於 04x

以下為 npm提供的安裝指令

curl httpnpmjsorginstallsh | sh

安裝成功會看到如下訊息

installsh安裝成功的訊息

npm10105 homeUSERNAMElocalnodelibnode_modulesnpm

It worked

安裝於Mac OS X

建議採用與 Nodejs相同的方式進行 npm的安裝例如使用MacPorts安裝 Nodejs就同樣使用MacPorts安裝 npm這樣對日後的維護才會更方便容易

使用 MacPorts安裝 npm是本書比較建議的方式它可以讓 npm的安裝移除及更新工作自動化將會幫助開發者節省寶貴時間

51 安裝 NPM 43

Nodejs中文電子書

安裝MacPorts的提示

在MacPorts網站可以取得 OS X系統版本對應的安裝程式(例如 106或 107)httpwwwmacportsorg安裝過程會詢問系統管理者密碼使用預設的選項完成安裝即可安

裝MacPorts之後在終端機執行 port -v將會看到MacPorts的版本訊息

安裝 npm之前先更新MacPorts的套件清單以確保安裝的 npm是最新版本

sudo port -d selfupdate

接著安裝 npm

sudo port install npm

若讀者的 Nodejs並非使用MacPorts安裝則不建議使用MacPorts安裝npm因為 MacPorts會自動檢查並安裝相依套件而 npm相依 nodejs所以MacPorts也會一併將 nodejs套件安裝造成先前讀者使用其它方式安裝的 nodejs被覆蓋

讀者可以先使用 MacPorts安裝 curl(sudo port install curl)

再參考 Linux的 installsh安裝方式即可使用 npm官方提供的安裝程式

NPM安裝後測試

npm是指令列工具(command-line tool)使用時請先打開系統的文字終端機工具

測試 npm安裝與設定是否正確請輸入指令如下

npm -v

或是

npm --version

如果 npm已經正確安裝設定就會顯示版本訊息

44 5 NPM套件管理工具

Nodejs中文電子書

執行結果(範例)

110-2

52 使用 NPM安裝套件

npm目前擁有超過 6000種套件(packages)可以在 npm registry使用關鍵字搜尋套件

httpsearchnpmjsorg

舉例來說在關鍵字欄位輸入「coffee-script」下方的清單就會自動列出包含 coffee-script關鍵字的套件

接著我們回到終端機模式的操作npm的指令工具本身就可以完成套

件搜尋的任務

例如以下的指令同樣可以找出 coffee-script相關套件

npm search coffee-script

以下是搜尋結果的參考畫面

52 使用 NPM安裝套件 45

Nodejs中文電子書

找到需要的套件後(例如 express)即可使用以下指令安裝

npm install coffee-script

值得注意的一點是使用 npm install會將指定的套件安裝在工

作目錄(Working Directory)的 node modules資料夾下

以Windows為例如果執行 npm install的目錄位於

Cproject1

那麼 npm將會自動建立一個 node modules的子目錄(如果不存在)

Cproject1node modules

並且將下載的套件放置於這個子目錄例如

Cproject1node modulescoffee-script

這個設計讓專案可以個別管理相依的套件並且可以在專案佈署或發

行時將這些套件(位於 node modules)一併打包方便其它專案的使用者不必再重新下載套件

這個 npm install的預設安裝模式為 local(本地)只會變更當前專案的資料夾不會影響系統

另一種安裝模式稱為 global(全域)這種模式會將套件安裝到系統資

料夾也就是 npm安裝路徑的 node modules資料夾例如

CProgram Filesnodejsnode modules

46 5 NPM套件管理工具

Nodejs中文電子書

是否要使用全域安裝可以依照套件是否提供新指令來判斷舉例來說express 套件提供 express 這個指令而 coffee-script 則提供 coffee

指令

在 local 安 裝 模 式 中這 些 指 令 的 程 式 檔 案會 被 安 裝 到node modules的 bin這個隱藏資料夾下除非將bin的路徑加入 PATH環境變數否則要執行這些指令將會相當不便

為了方便指令的執行我們可以在 npm install 加上 -g 或 --

global參數啟用 global安裝模式例如

npm install -g coffee-script

npm install -g express

使用 global安裝模式需要注意執行權限的問題若權限不足可能會出現類似以下的錯誤訊息

npm ERR Error EACCES permission denied rsquorsquo

npm ERR

npm ERR Please try running this command again as rootAdministrator

要獲得足夠得執行權限請參考以下說明

bull Windows 7 或 2008 以上在「命令提示字元」的捷徑按右鍵選擇「以系統管理員身分執行」執行 npm指令時就會具有 Administrator身分

bull Mac OS X或 Linux系統可以使用 sudo指令例如

sudo npm install -g express

bull Linux系統可以使用 root權限登入或是以「sudo su -」切換成 root身分(使用 root權限操作系統相當危險因此並不建議使用這種方式)

若加上 -g參數使用 npm install -g coffee-script完成安裝

後就可以在終端機執行 coffee指令例如

coffee -v

52 使用 NPM安裝套件 47

Nodejs中文電子書

執行結果(範例)

CoffeeScript version 120

53 套件的更新及維護

除了前一節說明的 search 及 install 用法npm 還提供其他許多指令(commands)

使用 npm help可以查詢可用的指令

npm help

執行結果(部分)

where ltcommandgt is one of

adduser apihelp author bin bugs c cache completion

config deprecate docs edit explore faq find get

help help-search home i info init install la link

list ll ln login ls outdated owner pack prefix

prune publish r rb rebuild remove restart rm root

run-script s se search set show star start stop

submodule tag test un uninstall unlink unpublish

unstar up update version view whoami

使用 npm help command可以查詢指令的詳細用法例如

npm help list

接下來本節要介紹開發過程常用的 npm指令

使用 list可以列出已安裝套件

npm list

48 5 NPM套件管理工具

Nodejs中文電子書

執行結果(範例)

-- coffee-script120

- express256

- connect185

| -- formidable108

-- mime124

-- mkdirp007

-- qs041

檢視某個套件的詳細資訊例如

npm show express

升級所有套件(如果該套件已發佈更新版本)

npm update

升級指定的套件

npm update express

移除指定的套件

npm uninstall express

54 使用 packagejson

對於正式的 Nodejs專案可以建立一個命名為 packagejson的設

定檔(純文字格式)檔案內容參考範例如下

54 使用 packagejson 49

Nodejs中文電子書

packagejson(範例)

rdquonamerdquo rdquoapplication-namerdquo

rdquoversionrdquo rdquo001rdquo

rdquoprivaterdquo true

rdquodependenciesrdquo

rdquoexpressrdquo rdquo255rdquo

rdquocoffee-scriptrdquo rdquolatestrdquo

rdquomongooserdquo rdquogt= 253rdquo

其中 name與 version依照專案的需求設置

需要注意的是 dependencies的設定它用於指定專案相依的套件名

稱及版本

bull rdquoexpressrdquo rdquo255rdquo

代表此專案相依版本 255的 express套件

bull rdquocoffee-scriptrdquo rdquolatestrdquo

使用最新版的 coffee-script套件(每次更新都會檢查新版)

bull rdquomongooserdquo rdquogt= 253rdquo

使用版本大於 253的 mongoose套件

假設某個套件的新版可能造成專案無法正常運作就必須指定套件的

版本避免專案的程式碼來不及更新以相容新版套件通常在開發初期的

專案需要盡可能維持新套件的相容性(以取得套件的更新或修正)可以

用「gt=」設定最低相容的版本或是使用「latest」設定永遠保持最新

套件

50 5 NPM套件管理工具

6

Express介紹

在前面的 nodejs基礎當中介紹許多許多開設 http的使用方法及介紹以及許多基本的 nodejs基本應用

接下來要介紹一個套件稱為 express [Express](httpexpressjscom)這個套件主要幫忙解決許多 nodejs http server所需要的基本服務讓開發 httpservice變得更為容易不需要像之前需要透過層層模組(module)才有辦法開始編寫自己的程式

這個套件是由 TJ Holowaychuk 製作而成的套件裡面包含基本的路由處理 (route)http資料處理(GETPOSTPUT)另外還與樣板套件(jshtml template engine)搭配同時也可以處理許多複雜化的問題

61 Express安裝

安裝方式十分簡單只要透過之前介紹的 NPM就可以使用簡單的指令安裝指令如下

這邊建議需要將此套件安裝成為全域模組方便日後使用

51

Nodejs中文電子書

62 Express基本操作

express的使用也十分簡單先來建立一個基本的 hello world

var app = require(rsquoexpressrsquo)createServer()

port = 1337

applisten(port)

appget(rsquorsquo function(req res)

ressend(rsquohello worldrsquo)

)

consolelog(rsquostart express servernrsquo)

可以從上面的程式碼發現基本操作與 nodejs http的建立方式沒有太大差異主要差在當我們設定路由時可以直接透過 appget方式設定回應與接受方式

63 Express路由處理

Express對於 http服務上有許多包裝讓開發者使用及設定上更為方便例如有幾個路由設定那我們就統一藉由 appget來處理

Create http server

appget(rsquorsquo function(req res)

ressend(rsquohello worldrsquo)

)

appget(rsquotestrsquo function(req res)

ressend(rsquotest renderrsquo)

)

appget(rsquouserrsquo function(req res)

ressend(rsquouser pagersquo)

)

52 6 Express介紹

Nodejs中文電子書

如上面的程式碼所表示appget可以帶入兩個參數第一個是路徑名稱設定第二個為回應函式 (call back function)回應函式裡面就如同之前的 createServer方法裡面包含 requestresponse兩個物件可供使用使用者就可以透過瀏覽器輸入不同的 url切換到不同的頁面顯示不同的結果

路由設定上也有基本的配對方式讓使用者從瀏覽器輸入的網址可以

是一個變數只要符合型態就可以有對應的頁面產出例如

Create http server

appget(rsquouseridrsquo function(req res)

ressend(rsquouser rsquo + reqparamsid)

)

appget(rsquonumberrsquo function(req res)

ressend(rsquonumber rsquo + reqparamsnumber)

)

裡 面 使 用 到number 從 網 址 輸 入 之 後 就 可 以 直 接 使 用reqparamsnumber 取得所輸入的資料變成 url 參數使用當然前面也是可以加上路徑的設定userid在瀏覽器上路徑必須符合userxxx透

過 reqparamsid就可以取到 xxx這個字串值

另外express參數處理也提供了路由參數配對處理也可以透過正規表示法作為參數設定

var app = require(rsquoexpressrsquo)createServer()

port = 1337

applisten(port)

appget(^ip((d23)((d23))((d23))((d23))) function(req res)

ressend(reqparams)

)

上面程式碼可以發現後面路由設定的型態是正規表示法裡面設定

格式為ip之後必須要加上 ip型態才會符合資料格式同時取得 ip資料已經由正規表示法將資料做分群因此可以取得 ip的四個數字

此程式執行之後可以透過瀏覽器測試輸入網址為 localhost3000ip25525510010可以從頁面獲得資料

63 Express路由處理 53

Nodejs中文電子書

此章節全部範例程式碼如下

overview

author Caesar Chi

blog clonnblogspotcom

version 20120226

create server

var app = require(rsquoexpressrsquo)createServer()

port = 1337

applisten(port)

normal style

appget(rsquorsquo function(req res)

ressend(rsquohello worldrsquo)

)

appget(rsquotestrsquo function(req res)

ressend(rsquotest renderrsquo)

)

parameter style

appget(rsquouseridrsquo function(req res)

ressend(rsquouser rsquo + reqparamsid)

)

appget(rsquonumberrsquo function(req res)

ressend(rsquonumber rsquo + reqparamsnumber)

)

REGX style

appget(^ip((d23)((d23))((d23))) function(req res)

ressend(reqparams)

)

appget(rsquorsquo function(req res)

ressend(rsquoPage not foundrsquo 404)

54 6 Express介紹

Nodejs中文電子書

)

consolelog(rsquostart express servernrsquo)

64 Express middleware

Express 裡面有一個十分好用的應用概念稱為 middleware可以透過middleware做出複雜的效果同時上面也有介紹 next方法參數傳遞就是靠 middleware的概念來傳遞參數讓開發者可以明確的控制程式邏輯

create http server

appuse(expressbodyParser())

appuse(expressmethodOverride())

appuse(expresssession())

上面都是一種 middleware的使用方式透過 appuse方式裡面載入函式執行方法回應函式會包含三個基本參數responserequestnext其中next表示下一個 middleware執行函式同時會自動將預設三個參數繼續帶往下個函式執行底下有個實驗

上面的片段程式執行後開啟瀏覽器連結上 localhost1337會發現伺服器回應結果順序如下

first middle ware

second middle ware

execute middle ware

end middleware function

從上面的結果可以得知剛才設定的 middleware都生效了在 appuse設定的 middleware是所有 url皆會執行方法如果有指定特定方法就可以使用 appget的 middleware設定在 appget函式的第二個參數就可以帶入函式或者是匿名函式只要函式裡面最後會接受 request response next這三個參數同時也有正確指定 next函式的執行時機最後都會執行到最後一個方法當然開發者也可以評估程式邏輯要執行到哪一個階段讓邏輯

可以更為分明

64 Express middleware 55

Nodejs中文電子書

65 Express路由應用

在實際開發上可能會遇到需要使用參數等方式混和變數一起使用

express裡面提供了一個很棒的處理方法 appall這個方式可以先採用基本路由配對再將設定為每個不同的處理方式開發者可以透過這個方式簡

化自己的程式邏輯

overview

author

version 20120226

create server

var app = require(rsquoexpressrsquo)createServer()

port = 1337

users = [

name rsquoClonnrsquo

name rsquoChirsquo

]

applisten(port)

appall(rsquouseridoprsquo function(req res next)

requser = users[reqparamsid]

if (requser)

next()

else

next(new Error(rsquocannot find user rsquo + reqparamsid))

)

appget(rsquouseridrsquo function(req res)

ressend(rsquoviewing rsquo + requsername)

)

appget(rsquouserideditrsquo function(req res)

ressend(rsquoediting rsquo + requsername)

)

56 6 Express介紹

Nodejs中文電子書

appget(rsquouseriddeletersquo function(req res)

ressend(rsquodeleting rsquo + requsername)

)

appget(rsquorsquo function(req res)

ressend(rsquoPage not foundrsquo 404)

)

consolelog(rsquostart express servernrsquo)

內部宣告一組預設的使用者分別給予名稱設定藉由 appall這個方法可以先將路由雛形建立再接下來設定 appget的路徑格式只要符合格式就會分配進入對應的方法中像上面的程式當中如果使用者輸入路徑為user0除了執行 appall程式之後執行 next方法就會對應到路徑設定為userid的這個方法當中如果使用者輸入路徑為user0edit就會執行到useridedit的對應方法

66 Express GET應用範例

我們準備一個使用 GET方法傳送資料的表單

ltDOCTYPE htmlgt

lthtmlgt

ltheadgt

ltmeta http-equiv=rdquoContent-Typerdquo content=rdquotexthtml charset=utf8rdquogt

lttitlegtNodejs 菜鳥筆記 (1)lttitlegt

ltlink rel=rdquostylesheetrdquo href=rdquocssstylecssrdquo type=rdquotextcssrdquo media=rdquoallrdquo gt

ltheadgt

ltbodygt

lth1gtNodejs 菜鳥筆記-註冊lth1gt

ltform id=rdquosignuprdquo method=rdquoGETrdquo action=rdquohttplocalhost3000Signuprdquogt

ltlabelgt 使用者名稱ltlabelgtltinput type=rdquotextrdquo id=rdquousernamerdquo name=rdquousernamerdquo gtltbrgt

ltlabelgt 電子郵件ltlabelgtltinput type=rdquotextrdquo id=rdquoemailrdquo name=rdquoemailrdquo gtltbrgt

ltinput type=rdquosubmitrdquo value=rdquo 註冊我的帳號rdquo gtltbrgt

ltformgt

ltbodygt

lthtmlgt

66 Express GET應用範例 57

Nodejs中文電子書

這個表單沒有什麼特別的地方我們只需要看第 9 行form 使用的method是 GET然後 action是rdquohttplocalhost3000Signupldquo等一下我們要來撰寫Signup這個 URL Path的處理程式

處理 Signup行為

我們知道所謂的 GET方法會透過 URL來把表單的值給帶過去以上面的表單來說到時候 URL會以這樣的形式傳遞

httplocalhost3000Signupusername=xxxampemail=xxx

所以要能處理這樣的資料必須有以下功能

bull 解析 URL

bull 辨別動作是 Signup

bull 解析出 username和 email

一旦能取得 username和 email的值程式就能加以應用了

處理 Signup的程式碼雛形

load module

var url = require(rsquourlrsquo)

urlData = urlparse(requrltrue)

action = urlDatapathname

reswriteHead(200 rdquoContent-Typerdquordquotexthtml charset=utf-8rdquo)

if (action === rdquoSignuprdquo)

user = urlDataquery

resend(rdquolth1gtrdquo + userusername + rdquo 歡迎您的加入 lth1gtltpgt 我們已經將會員啟用信寄至rdquo + useremail + rdquoltpgtrdquo)

首先需要加載 url module它是用來協助我們解析 URL的模組接著使用 urlparse方法第一個傳入 url字串變數也就是 requrl另外第二個參數的用意是設為 ture則引進 querystring模組來協助處理預設是 false它影響到的是 urlDataquery設為 true會傳回物件不然就只是一般的字串urlparse會將字串內容整理成一個物件我們把它指定給 urlData

action變數作為記錄 pathname這是我們稍後要來判斷目前網頁的動作是什麼接著先將 html表頭資訊 (Header)準備好再來判斷路徑邏輯如

58 6 Express介紹

Nodejs中文電子書

果是 Signup這個動作就把 urlDataquery裡的資料指定給 user然後輸出userusername和 useremail把使用者從表單註冊的資料顯示於頁面中

最後進行程式測試啟動 nodejs主程式之後開啟瀏覽器就會看到表單填寫完畢按下送出就可以看到結果了

完整 nodejs程式碼如下

var http = require(rsquohttprsquo)

url = require(rsquourlrsquo)

fs = require(rdquofsrdquo)

server

server = httpcreateServer(function (reqres)

var urlData

encode = rdquoutf8rdquo

filePath = rdquoviewexpress_get_example_formhtmlrdquo

action

urlData = urlparse(requrltrue)

action = urlDatapathname

reswriteHead(200 rdquoContent-Typerdquordquotexthtml charset=utf-8rdquo)

if (action === rdquoSignuprdquo)

user = urlDataquery

resend(rdquolth1gtrdquo + userusername + rdquo 歡迎您的加入 lth1gtltpgt 我們已經將會員啟用信寄至rdquo + useremail + rdquoltpgtrdquo)

else

fsreadFile(filePath encode function(err file)

reswrite(file)

resend()

)

)

serverlisten(3000)

consolelog(rsquoServer 跑起來了現在時間是rsquo + new Date())

66 Express GET應用範例 59

Nodejs中文電子書

67 Express POST應用範例

一開始準備基本的 html表單傳送內容以 POST方式form的 action屬性設定為 POST其餘 html內容與前一個範例應用相同

ltDOCTYPE htmlgt

lthtmlgt

ltheadgt

ltmeta http-equiv=rdquoContent-Typerdquo content=rdquotexthtml charset=utf8rdquogt

lttitlegtNodejs 菜鳥筆記 (1)lttitlegt

ltlink rel=rdquostylesheetrdquo href=rdquocssstylecssrdquo type=rdquotextcssrdquo media=rdquoallrdquo gt

ltheadgt

ltbodygt

lth1gtNodejs 菜鳥筆記-註冊lth1gt

ltform id=rdquosignuprdquo method=rdquoPOSTrdquo action=rdquohttplocalhost3000Signuprdquogt

ltlabelgt 使用者名稱ltlabelgtltinput type=rdquotextrdquo id=rdquousernamerdquo name=rdquousernamerdquo gtltbrgt

ltlabelgt 電子郵件ltlabelgtltinput type=rdquotextrdquo id=rdquoemailrdquo name=rdquoemailrdquo gtltbrgt

ltinput type=rdquosubmitrdquo value=rdquo 註冊我的帳號rdquo gtltbrgt

ltformgt

ltbodygt

lthtmlgt

nodejs的程式處理邏輯與前面 GET範例類似部分程式碼如下

qs = require(rsquoquerystringrsquo)

if (action === rdquoSignuprdquo)

formData = rsquorsquo

reqon(rdquodatardquo function (data)

formData += data

)

reqon(rdquoendrdquo function ()

user = qsparse(formData)

resend(rdquolth1gtrdquo + userusername + rdquo 歡迎您的加入 lth1gtltpgt 我們已經將會員啟用信寄至rdquo + useremail + rdquoltpgtrdquo)

)

60 6 Express介紹

Nodejs中文電子書

主要加入了rsquoquerystringrsquo這個moduel方便我們等一下解析由表單 POST回來的資料另外加入一個 formData的變數用來搜集待等一下表單回傳的資料前面的 GET範例我們只從 req拿出 url的資料這次要在利用req身上的事件處理

JavaScript 在訂閱事件時使用 addEventListener而 nodejs 使用的則是on這邊加上了監聽 data的事件會在瀏覽器傳送資料到Web Server時被執行參數是它所接收到的資料型態是字串

接著再增加 end的事件當瀏覽器的請求事件結束時它就會動作

由於瀏覽器使用 POST在上傳資料時會將資料一塊塊地上傳因為我們在監聽 data事件時透過 formData變數將它累加起來lt不過由於我們

上傳的資料很少一次就結束不過如果日後需要傳的是資料比較大的檔

案這個累加動作就很重要

當資料傳完就進到 end 事件中會用到 qsparse 來解析 formDataformData的內容是字串內容是

username=wordsmithampemail=wordsmith40somewhere

而 qsparse可以幫我們把這個 querystring轉成物件的格式也就是

username=wordsmithampemail=wordsmith40somewhere

一旦轉成物件並指定給 user之後其他的事情就和 GET方法時操作的一樣寫 response的表頭將內容回傳並將 userusername和 useremail代入到內容中

修改完成後接著執行 nodejs程式啟動 web server開啟瀏覽器進入表單測試看看POST的方式能否順利運作

完整程式碼如下

var http = require(rsquohttprsquo)

url = require(rsquourlrsquo)

fs = require(rdquofsrdquo)

qs = require(rsquoquerystringrsquo)

server

server = httpcreateServer(function (reqres)

var urlData

67 Express POST應用範例 61

Nodejs中文電子書

encode = rdquoutf8rdquo

filePath = rdquoviewexpress_post_example_formhtmlrdquo

formData

action

urlData = urlparse(requrltrue)

action = urlDatapathname

reswriteHead(200 rdquoContent-Typerdquordquotexthtml charset=utf-8rdquo)

if (action === rdquoSignuprdquo)

formData = rsquorsquo

reqon(rdquodatardquo function (data)

formData += data

)

reqon(rdquoendrdquo function ()

user = qsparse(formData)

resend(rdquolth1gtrdquo + userusername + rdquo 歡迎您的加入 lth1gtltpgt 我們已經將會員啟用信寄至rdquo + useremail + rdquoltpgtrdquo)

)

else

fsreadFile(filePath encode function(err file)

reswrite(file)

resend()

)

)

serverlisten(3000)

consolelog(rsquoServer 跑起來了現在時間是rsquo + new Date())

62 6 Express介紹

Nodejs中文電子書

68 Express AJAX應用範例

在 Nodejs要使用 Ajax傳送資料並且與之互動在接受資料的部份沒有太大的差別client端不是用 GET就是用 POST來傳資料重點在處理完後用 JSON格式回傳當然 Ajax不見得只傳 JSON格式有時是回傳一段 HTML碼不過後者對伺服器來說基本上就和前兩篇沒有差別了所以我們還是以回傳 JSON做為這一回的主題

這一回其實大多數的工作都會落在前端 Ajax上面前端要負責發送與接收資料並在接收資料後撤掉原先發送資料的表單並將取得的資料

改成 HTML格式之後放上頁面

首先先準備 HTML靜態頁面資料

ltDOCTYPE htmlgt

lthtmlgt

ltheadgt

lttitlegtNodejs 菜鳥筆記 (3)lttitlegt

ltlink rel=rdquostylesheetrdquo href=rdquocssstylecssrdquo type=rdquotextcssrdquo media=rdquoallrdquo gt

ltscript src=rdquohttpsajaxgoogleapiscomajaxlibsjquery171jqueryminjsrdquogtltscriptgt

ltheadgt

ltbodygt

lth1gtNodejs 菜鳥筆記-註冊 (用 Ajax)lth1gt

ltform id=rdquosignuprdquo action=rdquoSignuprdquo method=rdquoPOSTrdquogt

ltlabelgt 使用者名稱ltlabelgtltinput type=rdquotextrdquo id=rdquousernamerdquo name=rdquousernamerdquo gtltbrgt

ltlabelgt 電子郵件ltlabelgtltinput type=rdquotextrdquo id=rdquoemailrdquo name=rdquoemailrdquo gtltbrgt

ltinput type=rdquosubmitrdquo value=rdquo 註冊我的帳號rdquo gtltbrgt

ltformgt

ltdiv id=rdquoresultrdquogt

ltdivgt

ltscript type=rdquotextjavascriptrdquogt

$(function()$(rsquosignup input[type=rdquosubmitrdquo]rsquo)click(function(e)

var user =

userusername = $(rdquousernamerdquo)val()useremail = $(rdquoemailrdquo)val()

$post(rdquoSignuprdquouserfunction(data)greet(data)

)

68 Express AJAX應用範例 63

Nodejs中文電子書

epreventDefault()

)

var greet = function(msg)

var resultNode = $(rsquoresultrsquo)greeting = $(rsquolth2gtHirsquo+msgusername+rsquo 你的會員 id 是rsquo+ msgid +rsquo 我們會將會員啟動信寄至rsquo+msgemail+rsquolth2gtrsquo)

$(rsquosignuprsquo)hide()resultNodehtml(rdquordquo)

resultNodeappend(greeting)

)

ltscriptgt

ltbodygt

lthtmlgt

HTML頁面上準備了一個表單用來傳送註冊資料接著直接引用了Google CDN來載入 jQuery用來幫我們處理 Ajax的工作這次要傳送和接收的工作很大的變動都在 HTML頁面上的 JavaScript當中我們要做的事有 (相關 jQuery處理這邊不多做贅述指提起主要功能解說)

bull 用 jQUery取得 submit按鈕綁定它的 click動作

bull 取得表單 username和 email的值存放在 user這個物件中

bull 用 jQuery的 $post方法將 user的資料傳到 Server

bull 一旦成功取得資料後透過 greet這個 function組成回報給 user的訊息

bull 清空原本給使用者填資料的表單

bull 將 Server回傳的 usernameemail和 id這 3個資料組成回應的訊息

bull 將訊息放到原本表單的位置

經過以上的處理後一個 Ajax的表單的基本功能已經完成

接著進行 nodejs主要程式的編輯部分程式碼如下

var fs = require(rdquofsrdquo)

64 6 Express介紹

Nodejs中文電子書

qs = require(rsquoquerystringrsquo)

if (action === rdquoSignuprdquo)

formData = rsquorsquo

reqon(rdquodatardquo function (data)

formData += data

)

reqon(rdquoendrdquo function ()

var msg

user = qsparse(formData)

userid = rdquo123456rdquo

msg = JSONstringify(user)

reswriteHead(200 rdquoContent-Typerdquordquoapplicationjson charset=utf-8rdquordquoContent-Lengthrdquomsglength)

resend(msg)

)

這裡的程式和前面 POST範例基本上大同小異差別在

bull 幫 user的資料加上 id隨意存放一些文字進去讓 Server回傳的資料多於 Client端傳上來的不然會覺得 Server都沒做事

bull 增加了 msg 這個變數存放將 user 物件 JSON 文字化的結果JSONstringify這個轉換函式是 V8引擎所提供的如果你好奇的話

bull 大重點來了我們要告訴 Client端這次回傳的資料格式是 JSON所在 Content-type和 Content-Length要提供給 Client

Server很輕鬆就完成任務了最後進行程式測試啟動 nodejs主程式之後開啟瀏覽器就會看到表單填寫完畢按下送出就可以看到結果了

最後 nodejs本篇範例程式碼如下

var http = require(rsquohttprsquo)

url = require(rsquourlrsquo)

fs = require(rdquofsrdquo)

qs = require(rsquoquerystringrsquo)

server

68 Express AJAX應用範例 65

Nodejs中文電子書

server = httpcreateServer(function (reqres)

var urlData

encode = rdquoutf8rdquo

filePath = rdquoviewexpress_ajax_example_formhtmlrdquo

formData

action

urlData = urlparse(requrltrue)

action = urlDatapathname

if (action === rdquoSignuprdquo)

formData = rsquorsquo

reqon(rdquodatardquo function (data)

formData += data

)

reqon(rdquoendrdquo function ()

var msg

user = qsparse(formData)

userid = rdquo123456rdquo

msg = JSONstringify(user)

reswriteHead(200 rdquoContent-TyperdquordquoapplicationjsonrdquordquoContent-Lengthrdquomsglength)

resend(msg)

)

else

fsreadFile(filePath encode function(err file)

reswriteHead(200 rdquoContent-Typerdquordquotexthtml charset=utf-8rdquo)

reswrite(file)

resend()

)

)

serverlisten(3000)

66 6 Express介紹

Nodejs中文電子書

consolelog(rsquoServer 跑起來了現在時間是rsquo + new Date())

69 原始資料提供

bull [NodeJS初學者筆記 (1)-用 GET傳送資料] (httpithelpithomecomtwquestion10087402)

bull [NodeJS初學者筆記 (2)-用 POST傳送資料] (httpithelpithomecomtwquestion10087489)

bull [NodeJS 初學者筆記 (3)-用 Ajax 傳送資料] (httpithelpithomecomtwquestion10087627)

69 原始資料提供 67

Nodejs中文電子書

68 6 Express介紹

7

CoffeeScript

bull Spine

bull Hem

bull Spineapp

Letrsquos Have a Cup of CoffeeScript

bull es5-shim ECMAScript 5 compatibility shims for legacy JavaScript engines

ESnext

node-iform - A middleware for node-validator httpsgithubcomguileennode-iform

69

Nodejs中文電子書

70 7 CoffeeScript

8

製作一個 Hubot的 Plurk Adapter

81 應用事項提醒

此應用範例以CoffeeScript編寫主要程式架構採用Github釋放的Hubot專案以下有幾個主要注意事項請讀者注意

bull Hubot 一開始的架構除了內建的兩個 Adapter 之外其餘都要以Nodejs的Module方式才能運作

bull Hubot的 binhubot裡面寫著 npm install所以不管你怎麼改原始碼也不能改變第一點的狀況

其實上述都在討論同一件事情NodeJS的模組而且模組不能設定相對路徑之類的來安裝一定要包裝成 npm相符和格式才有辦法正確執行

82 建立 Adapter

首先需要準備兩個檔案

bull packagejson

bull plurkcoffee

71

Nodejs中文電子書

有這兩個就足以變成 NodeJS的模組了在開始 Coding之前先來設定一下 packagejson相依性

rdquonamerdquo rdquohubot-plurkrdquo

rdquoversionrdquo rdquo011rdquo

rdquomainrdquo rdquoplurkrdquo

rdquodependenciesrdquo

rdquohubotrdquo rdquogt=205rdquo

rdquooauthrdquo rdquordquo

rdquocronrdquordquordquo

這邊會使用到NodeJS的 cron模組(Module)而登入 Plurk需要OAuth標準授權才行所以也需要加載 OAuth模組

注意Hubot在讀取非內建模組時會自動在前面加上 hubot-的前置

83 建立 Robot跟 API

本篇應用基本上程式碼大部分參考 Hubot - Twitter Adapter來製作主要差異只有在使用 OAuth這個模組Twitter有 Streaming可用而 Plurk則得用 Comet方式來達到即時讀取

plurkcoffee程式碼

Robot = require(rdquohubotrdquo)robot()

Adapter = require(rdquohubtordquo)adapter()

EventEmitter = require(rdquoeventsrdquo)EventEmitter

oauth = require(rdquooauthrdquo)

cronJob = require(rdquocronrdquo)CronJob

class Plurk exntends Adapter

class PlurkStreaming exnteds EventEmitter

72 8 製作一個 Hubot的 Plurk Adapter

Nodejs中文電子書

先弄個基本架構主要 Class皆參考 Twitter Adapter命名方式接著再將 Plurk這個 Class增加幾個Method基本上只要有 run send reply就夠了而 run用來做初始化的部分

class Plurk entends Adapter

send (plurk id strings⋯) -gt

reply (plurk id strings⋯) -gt

run -gt

看起來有點東西接著來處理主要的 Plurk API結合部分

class PlurkStreaming extends EventEmitter

consuctor (options) -gt

plurk (callback) -gt

觀察河道

getChannel -gt

取得 Comet 網址

reply (plurk id message) -gt

回噗

acceptFriends -gt

接受好友

get (path callback) -gt

GET 請求

post (path body callback)-gt

POST 請求(其實是裝飾)

request (method path body callback)-gt

主要的 OAuth 請求

comet (server callback)-gt

噗浪的 Comet 傳回是 JavaScript Callback 要另外處理後才會變成 JSON

然後把注意力集中到 constructor上先把建構子弄好

constructor (options) -gt

super()

if optionskey and optionssecret and optionstoken and optionstoken secret

key = optionskey

secret = optionssecret

token = optionstoken

token secret = optionstoken secret

83 建立 Robot跟 API 73

Nodejs中文電子書

建立 OAuth 連接

consumer = new oauthOAuth(

rdquohttpwwwplurkcomOAuthrequest tokenrdquo

rdquohttpwwwplurkcomOAuthaccess tokenrdquo

key

secret

rdquo10rdquo

rdquohttpwwwplurkcomOAuthauthorizerdquo

rdquoHMAC-SHA1rdquo

)

domain = rdquowwwplurkcomrdquo

初始化取得 Comet 網址

do getChannel

else

throw new Error(rdquo 參數不足需要 Key Secret Token Token Secretrdquo)

接著來處理 request這個 method

request (method path body callback) -gt

記錄一下這次的 Request

consolelog(rdquohttpdomainpathrdquo)

Callback 這邊先不丟進去要用另一種方式處理

request = consumerget(rdquohttpdomainpathrdquo token token secret null)

requeston rdquoresponserdquo (res) -gt

reson rdquodatardquo (chunk) -gt

parseResponse(chunk+rsquorsquo callback)

reson rdquoendrdquo (data) -gt

consolelog rdquoEnd Request pathrdquo

reson rdquoerrorrdquo (data) -gt

consolelog rdquoError rdquo + data

requestend()

處理資料

parseResponse = (data callback) -gt

if datalength gt 0

用 TryCatch 避免處理 JSON 出錯導致整個中斷

try

callback null JSONparse(data)

catch err

74 8 製作一個 Hubot的 Plurk Adapter

Nodejs中文電子書

consolelog(rdquoError Parse JSONrdquo + data err)

繼續執行

callback null data ||

大致上就是這樣上面程式的架構已經將整個 Hubot Plurk Adapter完成因為在測試時竟然因為噗浪 Lag而沒讀到完整的 Comet資料然後造成程式異常為了避免這個問題發生因此需要加上為了完美呈現需要再

加上 Comet的處理所以要使用到 EventEmitter的功能

comet (server callback) -gt

在 Callback 裡面會找不到自身所以設定區域變數

self =

記錄一下這次的 Request

consolelog(rdquo[Comet] serverrdquo)

Callback 這邊先不丟進去要用另一種方式處理

request = consumerget(rdquohttpdomainpathrdquo token token secret null)

requeston rdquoresponserdquo (res) -gt

reson rdquodatardquo (chunk) -gt

parseResponse(chunk+rsquorsquo callback)

reson rdquoendrdquo (data) -gt

consolelog rdquoEnd Request pathrdquo

請求結束發出事件通知可以進行下一次請求

selfemit rdquonextPlurkrdquo

reson rdquoerrorrdquo (data) -gt

consolelog rdquoError rdquo + data

requestend()

處理資料

parseResponse = (data callback) -gt

if datalength gt 0

用 trycatch 避免失敗中斷

try

去掉 JavaScript 的 Callback

data = datamatch(CometChannelscriptCallback((+))s)

jsonData = rdquordquo

if data

83 建立 Robot跟 API 75

Nodejs中文電子書

jsonData = JSONparse(data[1])

else

如果沒有任何 Match 嘗試直接 parse

jsonData = JSONparse(data)

catch err

consolelog(rdquo[Comet] Errorrdquo data err)

用 TryCatch 避免處理 JSON 出錯導致整個中斷

try

只傳入 json 的 data 部分

callback null jsonDatadata

catch err

consolelog(rdquo[Comet]Error Parse JSONrdquo + data err)

繼續執行

callback null data ||

後面的 get跟 post就簡單多了

get (path callback) -gt

request(rdquoGETrdquo path null callback)

post (path body callback) -gt

request(rdquoPOSTrdquo path body callback)

接著處理取的 Comet網址的 getChannel

getChannel -gt

self =

get rdquoAPPRealtimegetUserChannelrdquo (error data) -gt

if error

檢查是否有 comet server

if datacomet server

selfchannel = datacomet server

如果沒有 Channel Ready 就嘗試連接會失敗

selfemit(rsquochannel readyrsquo)

那麼先來處理 Plurk Adaper好處理的部份

send (plruk id strings⋯)-gt

跟 Reply 一樣直接交給 reply 做

reply plurk id strings⋯

76 8 製作一個 Hubot的 Plurk Adapter

Nodejs中文電子書

reply (plurk id strings⋯) -gt

stringsforEach (message) =gt

botreply(plruk id message)

接著把 run處理好就可以上線運作摟

run -gt

self =

options =

key processenvHUBOT PLURK KEY

secret processenvHUBOT PLURK SECRET

token processenvHUBOT PLURK TOKEN

token secret processenvHUBOT PLURK TOKEN SECRET

創建剛剛的 API

bot = new PlurkStreaming(options)

依照 Twitter 的 new RobotTextMessage 會沒有反應所以參考 hubot-minecraft 的方式

r = robotconstructor

處理噗浪河道訊息

doPlurk = (data)-gt

檢查是否為回噗

if dataresponse

datacontent raw = dataresponsecontent raw

datauser id = dataresponseuser id

確定有噗浪 ID 跟訊息

if dataplurk id and datacontent raw

selfreceive new rTextMessage(dataplurk id datacontent raw)

取得 Comet Server 完成開始第一次 Comet 連接

boton rdquochannel readyrdquo () -gt

botplurk selfdoPlurk

上一次 Comet 完成繼續 Polling

boton rdquonextPlurkrdquo ()-gt

botplurk selfdoPlurk

定時接受好友邀請

do botacceptFriends

bot = bot

83 建立 Robot跟 API 77

Nodejs中文電子書

終於完成 AdapterHubot專案裡面的 scripts資料夾內是互動部分不需要像 Adapter如此大費周章處理只新增檔案並且設計好對白之後就會回噗了我開發用的機器人在此大家可以去跟他玩玩[httpplurkcomelct9620 bot](httpplurkcomelct9620 bot)

84 原始資料提供

bull [製 作 一 個 Hubot 的 噗 浪 Adapter](http revo-skillfrosttw blog20120318create-a-hubot-plurk-adapter)

78 8 製作一個 Hubot的 Plurk Adapter

9

Nodejs好用工具介紹

91 logio

Real-time log monitoring in your browser

httplogioorg

nodejs (gt=0412 lt=0611)

npm config set unsafe-perm true

npm install -g --prefix=usrlocal logio

logio server start

httplocalhost8998

79

Nodejs中文電子書

80 9 Nodejs好用工具介紹

10

精選文章

101 Express

bull 使用 NodeJS + Express從 GETPOST Request取值 [邀稿中]

bull link2 [邀稿中]

bull link3 [邀稿中]

81

Nodejs中文電子書

82 10 精選文章

11

參考資源

111 Nodejs書籍

bull Programming Nodejs April 2012 (est) Travis Swicegood Pragmatic Bookshelf

bull e Little Book on CoffeeScript January 2012 Alex MacCaw OrsquoReilly Media

bull Node for Front-End Developers January 2012 Garann Means OrsquoReilly Media

bull Building Hypermedia APIs with HTML5 and Node November 2011 MikeAmundsen OrsquoReilly Media

bull Node Web Development August 2011 David Herron PacktPub

bull CoffeeScript Accelerated JavaScript Development July 2011 Trevor BurnhamPragmatic Bookshelf

bull Getting Started with GEO CouchDB and Nodejs July 2011 Mick omp-son OrsquoReilly Media

bull What Is Node July 2011 Brett McLaughlin OrsquoReilly Media

bull Node Up and Running May 2011 Tom Hughes-Croucher Mike WilsonOrsquoReilly Media

83

Nodejs中文電子書

bull Hands-on Nodejs e Nodejs introduction and API reference May 2011 Pe-dro Teixeira LeanPub

bull e Node Beginner Book Manuel Kiessling

bull Mastering Nodejs visionmedia

112 Nodejs影音教學

bull e Node Sessions e Best of OSCON 2011 August 2011 OrsquoReilly Media(Video)

bull Tom Hughes-Croucher on Node March 2011 OrsquoReilly Media (Video)

113 Nodejs教學網站

bull Node Tuts

114 Nodejs課程

bull Introduction To Nodejs Van Nguyen CodeLesson

84 11 參考資源

Page 21: Nodejs Wiki

Nodejs中文電子書

就可以對各個 handler做單元測試了例如

var http = require(lsquohttprsquo) var tools =

cookieParser function(request response) if(requestheaders[rsquoCookiersquo]) do parsing

var server = httpcreateServer(function(request response)

thisemit(lsquoinitrsquo request response)

) serveron(lsquoinitrsquo toolscookieParser) serverlisten(8080 lsquo127001rsquo)

更進一步可以把 tools改成外部 module例如叫做 toolsjs

moduleexports = cookieParser function(request response) if(requestheaders[rsquoCookiersquo]) do parsing

然後把程式改成

var http = require(lsquohttprsquo)

var server = httpcreateServer(function(request response) thisemit(lsquoinitrsquo re-quest response)

) serveron(lsquoinitrsquo require(lsquo toolsrsquo)cookieParser) serverlisten(8080lsquo127001rsquo)

這樣就可以單元測試 cookieParser了例如使用 nodeunit時可以這樣寫

var testCase = require(lsquonodeunitrsquo)testCase moduleexports = testCase(

ldquosetUprdquo function(cb) thisrequest = headers Cookielsquoname1val1 name2val2rsquo thisresponse = thisresult= name1rsquoval1rsquoname2rsquoval2rsquo

cb()

ldquotearDownrdquo function(cb)

cb()

23 Callback 15

Nodejs中文電子書

ldquonormal caserdquo function(test)

testexpect(1) var obj = require(lsquotoolsrsquo)cookieParser(thisrequest thisresponse)testdeepEqual(obj thisresult) testdone()

)

善於利用模組可以讓程式更好維護與測試

24 CPS(Continuation-Passing Style)

cps 是 callback 使用上的特例形式上就是在函數最後呼叫 callback這樣就好像把函數執行後把結果交給 callback 繼續運行所以稱作continuation-passing style利用 cps可以在非同步執行的情況下透過傳給 callback的這個 cps callback來獲知 callback執行完畢或是取得執行結果例如

lthtmlgt ltbodygt ltdiv id=rdquopanelrdquo style=rdquovisibilityhiddenrdquogtlt divgtlt bodygt lt htmlgt ltscriptgt var request = new XMLHttpRequest() re-questopen(lsquoGETrsquo lsquotest749txt timestamp=rsquo+new Date()getTime() true) re-questaddEventListener(lsquoreadystatechangersquo function(next)

return function() if(thisreadyState===4ampampthisstatus===200) next(thisresponseText)lt== 傳入的 cps callback 在動作完成時執行並取得結果進一步處理

(function(str)lt==這個匿名函數就是 cps callback

documentgetElementById(lsquopanelrsquo)innerHTML=str docu-mentgetElementById(lsquopanelrsquo)stylevisibility = lsquovisiblersquo

) false) requestsend() ltscriptgt

進一步的應用也可以參考 2-6流程控制

16 2 JavaScript與 NodeJS

Nodejs中文電子書

25 函數返回函數與 Currying

前面的 cps範例裡面使用了函數返回函數這是為了把 cps callback傳遞給 onreadystatechange事件處理函數的方法(因為這個事件處理函數並沒有設計好會傳送接收這樣的參數)實際會執行的事件處理函數其實是內層返回的那個函數之外包覆的這個函數主要是為了利用 Closure把 next傳給內層的事件處理函數這個方法更常使用的地方是為了解決一些

scope問題例如

ltscriptgt var accu=0count=10 for(var i=0 iltcount i++)

setTimeout(

function() countndash accu+=i if(countlt=0)

consolelog(accu)

50)

ltscriptgt

最後得出的結果會是 100而不是想像中的 45這是因為等到 setTime-out指定的函數執行時變數 i已經變成 10而離開迴圈了要解決這個問題就需要透過 Closure來保存變數 i

ltscriptgt var accu=0count=10 for(var i=0 iltcount i++)

setTimeout(

function(i) return function() countndash

accu+=i if(countlt=0)

consolelog(accu)

(i)

50)

25 函數返回函數與 Currying 17

Nodejs中文電子書

淺藍色底色的部份是跟上面例子不一樣的地方 ltscriptgt

函數返回函數的另外一個用途是可以暫緩函數執行例如

function add(m n) return m+n

var a = add(20 10) consolelog(a)

add這個函數必須同時輸入兩個參數才有辦法執行如果我希望這個函數可以先給它一個參數等一些處理過後再給一個參數然後得到結

果就必須用函數返回函數的方式做修改

function add(m)

return function(n) return m+n

var wait another arg = add(20)先給一個參數 var a = function(arr)

var ret=0 for(var i=0iltarrlengthi++) ret+=arr[i] return ret

([1234])計算一下另一個參數 var b = wait another arg(a)然後再繼續執行 consolelog(b)

像這樣利用函數返回函數使得原本接受多個參數的函數可以一次

接受一個參數直到參數接收完成才執行得到結果的方式有一個學名就

叫做Currying

綜合以上許多奇技淫巧就可以透過用函數來處理函數的方式調整

程式流程接下來看看

26 流程控制

(以 sync方式使用 async函數避開巢狀 callback循序呼叫 async callback等奇技淫巧)

建議參考

bull httphowtonodeorgcontrol- ow

bull httphowtonodeorgcontrol- ow-part-ii

18 2 JavaScript與 NodeJS

Nodejs中文電子書

bull httphowtonodeorgcontrol- ow-part-iii

bull httpblogmixunet20110202essential-node-js-patterns-and-snippets

這幾篇都是非常經典的 NodeJSJavascript流程控制好文章(阿mixu是在介紹一些 pattern時提到這方面的主題)不過我還是用幾個簡單的程式介紹一下做法跟概念

並發與等待

下面的程式參考了 mixu文章中的做法

var wait = function(callbacks done) consolelog(lsquowait startrsquo) var counter = call-backslength var results = [] var next = function(result) 接收函數執行結果並判斷是否結束執行 resultspush(result) if(ndashcounter == 0) done(results)如果結束執行就把所有執行結果傳給指定的 callback處理 for(var i = 0 i lt callbackslength i++) 依次呼叫所有要執行的函數 callbacks[i](next) consolelog(lsquowait endrsquo)

wait( [ function(next) setTimeout(function() consolelog(lsquodone arsquo) var result =500 next(result) 500) function(next) setTimeout(function() con-solelog(lsquodone brsquo) var result = 1000 next(result) 1000) function(next)setTimeout(function() consolelog(lsquodone crsquo) var result = 1500 next(1500) 1500) ] function(results) var ret = 0 i=0 for( iltresultslength i++) ret+= results[i] consolelog(lsquodone all result lsquo+ret)

)

執行結果wait start wait end done a done b done c done all result 3000

可以看出來其實 wait並不是真的等到所有函數執行完才結束執行而是在所有傳給他的函數執行完畢後(不論同步非同步)才執行處理結

果的函數(也就是 done())

不過這樣的寫法還不夠實用因為沒辦法實際讓函數可以等待執行

完畢又能當作事件處理函數來實際使用上面參考到的 Tim Caswell的文

26 流程控制 19

Nodejs中文電子書

章裡面有一種解法不過還需要額外包裝(在他的例子中)NodeJS核心的 fs物件把一些函數(例如 readFile)用 Currying處理類似像這樣

var fs = require(lsquofsrsquo) var readFile = function(path)

return function(callback errback)

fsreadFile(path function(err data)

if(err) errback()

else callback(data)

)

其他部份可以參考 Tim Caswell的文章他的 Doparallel跟上面的 wait差不多意思這裡只提示一下他沒說到的地方

另外一種做法是去修飾一下 callback當他作為事件處理函數執行後再用 cps的方式取得結果

ltscriptgt function Wait(fns done)

var count = 0 var results = [] thisgetCallback = function(index)

count++ return (function(waitback)

return function() var i=0args=[]for(iltargumentslengthi++)

argspush(arguments[i])

argspush(waitback) fns[index]apply(thisargs)

)(function(result) resultspush(result) if(ndashcount == 0)

20 2 JavaScript與 NodeJS

Nodejs中文電子書

done(results)

)

var a = new Wait(

[ function(waitback) consolelog(lsquodone arsquo) var result = 500 wait-back(result) function(waitback) consolelog(lsquodone brsquo) var result =1000 waitback(result) function(waitback) consolelog(lsquodone crsquo) varresult = 1500 waitback(result) ] function(results) var ret = 0 i=0for( iltresultslength i++) ret += results[i] consolelog(lsquodone allresult lsquo+ret)

) var callbacks = [agetCallback(0)agetCallback(1)agetCallback(0)agetCallback(2)]一次取出要使用的 callbacks避免結果提早送出 setTimeout(callbacks[0]500) setTimeout(callbacks[1] 1000) setTimeout(callbacks[2] 1500) setTime-out(callbacks[3] 2000) 當所有取出的 callbacks執行完畢就呼叫 done()來處理結果 ltscriptgt

執行結果

done a done b done a done c done all result 3500

上面只是一些小實驗更成熟的作品是 Tim Caswell 的 stephttpsgithubcomcreationixstep

如果希望真正使用同步的方式寫非同步則需要使用 Promisejs這一類的 library來轉換非同步函數不過他結構比較複雜 XD(見仁見智不過有些人認為 Promise有點過頭了)httpblogsmsdncombrbucktonarchive20110815promise-js-2-0-promise-framework-for-javascriptaspx

如果想不透過其他 Library做轉換又能直接用同步方式執行非同步函數大概就要使用一些需要額外 compile原始程式碼的方法了例如 BrunoJouhier的 streamlinejshttpsgithubcomSagestreamlinejs

26 流程控制 21

Nodejs中文電子書

循序執行

循序執行可以協助把非常深的巢狀 callback結構攤平例如用這樣的簡單模組來做(serialjs)

moduleexports = function(funs) var c = 0 if(isArrayOfFunctions(funs))

throw(lsquoArgument type was not matched Should be array of func-tionsrsquo)

return function()

var args = Arrayprototypeslicecall(arguments 0) if((cgt=funslength))

c++ return funs[c-1]apply(this args)

function isArrayOfFunctions(f ) if(typeof f == lsquoobjectrsquo) return false if(flength)return false if(fconcat) return false if(fsplice) return false var i = 0 for(iltflength i++)

if(typeof f[i] == lsquofunctionrsquo) return false

return true

簡單的測試範例(testSerialjs)使用 fs 模組確定某個 path 是檔案然後讀取印出檔案內容這樣會用到兩層的 callback所以測試中有使用serial的版本與 nested callbacks的版本做對照

var serial = require(lsquoserialrsquo) fs = require(lsquofsrsquo) path = lsquodclientjsrsquo cb = serial([ func-tion(err data)

if(err)

if(dataisFile) fsreadFile(path cb)

22 2 JavaScript與 NodeJS

Nodejs中文電子書

else consolelog(err)

function(err data)

if(err) consolelog(lsquo[ attened by searial]rsquo) con-solelog(datatoString(lsquoutf8rsquo))

else consolelog(err)

]) fsstat(path cb)

fsstat(path function(err data) 第一層 callback if(err)

if(dataisFile)

fsreadFile(path function(err data) 第二層 callback if(err)

consolelog(lsquo[nested callbacks]rsquo) con-solelog(datatoString(lsquoutf8rsquo))

else consolelog(err)

)

else consolelog(err)

)

關鍵在於這些 callback的執行是有順序性的所以利用 serial返回的一個函數 cb來取代這些 callback然後在 cb中控制每次會循序呼叫的函數

26 流程控制 23

Nodejs中文電子書

就可以把巢狀的 callback攤平成循序的 function陣列(就是傳給 serial函數的參數)

測試中的dclientjs 是一個簡單的 dnode 測試程式放在跟 testSerialjs同一個目錄

var dnode = require(lsquodnodersquo)

dnodeconnect(8000 lsquolocalhostrsquo function(remote)

remoterestart(function(str) consolelog(str) processexit()

)

)

執行測試程式後出現結果

[ attened by searial] var dnode = require(lsquodnodersquo)

dnodeconnect(8000 lsquolocalhostrsquo function(remote)

remoterestart(function(str) consolelog(str) processexit()

)

)

[nested callbacks] var dnode = require(lsquodnodersquo)

dnodeconnect(8000 lsquolocalhostrsquo function(remote)

remoterestart(function(str) consolelog(str) processexit()

)

)

對照起來看兩種寫法的結果其實是一樣的但是利用 serialjs巢狀的 callback結構就會消失

不過這樣也只限於順序單純的狀況如果函數執行的順序比較複雜

(不只是一直線)還是需要用功能更完整的流程控制模組比較好例如

httpsgithubcomcaolanasync

24 2 JavaScript與 NodeJS

3

Nodejs安裝與設定

本篇將講解如何在各個不同 OS建立 NodeJS環境目前 NodeJS 048版本環境架設方式需依賴 Linux指令才可編譯完成當然在不同作業系統中也已經有 NodeJS package可以直接使用指令快速架設以下各不同作業系統解說如何安裝 NodeJS

31 Ubuntu Linux

更新推薦使用 nvm

git clone gitgithubcomcreationixnvmgit ~nvm

echo rdquo ~nvmnvmshrdquo gtgt ~bashrc

nvm install v0614

nvm alias default v0614

以 上 可 參 考 http dreamerslabcom blog tw how-to-setup-a-node-js-development-environment-on-ubuntu-11-04

使用 APT套件管理工具是常見的方法以下是使用社群提供的 PPA安裝方式

sudo apt-get install python-software-properties

sudo add-apt-repository ppachris-leanodejs-devel

25

Nodejs中文電子書

sudo apt-get update

sudo apt-get install nodejs

32 Other Linux

Linux很適合作為 NodeJS的伺服器作業系統及開發環境安裝前請先確認以下套件已正確安裝

bull curl (wget)用來下載檔案的工具

bull git先進的版本控制工具

bull g++ GNU C++軟體編譯工具

bull make GNU軟體專案建置工具

安裝指令如下如設有權限問題請在指令前面加上 sudo

git clone httpsgithubcomjoyentnodegit

cd node

git checkout v067

configure

make

sudo make install

接著測試 nodeJS是否正常執行

node --version

出現版本訊息即表示安裝成功

33 Windows

nodeJS 在 v060 版本之後開始正式支援 windows native直接使用nodeexe 就可以執行程式支援性完全與 linux 相同更棒的部份就是不需經過編譯經過下載之後簡單設定完成立即開發 node程式

26 3 Nodejs安裝與設定

Nodejs中文電子書

下載 nodejs安裝檔案 lthttpnodejsorgdownloadgt

如此完成 windows native nodeexe安裝接著可以進入 command line執行測試在 command line輸入rdquonoderdquo會出現 nodejs版本訊息畫面表示安裝完成

33 Windows 27

Nodejs中文電子書

28 3 Nodejs安裝與設定

4

Nodejs基礎

前篇文章已經由介紹安裝至設定都有完整介紹nodeJS 內部除了javascript常用的函式 (function)物件 (object)之外也有許多不同的自訂物件nodeJS預設建立這些物件為核心物件是為了要讓開發流程更為這些資料在官方文件已經具有許多具體說明接下來將會介紹在開發 nodeJS程式時常見的物件特性與使用方法

41 nodejs http伺服器建立

在 lsquonodejs官方網站 lthttpnodejsorggtlsquo裡面有舉一個最簡單的 HTTP伺服器建立一開始初步就是建立一個伺服器平台讓 nodejs可以與瀏覽器互相行為每種語言一開始的程式建立都是以 Hello world開始最初也從 Hello world帶各位進入 nodejs的世界

輸入以下程式碼儲存檔案為 node basic http hello worldjs

var server

ip = rdquo127001rdquo

port = 1337

http = require(rsquohttprsquo)

server = httpcreateServer(function (req res)

29

Nodejs中文電子書

reswriteHead(200 rsquoContent-Typersquo rsquotextplainrsquo)

resend(rsquoHello Worldnrsquo)

)

serverlisten(port ip)

consolelog(rdquoServer running at httprdquo + ip + rdquordquo + port)

程式碼解講一開始需要有幾個基本的變數

bull ip 機器本身的 ip位置因為使用本地端因此設定為 127001

bull port 需要開通的阜號通常設定為 http port 80因範例不希望與基本 port相衝隨意設定為 1337

在 nodejs 的程式中有許多預設的模組可以使用因此需要使用require方法將模組引入在這邊我們需要使用 http這個模組因此將 http載入Http模組裡面內建有許多方法可以使用這邊採用 createServer創建一個基本的 http伺服器再將 http伺服器給予一個 server變數裡面的回呼函式 (call back function)可以載入 http伺服器的資料與回應方法 (requestresponse)在程式裡面就可以看到我們直接回應給瀏覽器端所需的 Header回應內容

reswriteHead(200 rsquoContent-Typersquo rsquotextplainrsquo)

resend(rsquoHello Worldnrsquo)

Http伺服器需要設定 port ip在最後需要設定 Http監聽需要使用到listen事件監聽所有 Http伺服器行為

httplisten(port ip)

所有事情都完成之後需要確認伺服器正確執行因此使用 console在javascript裡就有這個原生物件console所印出的資料都會顯示於 nodejs伺服器頁面這邊印出的資料並不會傳送到使用者頁面上之後許多除壞

(debug)都會用到 console物件

consolelog(rdquoServer running at httprdquo + ip + rdquordquo + port)

30 4 Nodejs基礎

Nodejs中文電子書

42 nodejs http路徑建立

前面已經介紹如何建立一個簡單的 http伺服器接下來本章節將會介紹如何處理伺服器路徑 (rout)問題在 http的協定下所有從瀏覽器發出的要求 (request)都需要經過處理路徑上的建立也是如此

路徑就是指伺服器 ip位置或者是網域名稱之後對於伺服器給予的要求修改剛才的 hello world檔案修改如下

server = httpcreateServer(function (req res)

consolelog(requrl)

reswriteHead(200 rsquoContent-Typersquo rsquotextplainrsquo)

resend(rsquohello worldnrsquo)

)

重新啟動 nodejs程式後在瀏覽器端測試一下路徑行為結果如下圖

當在瀏覽器輸入 http1270011337test 在伺服器端會收到兩個要求一個是我們輸入的test要求另外一個則是faviconicotest的路徑要求http伺服器本身需要經過程式設定才有辦法回應給瀏覽器端所需要的回應在伺服器中所有的路徑要求都是需要被解析才有辦法取得資料從

42 nodejs http路徑建立 31

Nodejs中文電子書

上面解說可以了解到在 nodejs當中所有的路徑都需要經過設定未經過設定的路由會讓瀏覽器無法取得任何資料導致錯誤頁面的發生底下將會解

說如何設定路由同時避免發生錯誤情形先前 nodejs程式需要增加一些修改才能讓使用者透過瀏覽器在不同路徑時有不同的結果根據剛才

的程式做如下的修改

var server

ip = rdquo127001rdquo

port = 1337

http = require(rsquohttprsquo)

url = require(rsquourlrsquo)

path

server = httpcreateServer(function (req res)

path = urlparse(requrl)

reswriteHead(200 rsquoContent-Typersquo rsquotextplainrsquo)

switch (pathpathname)

case rdquoindexrdquo

resend(rsquoI am indexnrsquo)

break

case rdquotestrdquo

resend(rsquothis is test pagenrsquo)

break

default

resend(rsquodefault pagenrsquo)

break

)

serverlisten(port ip)

consolelog(rdquoServer running at httprdquo + ip + rdquordquo + port)

程式做了片段的修改首先載入 url模組另外增加一個 path變數url模組就跟如同他的命名一般專門處理 url字串處理裡面提供了許多方法來解決路徑上的問題因為從瀏覽器發出的要求路徑可能會帶有多種需求

或者 GET參數組合等因此我們需要將路徑單純化取用路徑部分的資料即可例如使用者可能會送出 http1270011337testsend=1如果直接

32 4 Nodejs基礎

Nodejs中文電子書

信任 requrl就會收到結果為testsend=1所以需要透過 url模組的方法將路徑資料過濾

在這邊使用 urlparse的方法裡面帶入網址格式資料會回傳路徑資料為了後需方便使用將回傳的資料設定到 path變數當中在回傳的路徑資料裡面包含資訊如下圖

這邊只需要使用單純的路徑要求直接取用 pathpathname就可以達到我們的目的

最後要做路徑的判別在不同的路徑可以指定不同的輸出在範例中

有三個可能結果第一個從瀏覽器輸入index就會顯示 index結果test 就會呈現出 test頁面最後如果都不符合預期的輸入會直接顯示 default的頁面最後的預防可以讓瀏覽器不會出現非預期結果讓程式的可靠性提昇

底下為測試結果

42 nodejs http路徑建立 33

Nodejs中文電子書

43 nodejs檔案讀取

前面已經介紹如何使用路由(rount)做出不同的回應實際應用只有在瀏覽器只有輸出幾個文字資料總是不夠的在本章節中將介紹如何使

用檔案讀取輸出檔案資料讓使用者在前端瀏覽器也可以讀取到完整的

html css javascript檔案輸出

檔案管理最重要的部分就是 lsquoFile system lthttpnodejsorgdocslatestapifshtmlgtlsquo這個模組此模組可以針對檔案做管理監控讀取等行為裡面有許多預設的方法底下是檔案輸出的基本範例底下會有兩個檔案

第一個是靜態 html檔案

ltDOCTYPE htmlgt

lthtml xmlns=rdquohttpwwww3org1999xhtmlrdquo xmllang=rdquozh-CNrdquo lang=rdquozh-CNrdquogt

ltheadgt

ltmeta http-equiv=rdquoContent-Typerdquo content=rdquotexthtml charset=UTF-7rdquo gt

lttitlegtnodejs index html filelttitlegt

ltheadgt

ltbodygt

34 4 Nodejs基礎

Nodejs中文電子書

lth1gtnodejs index html filelth1gt

ltbodygt

lthtmlgt

另一個為 nodejs程式

var fs = require(rdquofsrdquo)

filename = rdquostaticindexhtmlrdquo

encode = rdquoutf8rdquo

fsreadFile(filename encode function(err file)

consolelog(file)

)

一開始直接載入 le system模組 載入名稱為 fs讀取檔案主要使

用的方法為 readFile裡面以三個參數路徑 ( le path) 編碼方式 (encoding)

回應函式 (callback)路徑必須要設定為靜態 html所在位置才能指定到正確的檔案靜態檔案的編碼方式也必須正確這邊使用靜態檔案的編

碼為 utf8如果編碼設定錯誤nodejs讀取出來檔案結果會使用 byte raw格式輸出如果錯誤編碼格式會導致輸出資料為 byte raw

回應函式中裡面會使用兩個變數error為錯誤資訊如果讀取的檔案不存在或者發生錯誤error數值會是 true如果成功讀取資料 error將會是 falsecontent則是檔案內容資料讀取後將會把資料全數丟到 content這個變數當中

最後程式的輸出結果畫面如下

43 nodejs檔案讀取 35

Nodejs中文電子書

44 nodejs http靜態檔案輸出

前面已經了解如何讀取本地端檔案接下來將配合 http伺服器路由讓每個路由都能夠輸出相對應的靜態 html檔案首先新增加幾個靜態 html檔案

ltDOCTYPE htmlgt

lthtml xmlns=rdquohttpwwww3org1999xhtmlrdquo xmllang=rdquozh-CNrdquo lang=rdquozh-CNrdquogt

ltheadgt

ltmeta http-equiv=rdquoContent-Typerdquo content=rdquotexthtml charset=UTF-7rdquo gt

lttitlegtnodejs index html filelttitlegt

ltheadgt

ltbodygt

lth1gtnodejs index html filelth1gt

ltbodygt

lthtmlgt

ltDOCTYPE htmlgt

lthtml xmlns=rdquohttpwwww3org1999xhtmlrdquo xmllang=rdquozh-CNrdquo lang=rdquozh-CNrdquogt

ltheadgt

ltmeta http-equiv=rdquoContent-Typerdquo content=rdquotexthtml charset=UTF-7rdquo gt

lttitlegtnodejs test html filelttitlegt

ltheadgt

ltbodygt

lth1gtnodejs test html filelth1gt

ltbodygt

lthtmlgt

ltDOCTYPE htmlgt

lthtml xmlns=rdquohttpwwww3org1999xhtmlrdquo xmllang=rdquozh-CNrdquo lang=rdquozh-CNrdquogt

ltheadgt

ltmeta http-equiv=rdquoContent-Typerdquo content=rdquotexthtml charset=UTF-7rdquo gt

lttitlegtnodejs static html filelttitlegt

ltheadgt

ltbodygt

lth1gtnodejs static html filelth1gt

ltbodygt

lthtmlgt

36 4 Nodejs基礎

Nodejs中文電子書

準備一個包含基本路由功能的 http伺服器

var server

ip = rdquo127001rdquo

port = 1337

http = require(rsquohttprsquo)

url = require(rsquourlrsquo)

server = httpcreateServer(function (req res)

var path = urlparse(requrl)

)

serverlisten(port ip)

consolelog(rdquoServer running at httprdquo + ip + rdquordquo + port)

加入 le system 模組使用 readFile 的功能將這一段程式放置於

createServer的回應函式中

fsreadFile(filePath encode function(err file)

)

readFile的回應函式裡面加入頁面輸出讓瀏覽器可以正確讀到檔案在這邊我們設定讀取的檔案為 html 靜態檔案所以 Content-type 設定為texthtml讀取到檔案的內容將會正確輸出成 html靜態檔案

fsreadFile(filePath encode function(err file)

reswriteHead(200 rsquoContent-Typersquo rsquotexthtmlrsquo)

reswrite(file)

resend()

)

到這邊為止基本的程式內容都已經完成剩下一些細節的調整首先

路徑上必須做調整目前的靜態檔案全部都放置於 static資料夾底下設定一個變數來記住資料夾位置

接著將瀏覽器發出要求路徑與資料夾組合讀取正確 html靜態檔案使用者有可能會輸入錯誤路徑所以在讀取檔案的時候要加入錯誤處理

同時回應 404伺服器無法正確回應的 http header格式

加入這些細節的修改一個基本的 http 靜態 html輸出伺服器就完成了完整程式碼如下

44 nodejs http靜態檔案輸出 37

Nodejs中文電子書

var server

ip = rdquo127001rdquo

port = 1337

http = require(rsquohttprsquo)

fs = require(rdquofsrdquo)

folderPath = rdquostaticrdquo

url = require(rsquourlrsquo)

path

filePath

encode = rdquoutf8rdquo

server = httpcreateServer(function (req res)

path = urlparse(requrl)

filePath = folderPath + pathpathname

fsreadFile(filePath encode function(err file)

if (err)

reswriteHead(404 rsquoContent-Typersquo rsquotextplainrsquo)

resend()

return

reswriteHead(200 rsquoContent-Typersquo rsquotextapplicationrsquo)

reswrite(file)

resend()

)

)

serverlisten(port ip)

consolelog(rdquoServer running at httprdquo + ip + rdquordquo + port)

45 nodejs http GET資料擷取

http伺服器中除了路由之外另一個最常使用的方法就是擷取 GET資料本單元將會介紹如何透過基本 http伺服器擷取瀏覽器傳來的要求擷取 GET資料

在 http協定中GET參數都是藉由 URL從瀏覽器發出要求送至伺服器

38 4 Nodejs基礎

Nodejs中文電子書

端基本的傳送網址格式可能如下

http127001testsend=1amptest=2

上面這段網址裡面的 GET參數就是 send而這個變數的數值就為 1如果想要在 http伺服器取得 GET資料需要在瀏覽器給予的要求 (request)做處理

首先需要載入 query string這個模組這個模組主要是用來將字串資料

過濾後轉換成javascript物件

qs = require(rsquoquerystringrsquo)

接著在第一階段利用 url模組過濾瀏覽器發出的 URL資料後將回應的物件裡面的 query這個變數是一個字串值資料過濾後如下

send=1amptest=2

透過 query string使用 parse這個方法將資料轉換成 javascript物件就表示 GET的資料已經被伺服器端正式擷取下來

path = urlparse(requrl)

parameter = qsparse(pathquery)

整個 nodejs http GET參數完整擷取程式碼如下

var server

ip = rdquo127001rdquo

port = 1337

http = require(rsquohttprsquo)

qs = require(rsquoquerystringrsquo)

url = require(rsquourlrsquo)

server = httpcreateServer(function (req res)

var path = urlparse(requrl)

parameter = qsparse(pathquery)

consoledir(parameter)

reswriteHead(200 rsquoContent-Typersquo rsquotextplainrsquo)

reswrite(rsquoBrowser test GET parameternrsquo)

resend()

)

45 nodejs http GET資料擷取 39

Nodejs中文電子書

serverlisten(port ip)

consolelog(rdquoServer running at httprdquo + ip + rdquordquo + port)

程式運作之後由瀏覽器輸入要求網址之後nodejs伺服器端回應資料為

Server running at http1270011337

send rsquo1rsquo test rsquo1rsquo

46 本章結語

前面所解說的部份一大部分主要是處理 http伺服器基本問題雖然在某些部分有牽扯到 http 伺服器基本運作原理主要還是希望可以藉由這些基本範例練習 nodejs練習回應函式與語法串接的特點習慣編寫javascript風格程式當然直接這樣開發 nodejs是非常辛苦的接下來在模組實戰開發的部份將會介紹特定的模組一步一步帶領各位從無到有進行

nodejs應用程式開發

40 4 Nodejs基礎

5

NPM套件管理工具

npm全名為 Node Package Manager是 Nodejs的套件(package)管理工具類似 Perl的 ppm或 PHP的 PEAR等安裝 npm後使用npm install

module name指令即可安裝新套件維護管理套件的工作會更加輕鬆

npm可以讓Nodejs的開發者直接利用擴充線上的套件庫(packagesregistry)加速軟體專案的開發npm提供很友善的搜尋功能可以快速找到安裝需要的套件當這些套件發行新版本時npm也可以協助開發者自動更新這些套件

npm不僅可用於安裝新的套件它也支援搜尋列出已安裝模組及更新的功能

51 安裝 NPM

Nodejs在 063版本開始內建 npm讀者安裝的版本若是此版本或更新的版本就可以略過以下安裝說明

若要檢查 npm是否正確安裝可以使用以下的指令

npm -v

41

Nodejs中文電子書

執行結果說明

若 npm正確安裝執行 npm -v將會看到類似 110-2的版本訊息

若讀者安裝的 Nodejs版本比較舊或是有興趣嘗試自己動手安裝 npm工具則可以參考以下的說明

安裝於Windows系統

Nodejs for Windows 於 062 版開始內建 npm使用 nodejsorg 官方提供的安裝程式不需要進一步的設定就可以立即使用 npm指令對於Windows的開發者來說大幅降低環境設定的問題與門檻

除了使用 Nodejs內建的 npm讀者也可以從 npm官方提供的以下網址

httpnpmjsorgdist

這是由 npm提供的 Fancy Windows Install版本請下載壓縮檔(例如npm-110-3zip)並將壓縮檔內容解壓縮至 Nodejs的安裝路徑(例如CProgram Filesnodejs)

解壓縮後在 Nodejs的安裝路徑下應該有以下的檔案及資料夾

bull npmcmd(檔案)

bull node modules(資料夾)

安裝於 Linux系統

Ubuntu Linux的使用者可以加入 NPM Unoffcial PPA這個 repository即可使用 apt-get完成 npm安裝

42 5 NPM套件管理工具

Nodejs中文電子書

Ubuntu Linux使用 apt-get安裝 npm

sudo apt-get install python-software-properties

sudo add-apt-repository ppagias-kay-leenpm

sudo apt-get update

sudo apt-get install npm

npm官方提供的安裝程式 installsh可以適用於大多數的 Linux系統使用這個安裝程式請先確認

1 系統已安裝 curl工具(請使用 curl --version查看版本訊息)

2 已安裝 Nodejs並且 PATH正確設置

3 Nodejs的版本必須大於 04x

以下為 npm提供的安裝指令

curl httpnpmjsorginstallsh | sh

安裝成功會看到如下訊息

installsh安裝成功的訊息

npm10105 homeUSERNAMElocalnodelibnode_modulesnpm

It worked

安裝於Mac OS X

建議採用與 Nodejs相同的方式進行 npm的安裝例如使用MacPorts安裝 Nodejs就同樣使用MacPorts安裝 npm這樣對日後的維護才會更方便容易

使用 MacPorts安裝 npm是本書比較建議的方式它可以讓 npm的安裝移除及更新工作自動化將會幫助開發者節省寶貴時間

51 安裝 NPM 43

Nodejs中文電子書

安裝MacPorts的提示

在MacPorts網站可以取得 OS X系統版本對應的安裝程式(例如 106或 107)httpwwwmacportsorg安裝過程會詢問系統管理者密碼使用預設的選項完成安裝即可安

裝MacPorts之後在終端機執行 port -v將會看到MacPorts的版本訊息

安裝 npm之前先更新MacPorts的套件清單以確保安裝的 npm是最新版本

sudo port -d selfupdate

接著安裝 npm

sudo port install npm

若讀者的 Nodejs並非使用MacPorts安裝則不建議使用MacPorts安裝npm因為 MacPorts會自動檢查並安裝相依套件而 npm相依 nodejs所以MacPorts也會一併將 nodejs套件安裝造成先前讀者使用其它方式安裝的 nodejs被覆蓋

讀者可以先使用 MacPorts安裝 curl(sudo port install curl)

再參考 Linux的 installsh安裝方式即可使用 npm官方提供的安裝程式

NPM安裝後測試

npm是指令列工具(command-line tool)使用時請先打開系統的文字終端機工具

測試 npm安裝與設定是否正確請輸入指令如下

npm -v

或是

npm --version

如果 npm已經正確安裝設定就會顯示版本訊息

44 5 NPM套件管理工具

Nodejs中文電子書

執行結果(範例)

110-2

52 使用 NPM安裝套件

npm目前擁有超過 6000種套件(packages)可以在 npm registry使用關鍵字搜尋套件

httpsearchnpmjsorg

舉例來說在關鍵字欄位輸入「coffee-script」下方的清單就會自動列出包含 coffee-script關鍵字的套件

接著我們回到終端機模式的操作npm的指令工具本身就可以完成套

件搜尋的任務

例如以下的指令同樣可以找出 coffee-script相關套件

npm search coffee-script

以下是搜尋結果的參考畫面

52 使用 NPM安裝套件 45

Nodejs中文電子書

找到需要的套件後(例如 express)即可使用以下指令安裝

npm install coffee-script

值得注意的一點是使用 npm install會將指定的套件安裝在工

作目錄(Working Directory)的 node modules資料夾下

以Windows為例如果執行 npm install的目錄位於

Cproject1

那麼 npm將會自動建立一個 node modules的子目錄(如果不存在)

Cproject1node modules

並且將下載的套件放置於這個子目錄例如

Cproject1node modulescoffee-script

這個設計讓專案可以個別管理相依的套件並且可以在專案佈署或發

行時將這些套件(位於 node modules)一併打包方便其它專案的使用者不必再重新下載套件

這個 npm install的預設安裝模式為 local(本地)只會變更當前專案的資料夾不會影響系統

另一種安裝模式稱為 global(全域)這種模式會將套件安裝到系統資

料夾也就是 npm安裝路徑的 node modules資料夾例如

CProgram Filesnodejsnode modules

46 5 NPM套件管理工具

Nodejs中文電子書

是否要使用全域安裝可以依照套件是否提供新指令來判斷舉例來說express 套件提供 express 這個指令而 coffee-script 則提供 coffee

指令

在 local 安 裝 模 式 中這 些 指 令 的 程 式 檔 案會 被 安 裝 到node modules的 bin這個隱藏資料夾下除非將bin的路徑加入 PATH環境變數否則要執行這些指令將會相當不便

為了方便指令的執行我們可以在 npm install 加上 -g 或 --

global參數啟用 global安裝模式例如

npm install -g coffee-script

npm install -g express

使用 global安裝模式需要注意執行權限的問題若權限不足可能會出現類似以下的錯誤訊息

npm ERR Error EACCES permission denied rsquorsquo

npm ERR

npm ERR Please try running this command again as rootAdministrator

要獲得足夠得執行權限請參考以下說明

bull Windows 7 或 2008 以上在「命令提示字元」的捷徑按右鍵選擇「以系統管理員身分執行」執行 npm指令時就會具有 Administrator身分

bull Mac OS X或 Linux系統可以使用 sudo指令例如

sudo npm install -g express

bull Linux系統可以使用 root權限登入或是以「sudo su -」切換成 root身分(使用 root權限操作系統相當危險因此並不建議使用這種方式)

若加上 -g參數使用 npm install -g coffee-script完成安裝

後就可以在終端機執行 coffee指令例如

coffee -v

52 使用 NPM安裝套件 47

Nodejs中文電子書

執行結果(範例)

CoffeeScript version 120

53 套件的更新及維護

除了前一節說明的 search 及 install 用法npm 還提供其他許多指令(commands)

使用 npm help可以查詢可用的指令

npm help

執行結果(部分)

where ltcommandgt is one of

adduser apihelp author bin bugs c cache completion

config deprecate docs edit explore faq find get

help help-search home i info init install la link

list ll ln login ls outdated owner pack prefix

prune publish r rb rebuild remove restart rm root

run-script s se search set show star start stop

submodule tag test un uninstall unlink unpublish

unstar up update version view whoami

使用 npm help command可以查詢指令的詳細用法例如

npm help list

接下來本節要介紹開發過程常用的 npm指令

使用 list可以列出已安裝套件

npm list

48 5 NPM套件管理工具

Nodejs中文電子書

執行結果(範例)

-- coffee-script120

- express256

- connect185

| -- formidable108

-- mime124

-- mkdirp007

-- qs041

檢視某個套件的詳細資訊例如

npm show express

升級所有套件(如果該套件已發佈更新版本)

npm update

升級指定的套件

npm update express

移除指定的套件

npm uninstall express

54 使用 packagejson

對於正式的 Nodejs專案可以建立一個命名為 packagejson的設

定檔(純文字格式)檔案內容參考範例如下

54 使用 packagejson 49

Nodejs中文電子書

packagejson(範例)

rdquonamerdquo rdquoapplication-namerdquo

rdquoversionrdquo rdquo001rdquo

rdquoprivaterdquo true

rdquodependenciesrdquo

rdquoexpressrdquo rdquo255rdquo

rdquocoffee-scriptrdquo rdquolatestrdquo

rdquomongooserdquo rdquogt= 253rdquo

其中 name與 version依照專案的需求設置

需要注意的是 dependencies的設定它用於指定專案相依的套件名

稱及版本

bull rdquoexpressrdquo rdquo255rdquo

代表此專案相依版本 255的 express套件

bull rdquocoffee-scriptrdquo rdquolatestrdquo

使用最新版的 coffee-script套件(每次更新都會檢查新版)

bull rdquomongooserdquo rdquogt= 253rdquo

使用版本大於 253的 mongoose套件

假設某個套件的新版可能造成專案無法正常運作就必須指定套件的

版本避免專案的程式碼來不及更新以相容新版套件通常在開發初期的

專案需要盡可能維持新套件的相容性(以取得套件的更新或修正)可以

用「gt=」設定最低相容的版本或是使用「latest」設定永遠保持最新

套件

50 5 NPM套件管理工具

6

Express介紹

在前面的 nodejs基礎當中介紹許多許多開設 http的使用方法及介紹以及許多基本的 nodejs基本應用

接下來要介紹一個套件稱為 express [Express](httpexpressjscom)這個套件主要幫忙解決許多 nodejs http server所需要的基本服務讓開發 httpservice變得更為容易不需要像之前需要透過層層模組(module)才有辦法開始編寫自己的程式

這個套件是由 TJ Holowaychuk 製作而成的套件裡面包含基本的路由處理 (route)http資料處理(GETPOSTPUT)另外還與樣板套件(jshtml template engine)搭配同時也可以處理許多複雜化的問題

61 Express安裝

安裝方式十分簡單只要透過之前介紹的 NPM就可以使用簡單的指令安裝指令如下

這邊建議需要將此套件安裝成為全域模組方便日後使用

51

Nodejs中文電子書

62 Express基本操作

express的使用也十分簡單先來建立一個基本的 hello world

var app = require(rsquoexpressrsquo)createServer()

port = 1337

applisten(port)

appget(rsquorsquo function(req res)

ressend(rsquohello worldrsquo)

)

consolelog(rsquostart express servernrsquo)

可以從上面的程式碼發現基本操作與 nodejs http的建立方式沒有太大差異主要差在當我們設定路由時可以直接透過 appget方式設定回應與接受方式

63 Express路由處理

Express對於 http服務上有許多包裝讓開發者使用及設定上更為方便例如有幾個路由設定那我們就統一藉由 appget來處理

Create http server

appget(rsquorsquo function(req res)

ressend(rsquohello worldrsquo)

)

appget(rsquotestrsquo function(req res)

ressend(rsquotest renderrsquo)

)

appget(rsquouserrsquo function(req res)

ressend(rsquouser pagersquo)

)

52 6 Express介紹

Nodejs中文電子書

如上面的程式碼所表示appget可以帶入兩個參數第一個是路徑名稱設定第二個為回應函式 (call back function)回應函式裡面就如同之前的 createServer方法裡面包含 requestresponse兩個物件可供使用使用者就可以透過瀏覽器輸入不同的 url切換到不同的頁面顯示不同的結果

路由設定上也有基本的配對方式讓使用者從瀏覽器輸入的網址可以

是一個變數只要符合型態就可以有對應的頁面產出例如

Create http server

appget(rsquouseridrsquo function(req res)

ressend(rsquouser rsquo + reqparamsid)

)

appget(rsquonumberrsquo function(req res)

ressend(rsquonumber rsquo + reqparamsnumber)

)

裡 面 使 用 到number 從 網 址 輸 入 之 後 就 可 以 直 接 使 用reqparamsnumber 取得所輸入的資料變成 url 參數使用當然前面也是可以加上路徑的設定userid在瀏覽器上路徑必須符合userxxx透

過 reqparamsid就可以取到 xxx這個字串值

另外express參數處理也提供了路由參數配對處理也可以透過正規表示法作為參數設定

var app = require(rsquoexpressrsquo)createServer()

port = 1337

applisten(port)

appget(^ip((d23)((d23))((d23))((d23))) function(req res)

ressend(reqparams)

)

上面程式碼可以發現後面路由設定的型態是正規表示法裡面設定

格式為ip之後必須要加上 ip型態才會符合資料格式同時取得 ip資料已經由正規表示法將資料做分群因此可以取得 ip的四個數字

此程式執行之後可以透過瀏覽器測試輸入網址為 localhost3000ip25525510010可以從頁面獲得資料

63 Express路由處理 53

Nodejs中文電子書

此章節全部範例程式碼如下

overview

author Caesar Chi

blog clonnblogspotcom

version 20120226

create server

var app = require(rsquoexpressrsquo)createServer()

port = 1337

applisten(port)

normal style

appget(rsquorsquo function(req res)

ressend(rsquohello worldrsquo)

)

appget(rsquotestrsquo function(req res)

ressend(rsquotest renderrsquo)

)

parameter style

appget(rsquouseridrsquo function(req res)

ressend(rsquouser rsquo + reqparamsid)

)

appget(rsquonumberrsquo function(req res)

ressend(rsquonumber rsquo + reqparamsnumber)

)

REGX style

appget(^ip((d23)((d23))((d23))) function(req res)

ressend(reqparams)

)

appget(rsquorsquo function(req res)

ressend(rsquoPage not foundrsquo 404)

54 6 Express介紹

Nodejs中文電子書

)

consolelog(rsquostart express servernrsquo)

64 Express middleware

Express 裡面有一個十分好用的應用概念稱為 middleware可以透過middleware做出複雜的效果同時上面也有介紹 next方法參數傳遞就是靠 middleware的概念來傳遞參數讓開發者可以明確的控制程式邏輯

create http server

appuse(expressbodyParser())

appuse(expressmethodOverride())

appuse(expresssession())

上面都是一種 middleware的使用方式透過 appuse方式裡面載入函式執行方法回應函式會包含三個基本參數responserequestnext其中next表示下一個 middleware執行函式同時會自動將預設三個參數繼續帶往下個函式執行底下有個實驗

上面的片段程式執行後開啟瀏覽器連結上 localhost1337會發現伺服器回應結果順序如下

first middle ware

second middle ware

execute middle ware

end middleware function

從上面的結果可以得知剛才設定的 middleware都生效了在 appuse設定的 middleware是所有 url皆會執行方法如果有指定特定方法就可以使用 appget的 middleware設定在 appget函式的第二個參數就可以帶入函式或者是匿名函式只要函式裡面最後會接受 request response next這三個參數同時也有正確指定 next函式的執行時機最後都會執行到最後一個方法當然開發者也可以評估程式邏輯要執行到哪一個階段讓邏輯

可以更為分明

64 Express middleware 55

Nodejs中文電子書

65 Express路由應用

在實際開發上可能會遇到需要使用參數等方式混和變數一起使用

express裡面提供了一個很棒的處理方法 appall這個方式可以先採用基本路由配對再將設定為每個不同的處理方式開發者可以透過這個方式簡

化自己的程式邏輯

overview

author

version 20120226

create server

var app = require(rsquoexpressrsquo)createServer()

port = 1337

users = [

name rsquoClonnrsquo

name rsquoChirsquo

]

applisten(port)

appall(rsquouseridoprsquo function(req res next)

requser = users[reqparamsid]

if (requser)

next()

else

next(new Error(rsquocannot find user rsquo + reqparamsid))

)

appget(rsquouseridrsquo function(req res)

ressend(rsquoviewing rsquo + requsername)

)

appget(rsquouserideditrsquo function(req res)

ressend(rsquoediting rsquo + requsername)

)

56 6 Express介紹

Nodejs中文電子書

appget(rsquouseriddeletersquo function(req res)

ressend(rsquodeleting rsquo + requsername)

)

appget(rsquorsquo function(req res)

ressend(rsquoPage not foundrsquo 404)

)

consolelog(rsquostart express servernrsquo)

內部宣告一組預設的使用者分別給予名稱設定藉由 appall這個方法可以先將路由雛形建立再接下來設定 appget的路徑格式只要符合格式就會分配進入對應的方法中像上面的程式當中如果使用者輸入路徑為user0除了執行 appall程式之後執行 next方法就會對應到路徑設定為userid的這個方法當中如果使用者輸入路徑為user0edit就會執行到useridedit的對應方法

66 Express GET應用範例

我們準備一個使用 GET方法傳送資料的表單

ltDOCTYPE htmlgt

lthtmlgt

ltheadgt

ltmeta http-equiv=rdquoContent-Typerdquo content=rdquotexthtml charset=utf8rdquogt

lttitlegtNodejs 菜鳥筆記 (1)lttitlegt

ltlink rel=rdquostylesheetrdquo href=rdquocssstylecssrdquo type=rdquotextcssrdquo media=rdquoallrdquo gt

ltheadgt

ltbodygt

lth1gtNodejs 菜鳥筆記-註冊lth1gt

ltform id=rdquosignuprdquo method=rdquoGETrdquo action=rdquohttplocalhost3000Signuprdquogt

ltlabelgt 使用者名稱ltlabelgtltinput type=rdquotextrdquo id=rdquousernamerdquo name=rdquousernamerdquo gtltbrgt

ltlabelgt 電子郵件ltlabelgtltinput type=rdquotextrdquo id=rdquoemailrdquo name=rdquoemailrdquo gtltbrgt

ltinput type=rdquosubmitrdquo value=rdquo 註冊我的帳號rdquo gtltbrgt

ltformgt

ltbodygt

lthtmlgt

66 Express GET應用範例 57

Nodejs中文電子書

這個表單沒有什麼特別的地方我們只需要看第 9 行form 使用的method是 GET然後 action是rdquohttplocalhost3000Signupldquo等一下我們要來撰寫Signup這個 URL Path的處理程式

處理 Signup行為

我們知道所謂的 GET方法會透過 URL來把表單的值給帶過去以上面的表單來說到時候 URL會以這樣的形式傳遞

httplocalhost3000Signupusername=xxxampemail=xxx

所以要能處理這樣的資料必須有以下功能

bull 解析 URL

bull 辨別動作是 Signup

bull 解析出 username和 email

一旦能取得 username和 email的值程式就能加以應用了

處理 Signup的程式碼雛形

load module

var url = require(rsquourlrsquo)

urlData = urlparse(requrltrue)

action = urlDatapathname

reswriteHead(200 rdquoContent-Typerdquordquotexthtml charset=utf-8rdquo)

if (action === rdquoSignuprdquo)

user = urlDataquery

resend(rdquolth1gtrdquo + userusername + rdquo 歡迎您的加入 lth1gtltpgt 我們已經將會員啟用信寄至rdquo + useremail + rdquoltpgtrdquo)

首先需要加載 url module它是用來協助我們解析 URL的模組接著使用 urlparse方法第一個傳入 url字串變數也就是 requrl另外第二個參數的用意是設為 ture則引進 querystring模組來協助處理預設是 false它影響到的是 urlDataquery設為 true會傳回物件不然就只是一般的字串urlparse會將字串內容整理成一個物件我們把它指定給 urlData

action變數作為記錄 pathname這是我們稍後要來判斷目前網頁的動作是什麼接著先將 html表頭資訊 (Header)準備好再來判斷路徑邏輯如

58 6 Express介紹

Nodejs中文電子書

果是 Signup這個動作就把 urlDataquery裡的資料指定給 user然後輸出userusername和 useremail把使用者從表單註冊的資料顯示於頁面中

最後進行程式測試啟動 nodejs主程式之後開啟瀏覽器就會看到表單填寫完畢按下送出就可以看到結果了

完整 nodejs程式碼如下

var http = require(rsquohttprsquo)

url = require(rsquourlrsquo)

fs = require(rdquofsrdquo)

server

server = httpcreateServer(function (reqres)

var urlData

encode = rdquoutf8rdquo

filePath = rdquoviewexpress_get_example_formhtmlrdquo

action

urlData = urlparse(requrltrue)

action = urlDatapathname

reswriteHead(200 rdquoContent-Typerdquordquotexthtml charset=utf-8rdquo)

if (action === rdquoSignuprdquo)

user = urlDataquery

resend(rdquolth1gtrdquo + userusername + rdquo 歡迎您的加入 lth1gtltpgt 我們已經將會員啟用信寄至rdquo + useremail + rdquoltpgtrdquo)

else

fsreadFile(filePath encode function(err file)

reswrite(file)

resend()

)

)

serverlisten(3000)

consolelog(rsquoServer 跑起來了現在時間是rsquo + new Date())

66 Express GET應用範例 59

Nodejs中文電子書

67 Express POST應用範例

一開始準備基本的 html表單傳送內容以 POST方式form的 action屬性設定為 POST其餘 html內容與前一個範例應用相同

ltDOCTYPE htmlgt

lthtmlgt

ltheadgt

ltmeta http-equiv=rdquoContent-Typerdquo content=rdquotexthtml charset=utf8rdquogt

lttitlegtNodejs 菜鳥筆記 (1)lttitlegt

ltlink rel=rdquostylesheetrdquo href=rdquocssstylecssrdquo type=rdquotextcssrdquo media=rdquoallrdquo gt

ltheadgt

ltbodygt

lth1gtNodejs 菜鳥筆記-註冊lth1gt

ltform id=rdquosignuprdquo method=rdquoPOSTrdquo action=rdquohttplocalhost3000Signuprdquogt

ltlabelgt 使用者名稱ltlabelgtltinput type=rdquotextrdquo id=rdquousernamerdquo name=rdquousernamerdquo gtltbrgt

ltlabelgt 電子郵件ltlabelgtltinput type=rdquotextrdquo id=rdquoemailrdquo name=rdquoemailrdquo gtltbrgt

ltinput type=rdquosubmitrdquo value=rdquo 註冊我的帳號rdquo gtltbrgt

ltformgt

ltbodygt

lthtmlgt

nodejs的程式處理邏輯與前面 GET範例類似部分程式碼如下

qs = require(rsquoquerystringrsquo)

if (action === rdquoSignuprdquo)

formData = rsquorsquo

reqon(rdquodatardquo function (data)

formData += data

)

reqon(rdquoendrdquo function ()

user = qsparse(formData)

resend(rdquolth1gtrdquo + userusername + rdquo 歡迎您的加入 lth1gtltpgt 我們已經將會員啟用信寄至rdquo + useremail + rdquoltpgtrdquo)

)

60 6 Express介紹

Nodejs中文電子書

主要加入了rsquoquerystringrsquo這個moduel方便我們等一下解析由表單 POST回來的資料另外加入一個 formData的變數用來搜集待等一下表單回傳的資料前面的 GET範例我們只從 req拿出 url的資料這次要在利用req身上的事件處理

JavaScript 在訂閱事件時使用 addEventListener而 nodejs 使用的則是on這邊加上了監聽 data的事件會在瀏覽器傳送資料到Web Server時被執行參數是它所接收到的資料型態是字串

接著再增加 end的事件當瀏覽器的請求事件結束時它就會動作

由於瀏覽器使用 POST在上傳資料時會將資料一塊塊地上傳因為我們在監聽 data事件時透過 formData變數將它累加起來lt不過由於我們

上傳的資料很少一次就結束不過如果日後需要傳的是資料比較大的檔

案這個累加動作就很重要

當資料傳完就進到 end 事件中會用到 qsparse 來解析 formDataformData的內容是字串內容是

username=wordsmithampemail=wordsmith40somewhere

而 qsparse可以幫我們把這個 querystring轉成物件的格式也就是

username=wordsmithampemail=wordsmith40somewhere

一旦轉成物件並指定給 user之後其他的事情就和 GET方法時操作的一樣寫 response的表頭將內容回傳並將 userusername和 useremail代入到內容中

修改完成後接著執行 nodejs程式啟動 web server開啟瀏覽器進入表單測試看看POST的方式能否順利運作

完整程式碼如下

var http = require(rsquohttprsquo)

url = require(rsquourlrsquo)

fs = require(rdquofsrdquo)

qs = require(rsquoquerystringrsquo)

server

server = httpcreateServer(function (reqres)

var urlData

67 Express POST應用範例 61

Nodejs中文電子書

encode = rdquoutf8rdquo

filePath = rdquoviewexpress_post_example_formhtmlrdquo

formData

action

urlData = urlparse(requrltrue)

action = urlDatapathname

reswriteHead(200 rdquoContent-Typerdquordquotexthtml charset=utf-8rdquo)

if (action === rdquoSignuprdquo)

formData = rsquorsquo

reqon(rdquodatardquo function (data)

formData += data

)

reqon(rdquoendrdquo function ()

user = qsparse(formData)

resend(rdquolth1gtrdquo + userusername + rdquo 歡迎您的加入 lth1gtltpgt 我們已經將會員啟用信寄至rdquo + useremail + rdquoltpgtrdquo)

)

else

fsreadFile(filePath encode function(err file)

reswrite(file)

resend()

)

)

serverlisten(3000)

consolelog(rsquoServer 跑起來了現在時間是rsquo + new Date())

62 6 Express介紹

Nodejs中文電子書

68 Express AJAX應用範例

在 Nodejs要使用 Ajax傳送資料並且與之互動在接受資料的部份沒有太大的差別client端不是用 GET就是用 POST來傳資料重點在處理完後用 JSON格式回傳當然 Ajax不見得只傳 JSON格式有時是回傳一段 HTML碼不過後者對伺服器來說基本上就和前兩篇沒有差別了所以我們還是以回傳 JSON做為這一回的主題

這一回其實大多數的工作都會落在前端 Ajax上面前端要負責發送與接收資料並在接收資料後撤掉原先發送資料的表單並將取得的資料

改成 HTML格式之後放上頁面

首先先準備 HTML靜態頁面資料

ltDOCTYPE htmlgt

lthtmlgt

ltheadgt

lttitlegtNodejs 菜鳥筆記 (3)lttitlegt

ltlink rel=rdquostylesheetrdquo href=rdquocssstylecssrdquo type=rdquotextcssrdquo media=rdquoallrdquo gt

ltscript src=rdquohttpsajaxgoogleapiscomajaxlibsjquery171jqueryminjsrdquogtltscriptgt

ltheadgt

ltbodygt

lth1gtNodejs 菜鳥筆記-註冊 (用 Ajax)lth1gt

ltform id=rdquosignuprdquo action=rdquoSignuprdquo method=rdquoPOSTrdquogt

ltlabelgt 使用者名稱ltlabelgtltinput type=rdquotextrdquo id=rdquousernamerdquo name=rdquousernamerdquo gtltbrgt

ltlabelgt 電子郵件ltlabelgtltinput type=rdquotextrdquo id=rdquoemailrdquo name=rdquoemailrdquo gtltbrgt

ltinput type=rdquosubmitrdquo value=rdquo 註冊我的帳號rdquo gtltbrgt

ltformgt

ltdiv id=rdquoresultrdquogt

ltdivgt

ltscript type=rdquotextjavascriptrdquogt

$(function()$(rsquosignup input[type=rdquosubmitrdquo]rsquo)click(function(e)

var user =

userusername = $(rdquousernamerdquo)val()useremail = $(rdquoemailrdquo)val()

$post(rdquoSignuprdquouserfunction(data)greet(data)

)

68 Express AJAX應用範例 63

Nodejs中文電子書

epreventDefault()

)

var greet = function(msg)

var resultNode = $(rsquoresultrsquo)greeting = $(rsquolth2gtHirsquo+msgusername+rsquo 你的會員 id 是rsquo+ msgid +rsquo 我們會將會員啟動信寄至rsquo+msgemail+rsquolth2gtrsquo)

$(rsquosignuprsquo)hide()resultNodehtml(rdquordquo)

resultNodeappend(greeting)

)

ltscriptgt

ltbodygt

lthtmlgt

HTML頁面上準備了一個表單用來傳送註冊資料接著直接引用了Google CDN來載入 jQuery用來幫我們處理 Ajax的工作這次要傳送和接收的工作很大的變動都在 HTML頁面上的 JavaScript當中我們要做的事有 (相關 jQuery處理這邊不多做贅述指提起主要功能解說)

bull 用 jQUery取得 submit按鈕綁定它的 click動作

bull 取得表單 username和 email的值存放在 user這個物件中

bull 用 jQuery的 $post方法將 user的資料傳到 Server

bull 一旦成功取得資料後透過 greet這個 function組成回報給 user的訊息

bull 清空原本給使用者填資料的表單

bull 將 Server回傳的 usernameemail和 id這 3個資料組成回應的訊息

bull 將訊息放到原本表單的位置

經過以上的處理後一個 Ajax的表單的基本功能已經完成

接著進行 nodejs主要程式的編輯部分程式碼如下

var fs = require(rdquofsrdquo)

64 6 Express介紹

Nodejs中文電子書

qs = require(rsquoquerystringrsquo)

if (action === rdquoSignuprdquo)

formData = rsquorsquo

reqon(rdquodatardquo function (data)

formData += data

)

reqon(rdquoendrdquo function ()

var msg

user = qsparse(formData)

userid = rdquo123456rdquo

msg = JSONstringify(user)

reswriteHead(200 rdquoContent-Typerdquordquoapplicationjson charset=utf-8rdquordquoContent-Lengthrdquomsglength)

resend(msg)

)

這裡的程式和前面 POST範例基本上大同小異差別在

bull 幫 user的資料加上 id隨意存放一些文字進去讓 Server回傳的資料多於 Client端傳上來的不然會覺得 Server都沒做事

bull 增加了 msg 這個變數存放將 user 物件 JSON 文字化的結果JSONstringify這個轉換函式是 V8引擎所提供的如果你好奇的話

bull 大重點來了我們要告訴 Client端這次回傳的資料格式是 JSON所在 Content-type和 Content-Length要提供給 Client

Server很輕鬆就完成任務了最後進行程式測試啟動 nodejs主程式之後開啟瀏覽器就會看到表單填寫完畢按下送出就可以看到結果了

最後 nodejs本篇範例程式碼如下

var http = require(rsquohttprsquo)

url = require(rsquourlrsquo)

fs = require(rdquofsrdquo)

qs = require(rsquoquerystringrsquo)

server

68 Express AJAX應用範例 65

Nodejs中文電子書

server = httpcreateServer(function (reqres)

var urlData

encode = rdquoutf8rdquo

filePath = rdquoviewexpress_ajax_example_formhtmlrdquo

formData

action

urlData = urlparse(requrltrue)

action = urlDatapathname

if (action === rdquoSignuprdquo)

formData = rsquorsquo

reqon(rdquodatardquo function (data)

formData += data

)

reqon(rdquoendrdquo function ()

var msg

user = qsparse(formData)

userid = rdquo123456rdquo

msg = JSONstringify(user)

reswriteHead(200 rdquoContent-TyperdquordquoapplicationjsonrdquordquoContent-Lengthrdquomsglength)

resend(msg)

)

else

fsreadFile(filePath encode function(err file)

reswriteHead(200 rdquoContent-Typerdquordquotexthtml charset=utf-8rdquo)

reswrite(file)

resend()

)

)

serverlisten(3000)

66 6 Express介紹

Nodejs中文電子書

consolelog(rsquoServer 跑起來了現在時間是rsquo + new Date())

69 原始資料提供

bull [NodeJS初學者筆記 (1)-用 GET傳送資料] (httpithelpithomecomtwquestion10087402)

bull [NodeJS初學者筆記 (2)-用 POST傳送資料] (httpithelpithomecomtwquestion10087489)

bull [NodeJS 初學者筆記 (3)-用 Ajax 傳送資料] (httpithelpithomecomtwquestion10087627)

69 原始資料提供 67

Nodejs中文電子書

68 6 Express介紹

7

CoffeeScript

bull Spine

bull Hem

bull Spineapp

Letrsquos Have a Cup of CoffeeScript

bull es5-shim ECMAScript 5 compatibility shims for legacy JavaScript engines

ESnext

node-iform - A middleware for node-validator httpsgithubcomguileennode-iform

69

Nodejs中文電子書

70 7 CoffeeScript

8

製作一個 Hubot的 Plurk Adapter

81 應用事項提醒

此應用範例以CoffeeScript編寫主要程式架構採用Github釋放的Hubot專案以下有幾個主要注意事項請讀者注意

bull Hubot 一開始的架構除了內建的兩個 Adapter 之外其餘都要以Nodejs的Module方式才能運作

bull Hubot的 binhubot裡面寫著 npm install所以不管你怎麼改原始碼也不能改變第一點的狀況

其實上述都在討論同一件事情NodeJS的模組而且模組不能設定相對路徑之類的來安裝一定要包裝成 npm相符和格式才有辦法正確執行

82 建立 Adapter

首先需要準備兩個檔案

bull packagejson

bull plurkcoffee

71

Nodejs中文電子書

有這兩個就足以變成 NodeJS的模組了在開始 Coding之前先來設定一下 packagejson相依性

rdquonamerdquo rdquohubot-plurkrdquo

rdquoversionrdquo rdquo011rdquo

rdquomainrdquo rdquoplurkrdquo

rdquodependenciesrdquo

rdquohubotrdquo rdquogt=205rdquo

rdquooauthrdquo rdquordquo

rdquocronrdquordquordquo

這邊會使用到NodeJS的 cron模組(Module)而登入 Plurk需要OAuth標準授權才行所以也需要加載 OAuth模組

注意Hubot在讀取非內建模組時會自動在前面加上 hubot-的前置

83 建立 Robot跟 API

本篇應用基本上程式碼大部分參考 Hubot - Twitter Adapter來製作主要差異只有在使用 OAuth這個模組Twitter有 Streaming可用而 Plurk則得用 Comet方式來達到即時讀取

plurkcoffee程式碼

Robot = require(rdquohubotrdquo)robot()

Adapter = require(rdquohubtordquo)adapter()

EventEmitter = require(rdquoeventsrdquo)EventEmitter

oauth = require(rdquooauthrdquo)

cronJob = require(rdquocronrdquo)CronJob

class Plurk exntends Adapter

class PlurkStreaming exnteds EventEmitter

72 8 製作一個 Hubot的 Plurk Adapter

Nodejs中文電子書

先弄個基本架構主要 Class皆參考 Twitter Adapter命名方式接著再將 Plurk這個 Class增加幾個Method基本上只要有 run send reply就夠了而 run用來做初始化的部分

class Plurk entends Adapter

send (plurk id strings⋯) -gt

reply (plurk id strings⋯) -gt

run -gt

看起來有點東西接著來處理主要的 Plurk API結合部分

class PlurkStreaming extends EventEmitter

consuctor (options) -gt

plurk (callback) -gt

觀察河道

getChannel -gt

取得 Comet 網址

reply (plurk id message) -gt

回噗

acceptFriends -gt

接受好友

get (path callback) -gt

GET 請求

post (path body callback)-gt

POST 請求(其實是裝飾)

request (method path body callback)-gt

主要的 OAuth 請求

comet (server callback)-gt

噗浪的 Comet 傳回是 JavaScript Callback 要另外處理後才會變成 JSON

然後把注意力集中到 constructor上先把建構子弄好

constructor (options) -gt

super()

if optionskey and optionssecret and optionstoken and optionstoken secret

key = optionskey

secret = optionssecret

token = optionstoken

token secret = optionstoken secret

83 建立 Robot跟 API 73

Nodejs中文電子書

建立 OAuth 連接

consumer = new oauthOAuth(

rdquohttpwwwplurkcomOAuthrequest tokenrdquo

rdquohttpwwwplurkcomOAuthaccess tokenrdquo

key

secret

rdquo10rdquo

rdquohttpwwwplurkcomOAuthauthorizerdquo

rdquoHMAC-SHA1rdquo

)

domain = rdquowwwplurkcomrdquo

初始化取得 Comet 網址

do getChannel

else

throw new Error(rdquo 參數不足需要 Key Secret Token Token Secretrdquo)

接著來處理 request這個 method

request (method path body callback) -gt

記錄一下這次的 Request

consolelog(rdquohttpdomainpathrdquo)

Callback 這邊先不丟進去要用另一種方式處理

request = consumerget(rdquohttpdomainpathrdquo token token secret null)

requeston rdquoresponserdquo (res) -gt

reson rdquodatardquo (chunk) -gt

parseResponse(chunk+rsquorsquo callback)

reson rdquoendrdquo (data) -gt

consolelog rdquoEnd Request pathrdquo

reson rdquoerrorrdquo (data) -gt

consolelog rdquoError rdquo + data

requestend()

處理資料

parseResponse = (data callback) -gt

if datalength gt 0

用 TryCatch 避免處理 JSON 出錯導致整個中斷

try

callback null JSONparse(data)

catch err

74 8 製作一個 Hubot的 Plurk Adapter

Nodejs中文電子書

consolelog(rdquoError Parse JSONrdquo + data err)

繼續執行

callback null data ||

大致上就是這樣上面程式的架構已經將整個 Hubot Plurk Adapter完成因為在測試時竟然因為噗浪 Lag而沒讀到完整的 Comet資料然後造成程式異常為了避免這個問題發生因此需要加上為了完美呈現需要再

加上 Comet的處理所以要使用到 EventEmitter的功能

comet (server callback) -gt

在 Callback 裡面會找不到自身所以設定區域變數

self =

記錄一下這次的 Request

consolelog(rdquo[Comet] serverrdquo)

Callback 這邊先不丟進去要用另一種方式處理

request = consumerget(rdquohttpdomainpathrdquo token token secret null)

requeston rdquoresponserdquo (res) -gt

reson rdquodatardquo (chunk) -gt

parseResponse(chunk+rsquorsquo callback)

reson rdquoendrdquo (data) -gt

consolelog rdquoEnd Request pathrdquo

請求結束發出事件通知可以進行下一次請求

selfemit rdquonextPlurkrdquo

reson rdquoerrorrdquo (data) -gt

consolelog rdquoError rdquo + data

requestend()

處理資料

parseResponse = (data callback) -gt

if datalength gt 0

用 trycatch 避免失敗中斷

try

去掉 JavaScript 的 Callback

data = datamatch(CometChannelscriptCallback((+))s)

jsonData = rdquordquo

if data

83 建立 Robot跟 API 75

Nodejs中文電子書

jsonData = JSONparse(data[1])

else

如果沒有任何 Match 嘗試直接 parse

jsonData = JSONparse(data)

catch err

consolelog(rdquo[Comet] Errorrdquo data err)

用 TryCatch 避免處理 JSON 出錯導致整個中斷

try

只傳入 json 的 data 部分

callback null jsonDatadata

catch err

consolelog(rdquo[Comet]Error Parse JSONrdquo + data err)

繼續執行

callback null data ||

後面的 get跟 post就簡單多了

get (path callback) -gt

request(rdquoGETrdquo path null callback)

post (path body callback) -gt

request(rdquoPOSTrdquo path body callback)

接著處理取的 Comet網址的 getChannel

getChannel -gt

self =

get rdquoAPPRealtimegetUserChannelrdquo (error data) -gt

if error

檢查是否有 comet server

if datacomet server

selfchannel = datacomet server

如果沒有 Channel Ready 就嘗試連接會失敗

selfemit(rsquochannel readyrsquo)

那麼先來處理 Plurk Adaper好處理的部份

send (plruk id strings⋯)-gt

跟 Reply 一樣直接交給 reply 做

reply plurk id strings⋯

76 8 製作一個 Hubot的 Plurk Adapter

Nodejs中文電子書

reply (plurk id strings⋯) -gt

stringsforEach (message) =gt

botreply(plruk id message)

接著把 run處理好就可以上線運作摟

run -gt

self =

options =

key processenvHUBOT PLURK KEY

secret processenvHUBOT PLURK SECRET

token processenvHUBOT PLURK TOKEN

token secret processenvHUBOT PLURK TOKEN SECRET

創建剛剛的 API

bot = new PlurkStreaming(options)

依照 Twitter 的 new RobotTextMessage 會沒有反應所以參考 hubot-minecraft 的方式

r = robotconstructor

處理噗浪河道訊息

doPlurk = (data)-gt

檢查是否為回噗

if dataresponse

datacontent raw = dataresponsecontent raw

datauser id = dataresponseuser id

確定有噗浪 ID 跟訊息

if dataplurk id and datacontent raw

selfreceive new rTextMessage(dataplurk id datacontent raw)

取得 Comet Server 完成開始第一次 Comet 連接

boton rdquochannel readyrdquo () -gt

botplurk selfdoPlurk

上一次 Comet 完成繼續 Polling

boton rdquonextPlurkrdquo ()-gt

botplurk selfdoPlurk

定時接受好友邀請

do botacceptFriends

bot = bot

83 建立 Robot跟 API 77

Nodejs中文電子書

終於完成 AdapterHubot專案裡面的 scripts資料夾內是互動部分不需要像 Adapter如此大費周章處理只新增檔案並且設計好對白之後就會回噗了我開發用的機器人在此大家可以去跟他玩玩[httpplurkcomelct9620 bot](httpplurkcomelct9620 bot)

84 原始資料提供

bull [製 作 一 個 Hubot 的 噗 浪 Adapter](http revo-skillfrosttw blog20120318create-a-hubot-plurk-adapter)

78 8 製作一個 Hubot的 Plurk Adapter

9

Nodejs好用工具介紹

91 logio

Real-time log monitoring in your browser

httplogioorg

nodejs (gt=0412 lt=0611)

npm config set unsafe-perm true

npm install -g --prefix=usrlocal logio

logio server start

httplocalhost8998

79

Nodejs中文電子書

80 9 Nodejs好用工具介紹

10

精選文章

101 Express

bull 使用 NodeJS + Express從 GETPOST Request取值 [邀稿中]

bull link2 [邀稿中]

bull link3 [邀稿中]

81

Nodejs中文電子書

82 10 精選文章

11

參考資源

111 Nodejs書籍

bull Programming Nodejs April 2012 (est) Travis Swicegood Pragmatic Bookshelf

bull e Little Book on CoffeeScript January 2012 Alex MacCaw OrsquoReilly Media

bull Node for Front-End Developers January 2012 Garann Means OrsquoReilly Media

bull Building Hypermedia APIs with HTML5 and Node November 2011 MikeAmundsen OrsquoReilly Media

bull Node Web Development August 2011 David Herron PacktPub

bull CoffeeScript Accelerated JavaScript Development July 2011 Trevor BurnhamPragmatic Bookshelf

bull Getting Started with GEO CouchDB and Nodejs July 2011 Mick omp-son OrsquoReilly Media

bull What Is Node July 2011 Brett McLaughlin OrsquoReilly Media

bull Node Up and Running May 2011 Tom Hughes-Croucher Mike WilsonOrsquoReilly Media

83

Nodejs中文電子書

bull Hands-on Nodejs e Nodejs introduction and API reference May 2011 Pe-dro Teixeira LeanPub

bull e Node Beginner Book Manuel Kiessling

bull Mastering Nodejs visionmedia

112 Nodejs影音教學

bull e Node Sessions e Best of OSCON 2011 August 2011 OrsquoReilly Media(Video)

bull Tom Hughes-Croucher on Node March 2011 OrsquoReilly Media (Video)

113 Nodejs教學網站

bull Node Tuts

114 Nodejs課程

bull Introduction To Nodejs Van Nguyen CodeLesson

84 11 參考資源

Page 22: Nodejs Wiki

Nodejs中文電子書

ldquonormal caserdquo function(test)

testexpect(1) var obj = require(lsquotoolsrsquo)cookieParser(thisrequest thisresponse)testdeepEqual(obj thisresult) testdone()

)

善於利用模組可以讓程式更好維護與測試

24 CPS(Continuation-Passing Style)

cps 是 callback 使用上的特例形式上就是在函數最後呼叫 callback這樣就好像把函數執行後把結果交給 callback 繼續運行所以稱作continuation-passing style利用 cps可以在非同步執行的情況下透過傳給 callback的這個 cps callback來獲知 callback執行完畢或是取得執行結果例如

lthtmlgt ltbodygt ltdiv id=rdquopanelrdquo style=rdquovisibilityhiddenrdquogtlt divgtlt bodygt lt htmlgt ltscriptgt var request = new XMLHttpRequest() re-questopen(lsquoGETrsquo lsquotest749txt timestamp=rsquo+new Date()getTime() true) re-questaddEventListener(lsquoreadystatechangersquo function(next)

return function() if(thisreadyState===4ampampthisstatus===200) next(thisresponseText)lt== 傳入的 cps callback 在動作完成時執行並取得結果進一步處理

(function(str)lt==這個匿名函數就是 cps callback

documentgetElementById(lsquopanelrsquo)innerHTML=str docu-mentgetElementById(lsquopanelrsquo)stylevisibility = lsquovisiblersquo

) false) requestsend() ltscriptgt

進一步的應用也可以參考 2-6流程控制

16 2 JavaScript與 NodeJS

Nodejs中文電子書

25 函數返回函數與 Currying

前面的 cps範例裡面使用了函數返回函數這是為了把 cps callback傳遞給 onreadystatechange事件處理函數的方法(因為這個事件處理函數並沒有設計好會傳送接收這樣的參數)實際會執行的事件處理函數其實是內層返回的那個函數之外包覆的這個函數主要是為了利用 Closure把 next傳給內層的事件處理函數這個方法更常使用的地方是為了解決一些

scope問題例如

ltscriptgt var accu=0count=10 for(var i=0 iltcount i++)

setTimeout(

function() countndash accu+=i if(countlt=0)

consolelog(accu)

50)

ltscriptgt

最後得出的結果會是 100而不是想像中的 45這是因為等到 setTime-out指定的函數執行時變數 i已經變成 10而離開迴圈了要解決這個問題就需要透過 Closure來保存變數 i

ltscriptgt var accu=0count=10 for(var i=0 iltcount i++)

setTimeout(

function(i) return function() countndash

accu+=i if(countlt=0)

consolelog(accu)

(i)

50)

25 函數返回函數與 Currying 17

Nodejs中文電子書

淺藍色底色的部份是跟上面例子不一樣的地方 ltscriptgt

函數返回函數的另外一個用途是可以暫緩函數執行例如

function add(m n) return m+n

var a = add(20 10) consolelog(a)

add這個函數必須同時輸入兩個參數才有辦法執行如果我希望這個函數可以先給它一個參數等一些處理過後再給一個參數然後得到結

果就必須用函數返回函數的方式做修改

function add(m)

return function(n) return m+n

var wait another arg = add(20)先給一個參數 var a = function(arr)

var ret=0 for(var i=0iltarrlengthi++) ret+=arr[i] return ret

([1234])計算一下另一個參數 var b = wait another arg(a)然後再繼續執行 consolelog(b)

像這樣利用函數返回函數使得原本接受多個參數的函數可以一次

接受一個參數直到參數接收完成才執行得到結果的方式有一個學名就

叫做Currying

綜合以上許多奇技淫巧就可以透過用函數來處理函數的方式調整

程式流程接下來看看

26 流程控制

(以 sync方式使用 async函數避開巢狀 callback循序呼叫 async callback等奇技淫巧)

建議參考

bull httphowtonodeorgcontrol- ow

bull httphowtonodeorgcontrol- ow-part-ii

18 2 JavaScript與 NodeJS

Nodejs中文電子書

bull httphowtonodeorgcontrol- ow-part-iii

bull httpblogmixunet20110202essential-node-js-patterns-and-snippets

這幾篇都是非常經典的 NodeJSJavascript流程控制好文章(阿mixu是在介紹一些 pattern時提到這方面的主題)不過我還是用幾個簡單的程式介紹一下做法跟概念

並發與等待

下面的程式參考了 mixu文章中的做法

var wait = function(callbacks done) consolelog(lsquowait startrsquo) var counter = call-backslength var results = [] var next = function(result) 接收函數執行結果並判斷是否結束執行 resultspush(result) if(ndashcounter == 0) done(results)如果結束執行就把所有執行結果傳給指定的 callback處理 for(var i = 0 i lt callbackslength i++) 依次呼叫所有要執行的函數 callbacks[i](next) consolelog(lsquowait endrsquo)

wait( [ function(next) setTimeout(function() consolelog(lsquodone arsquo) var result =500 next(result) 500) function(next) setTimeout(function() con-solelog(lsquodone brsquo) var result = 1000 next(result) 1000) function(next)setTimeout(function() consolelog(lsquodone crsquo) var result = 1500 next(1500) 1500) ] function(results) var ret = 0 i=0 for( iltresultslength i++) ret+= results[i] consolelog(lsquodone all result lsquo+ret)

)

執行結果wait start wait end done a done b done c done all result 3000

可以看出來其實 wait並不是真的等到所有函數執行完才結束執行而是在所有傳給他的函數執行完畢後(不論同步非同步)才執行處理結

果的函數(也就是 done())

不過這樣的寫法還不夠實用因為沒辦法實際讓函數可以等待執行

完畢又能當作事件處理函數來實際使用上面參考到的 Tim Caswell的文

26 流程控制 19

Nodejs中文電子書

章裡面有一種解法不過還需要額外包裝(在他的例子中)NodeJS核心的 fs物件把一些函數(例如 readFile)用 Currying處理類似像這樣

var fs = require(lsquofsrsquo) var readFile = function(path)

return function(callback errback)

fsreadFile(path function(err data)

if(err) errback()

else callback(data)

)

其他部份可以參考 Tim Caswell的文章他的 Doparallel跟上面的 wait差不多意思這裡只提示一下他沒說到的地方

另外一種做法是去修飾一下 callback當他作為事件處理函數執行後再用 cps的方式取得結果

ltscriptgt function Wait(fns done)

var count = 0 var results = [] thisgetCallback = function(index)

count++ return (function(waitback)

return function() var i=0args=[]for(iltargumentslengthi++)

argspush(arguments[i])

argspush(waitback) fns[index]apply(thisargs)

)(function(result) resultspush(result) if(ndashcount == 0)

20 2 JavaScript與 NodeJS

Nodejs中文電子書

done(results)

)

var a = new Wait(

[ function(waitback) consolelog(lsquodone arsquo) var result = 500 wait-back(result) function(waitback) consolelog(lsquodone brsquo) var result =1000 waitback(result) function(waitback) consolelog(lsquodone crsquo) varresult = 1500 waitback(result) ] function(results) var ret = 0 i=0for( iltresultslength i++) ret += results[i] consolelog(lsquodone allresult lsquo+ret)

) var callbacks = [agetCallback(0)agetCallback(1)agetCallback(0)agetCallback(2)]一次取出要使用的 callbacks避免結果提早送出 setTimeout(callbacks[0]500) setTimeout(callbacks[1] 1000) setTimeout(callbacks[2] 1500) setTime-out(callbacks[3] 2000) 當所有取出的 callbacks執行完畢就呼叫 done()來處理結果 ltscriptgt

執行結果

done a done b done a done c done all result 3500

上面只是一些小實驗更成熟的作品是 Tim Caswell 的 stephttpsgithubcomcreationixstep

如果希望真正使用同步的方式寫非同步則需要使用 Promisejs這一類的 library來轉換非同步函數不過他結構比較複雜 XD(見仁見智不過有些人認為 Promise有點過頭了)httpblogsmsdncombrbucktonarchive20110815promise-js-2-0-promise-framework-for-javascriptaspx

如果想不透過其他 Library做轉換又能直接用同步方式執行非同步函數大概就要使用一些需要額外 compile原始程式碼的方法了例如 BrunoJouhier的 streamlinejshttpsgithubcomSagestreamlinejs

26 流程控制 21

Nodejs中文電子書

循序執行

循序執行可以協助把非常深的巢狀 callback結構攤平例如用這樣的簡單模組來做(serialjs)

moduleexports = function(funs) var c = 0 if(isArrayOfFunctions(funs))

throw(lsquoArgument type was not matched Should be array of func-tionsrsquo)

return function()

var args = Arrayprototypeslicecall(arguments 0) if((cgt=funslength))

c++ return funs[c-1]apply(this args)

function isArrayOfFunctions(f ) if(typeof f == lsquoobjectrsquo) return false if(flength)return false if(fconcat) return false if(fsplice) return false var i = 0 for(iltflength i++)

if(typeof f[i] == lsquofunctionrsquo) return false

return true

簡單的測試範例(testSerialjs)使用 fs 模組確定某個 path 是檔案然後讀取印出檔案內容這樣會用到兩層的 callback所以測試中有使用serial的版本與 nested callbacks的版本做對照

var serial = require(lsquoserialrsquo) fs = require(lsquofsrsquo) path = lsquodclientjsrsquo cb = serial([ func-tion(err data)

if(err)

if(dataisFile) fsreadFile(path cb)

22 2 JavaScript與 NodeJS

Nodejs中文電子書

else consolelog(err)

function(err data)

if(err) consolelog(lsquo[ attened by searial]rsquo) con-solelog(datatoString(lsquoutf8rsquo))

else consolelog(err)

]) fsstat(path cb)

fsstat(path function(err data) 第一層 callback if(err)

if(dataisFile)

fsreadFile(path function(err data) 第二層 callback if(err)

consolelog(lsquo[nested callbacks]rsquo) con-solelog(datatoString(lsquoutf8rsquo))

else consolelog(err)

)

else consolelog(err)

)

關鍵在於這些 callback的執行是有順序性的所以利用 serial返回的一個函數 cb來取代這些 callback然後在 cb中控制每次會循序呼叫的函數

26 流程控制 23

Nodejs中文電子書

就可以把巢狀的 callback攤平成循序的 function陣列(就是傳給 serial函數的參數)

測試中的dclientjs 是一個簡單的 dnode 測試程式放在跟 testSerialjs同一個目錄

var dnode = require(lsquodnodersquo)

dnodeconnect(8000 lsquolocalhostrsquo function(remote)

remoterestart(function(str) consolelog(str) processexit()

)

)

執行測試程式後出現結果

[ attened by searial] var dnode = require(lsquodnodersquo)

dnodeconnect(8000 lsquolocalhostrsquo function(remote)

remoterestart(function(str) consolelog(str) processexit()

)

)

[nested callbacks] var dnode = require(lsquodnodersquo)

dnodeconnect(8000 lsquolocalhostrsquo function(remote)

remoterestart(function(str) consolelog(str) processexit()

)

)

對照起來看兩種寫法的結果其實是一樣的但是利用 serialjs巢狀的 callback結構就會消失

不過這樣也只限於順序單純的狀況如果函數執行的順序比較複雜

(不只是一直線)還是需要用功能更完整的流程控制模組比較好例如

httpsgithubcomcaolanasync

24 2 JavaScript與 NodeJS

3

Nodejs安裝與設定

本篇將講解如何在各個不同 OS建立 NodeJS環境目前 NodeJS 048版本環境架設方式需依賴 Linux指令才可編譯完成當然在不同作業系統中也已經有 NodeJS package可以直接使用指令快速架設以下各不同作業系統解說如何安裝 NodeJS

31 Ubuntu Linux

更新推薦使用 nvm

git clone gitgithubcomcreationixnvmgit ~nvm

echo rdquo ~nvmnvmshrdquo gtgt ~bashrc

nvm install v0614

nvm alias default v0614

以 上 可 參 考 http dreamerslabcom blog tw how-to-setup-a-node-js-development-environment-on-ubuntu-11-04

使用 APT套件管理工具是常見的方法以下是使用社群提供的 PPA安裝方式

sudo apt-get install python-software-properties

sudo add-apt-repository ppachris-leanodejs-devel

25

Nodejs中文電子書

sudo apt-get update

sudo apt-get install nodejs

32 Other Linux

Linux很適合作為 NodeJS的伺服器作業系統及開發環境安裝前請先確認以下套件已正確安裝

bull curl (wget)用來下載檔案的工具

bull git先進的版本控制工具

bull g++ GNU C++軟體編譯工具

bull make GNU軟體專案建置工具

安裝指令如下如設有權限問題請在指令前面加上 sudo

git clone httpsgithubcomjoyentnodegit

cd node

git checkout v067

configure

make

sudo make install

接著測試 nodeJS是否正常執行

node --version

出現版本訊息即表示安裝成功

33 Windows

nodeJS 在 v060 版本之後開始正式支援 windows native直接使用nodeexe 就可以執行程式支援性完全與 linux 相同更棒的部份就是不需經過編譯經過下載之後簡單設定完成立即開發 node程式

26 3 Nodejs安裝與設定

Nodejs中文電子書

下載 nodejs安裝檔案 lthttpnodejsorgdownloadgt

如此完成 windows native nodeexe安裝接著可以進入 command line執行測試在 command line輸入rdquonoderdquo會出現 nodejs版本訊息畫面表示安裝完成

33 Windows 27

Nodejs中文電子書

28 3 Nodejs安裝與設定

4

Nodejs基礎

前篇文章已經由介紹安裝至設定都有完整介紹nodeJS 內部除了javascript常用的函式 (function)物件 (object)之外也有許多不同的自訂物件nodeJS預設建立這些物件為核心物件是為了要讓開發流程更為這些資料在官方文件已經具有許多具體說明接下來將會介紹在開發 nodeJS程式時常見的物件特性與使用方法

41 nodejs http伺服器建立

在 lsquonodejs官方網站 lthttpnodejsorggtlsquo裡面有舉一個最簡單的 HTTP伺服器建立一開始初步就是建立一個伺服器平台讓 nodejs可以與瀏覽器互相行為每種語言一開始的程式建立都是以 Hello world開始最初也從 Hello world帶各位進入 nodejs的世界

輸入以下程式碼儲存檔案為 node basic http hello worldjs

var server

ip = rdquo127001rdquo

port = 1337

http = require(rsquohttprsquo)

server = httpcreateServer(function (req res)

29

Nodejs中文電子書

reswriteHead(200 rsquoContent-Typersquo rsquotextplainrsquo)

resend(rsquoHello Worldnrsquo)

)

serverlisten(port ip)

consolelog(rdquoServer running at httprdquo + ip + rdquordquo + port)

程式碼解講一開始需要有幾個基本的變數

bull ip 機器本身的 ip位置因為使用本地端因此設定為 127001

bull port 需要開通的阜號通常設定為 http port 80因範例不希望與基本 port相衝隨意設定為 1337

在 nodejs 的程式中有許多預設的模組可以使用因此需要使用require方法將模組引入在這邊我們需要使用 http這個模組因此將 http載入Http模組裡面內建有許多方法可以使用這邊採用 createServer創建一個基本的 http伺服器再將 http伺服器給予一個 server變數裡面的回呼函式 (call back function)可以載入 http伺服器的資料與回應方法 (requestresponse)在程式裡面就可以看到我們直接回應給瀏覽器端所需的 Header回應內容

reswriteHead(200 rsquoContent-Typersquo rsquotextplainrsquo)

resend(rsquoHello Worldnrsquo)

Http伺服器需要設定 port ip在最後需要設定 Http監聽需要使用到listen事件監聽所有 Http伺服器行為

httplisten(port ip)

所有事情都完成之後需要確認伺服器正確執行因此使用 console在javascript裡就有這個原生物件console所印出的資料都會顯示於 nodejs伺服器頁面這邊印出的資料並不會傳送到使用者頁面上之後許多除壞

(debug)都會用到 console物件

consolelog(rdquoServer running at httprdquo + ip + rdquordquo + port)

30 4 Nodejs基礎

Nodejs中文電子書

42 nodejs http路徑建立

前面已經介紹如何建立一個簡單的 http伺服器接下來本章節將會介紹如何處理伺服器路徑 (rout)問題在 http的協定下所有從瀏覽器發出的要求 (request)都需要經過處理路徑上的建立也是如此

路徑就是指伺服器 ip位置或者是網域名稱之後對於伺服器給予的要求修改剛才的 hello world檔案修改如下

server = httpcreateServer(function (req res)

consolelog(requrl)

reswriteHead(200 rsquoContent-Typersquo rsquotextplainrsquo)

resend(rsquohello worldnrsquo)

)

重新啟動 nodejs程式後在瀏覽器端測試一下路徑行為結果如下圖

當在瀏覽器輸入 http1270011337test 在伺服器端會收到兩個要求一個是我們輸入的test要求另外一個則是faviconicotest的路徑要求http伺服器本身需要經過程式設定才有辦法回應給瀏覽器端所需要的回應在伺服器中所有的路徑要求都是需要被解析才有辦法取得資料從

42 nodejs http路徑建立 31

Nodejs中文電子書

上面解說可以了解到在 nodejs當中所有的路徑都需要經過設定未經過設定的路由會讓瀏覽器無法取得任何資料導致錯誤頁面的發生底下將會解

說如何設定路由同時避免發生錯誤情形先前 nodejs程式需要增加一些修改才能讓使用者透過瀏覽器在不同路徑時有不同的結果根據剛才

的程式做如下的修改

var server

ip = rdquo127001rdquo

port = 1337

http = require(rsquohttprsquo)

url = require(rsquourlrsquo)

path

server = httpcreateServer(function (req res)

path = urlparse(requrl)

reswriteHead(200 rsquoContent-Typersquo rsquotextplainrsquo)

switch (pathpathname)

case rdquoindexrdquo

resend(rsquoI am indexnrsquo)

break

case rdquotestrdquo

resend(rsquothis is test pagenrsquo)

break

default

resend(rsquodefault pagenrsquo)

break

)

serverlisten(port ip)

consolelog(rdquoServer running at httprdquo + ip + rdquordquo + port)

程式做了片段的修改首先載入 url模組另外增加一個 path變數url模組就跟如同他的命名一般專門處理 url字串處理裡面提供了許多方法來解決路徑上的問題因為從瀏覽器發出的要求路徑可能會帶有多種需求

或者 GET參數組合等因此我們需要將路徑單純化取用路徑部分的資料即可例如使用者可能會送出 http1270011337testsend=1如果直接

32 4 Nodejs基礎

Nodejs中文電子書

信任 requrl就會收到結果為testsend=1所以需要透過 url模組的方法將路徑資料過濾

在這邊使用 urlparse的方法裡面帶入網址格式資料會回傳路徑資料為了後需方便使用將回傳的資料設定到 path變數當中在回傳的路徑資料裡面包含資訊如下圖

這邊只需要使用單純的路徑要求直接取用 pathpathname就可以達到我們的目的

最後要做路徑的判別在不同的路徑可以指定不同的輸出在範例中

有三個可能結果第一個從瀏覽器輸入index就會顯示 index結果test 就會呈現出 test頁面最後如果都不符合預期的輸入會直接顯示 default的頁面最後的預防可以讓瀏覽器不會出現非預期結果讓程式的可靠性提昇

底下為測試結果

42 nodejs http路徑建立 33

Nodejs中文電子書

43 nodejs檔案讀取

前面已經介紹如何使用路由(rount)做出不同的回應實際應用只有在瀏覽器只有輸出幾個文字資料總是不夠的在本章節中將介紹如何使

用檔案讀取輸出檔案資料讓使用者在前端瀏覽器也可以讀取到完整的

html css javascript檔案輸出

檔案管理最重要的部分就是 lsquoFile system lthttpnodejsorgdocslatestapifshtmlgtlsquo這個模組此模組可以針對檔案做管理監控讀取等行為裡面有許多預設的方法底下是檔案輸出的基本範例底下會有兩個檔案

第一個是靜態 html檔案

ltDOCTYPE htmlgt

lthtml xmlns=rdquohttpwwww3org1999xhtmlrdquo xmllang=rdquozh-CNrdquo lang=rdquozh-CNrdquogt

ltheadgt

ltmeta http-equiv=rdquoContent-Typerdquo content=rdquotexthtml charset=UTF-7rdquo gt

lttitlegtnodejs index html filelttitlegt

ltheadgt

ltbodygt

34 4 Nodejs基礎

Nodejs中文電子書

lth1gtnodejs index html filelth1gt

ltbodygt

lthtmlgt

另一個為 nodejs程式

var fs = require(rdquofsrdquo)

filename = rdquostaticindexhtmlrdquo

encode = rdquoutf8rdquo

fsreadFile(filename encode function(err file)

consolelog(file)

)

一開始直接載入 le system模組 載入名稱為 fs讀取檔案主要使

用的方法為 readFile裡面以三個參數路徑 ( le path) 編碼方式 (encoding)

回應函式 (callback)路徑必須要設定為靜態 html所在位置才能指定到正確的檔案靜態檔案的編碼方式也必須正確這邊使用靜態檔案的編

碼為 utf8如果編碼設定錯誤nodejs讀取出來檔案結果會使用 byte raw格式輸出如果錯誤編碼格式會導致輸出資料為 byte raw

回應函式中裡面會使用兩個變數error為錯誤資訊如果讀取的檔案不存在或者發生錯誤error數值會是 true如果成功讀取資料 error將會是 falsecontent則是檔案內容資料讀取後將會把資料全數丟到 content這個變數當中

最後程式的輸出結果畫面如下

43 nodejs檔案讀取 35

Nodejs中文電子書

44 nodejs http靜態檔案輸出

前面已經了解如何讀取本地端檔案接下來將配合 http伺服器路由讓每個路由都能夠輸出相對應的靜態 html檔案首先新增加幾個靜態 html檔案

ltDOCTYPE htmlgt

lthtml xmlns=rdquohttpwwww3org1999xhtmlrdquo xmllang=rdquozh-CNrdquo lang=rdquozh-CNrdquogt

ltheadgt

ltmeta http-equiv=rdquoContent-Typerdquo content=rdquotexthtml charset=UTF-7rdquo gt

lttitlegtnodejs index html filelttitlegt

ltheadgt

ltbodygt

lth1gtnodejs index html filelth1gt

ltbodygt

lthtmlgt

ltDOCTYPE htmlgt

lthtml xmlns=rdquohttpwwww3org1999xhtmlrdquo xmllang=rdquozh-CNrdquo lang=rdquozh-CNrdquogt

ltheadgt

ltmeta http-equiv=rdquoContent-Typerdquo content=rdquotexthtml charset=UTF-7rdquo gt

lttitlegtnodejs test html filelttitlegt

ltheadgt

ltbodygt

lth1gtnodejs test html filelth1gt

ltbodygt

lthtmlgt

ltDOCTYPE htmlgt

lthtml xmlns=rdquohttpwwww3org1999xhtmlrdquo xmllang=rdquozh-CNrdquo lang=rdquozh-CNrdquogt

ltheadgt

ltmeta http-equiv=rdquoContent-Typerdquo content=rdquotexthtml charset=UTF-7rdquo gt

lttitlegtnodejs static html filelttitlegt

ltheadgt

ltbodygt

lth1gtnodejs static html filelth1gt

ltbodygt

lthtmlgt

36 4 Nodejs基礎

Nodejs中文電子書

準備一個包含基本路由功能的 http伺服器

var server

ip = rdquo127001rdquo

port = 1337

http = require(rsquohttprsquo)

url = require(rsquourlrsquo)

server = httpcreateServer(function (req res)

var path = urlparse(requrl)

)

serverlisten(port ip)

consolelog(rdquoServer running at httprdquo + ip + rdquordquo + port)

加入 le system 模組使用 readFile 的功能將這一段程式放置於

createServer的回應函式中

fsreadFile(filePath encode function(err file)

)

readFile的回應函式裡面加入頁面輸出讓瀏覽器可以正確讀到檔案在這邊我們設定讀取的檔案為 html 靜態檔案所以 Content-type 設定為texthtml讀取到檔案的內容將會正確輸出成 html靜態檔案

fsreadFile(filePath encode function(err file)

reswriteHead(200 rsquoContent-Typersquo rsquotexthtmlrsquo)

reswrite(file)

resend()

)

到這邊為止基本的程式內容都已經完成剩下一些細節的調整首先

路徑上必須做調整目前的靜態檔案全部都放置於 static資料夾底下設定一個變數來記住資料夾位置

接著將瀏覽器發出要求路徑與資料夾組合讀取正確 html靜態檔案使用者有可能會輸入錯誤路徑所以在讀取檔案的時候要加入錯誤處理

同時回應 404伺服器無法正確回應的 http header格式

加入這些細節的修改一個基本的 http 靜態 html輸出伺服器就完成了完整程式碼如下

44 nodejs http靜態檔案輸出 37

Nodejs中文電子書

var server

ip = rdquo127001rdquo

port = 1337

http = require(rsquohttprsquo)

fs = require(rdquofsrdquo)

folderPath = rdquostaticrdquo

url = require(rsquourlrsquo)

path

filePath

encode = rdquoutf8rdquo

server = httpcreateServer(function (req res)

path = urlparse(requrl)

filePath = folderPath + pathpathname

fsreadFile(filePath encode function(err file)

if (err)

reswriteHead(404 rsquoContent-Typersquo rsquotextplainrsquo)

resend()

return

reswriteHead(200 rsquoContent-Typersquo rsquotextapplicationrsquo)

reswrite(file)

resend()

)

)

serverlisten(port ip)

consolelog(rdquoServer running at httprdquo + ip + rdquordquo + port)

45 nodejs http GET資料擷取

http伺服器中除了路由之外另一個最常使用的方法就是擷取 GET資料本單元將會介紹如何透過基本 http伺服器擷取瀏覽器傳來的要求擷取 GET資料

在 http協定中GET參數都是藉由 URL從瀏覽器發出要求送至伺服器

38 4 Nodejs基礎

Nodejs中文電子書

端基本的傳送網址格式可能如下

http127001testsend=1amptest=2

上面這段網址裡面的 GET參數就是 send而這個變數的數值就為 1如果想要在 http伺服器取得 GET資料需要在瀏覽器給予的要求 (request)做處理

首先需要載入 query string這個模組這個模組主要是用來將字串資料

過濾後轉換成javascript物件

qs = require(rsquoquerystringrsquo)

接著在第一階段利用 url模組過濾瀏覽器發出的 URL資料後將回應的物件裡面的 query這個變數是一個字串值資料過濾後如下

send=1amptest=2

透過 query string使用 parse這個方法將資料轉換成 javascript物件就表示 GET的資料已經被伺服器端正式擷取下來

path = urlparse(requrl)

parameter = qsparse(pathquery)

整個 nodejs http GET參數完整擷取程式碼如下

var server

ip = rdquo127001rdquo

port = 1337

http = require(rsquohttprsquo)

qs = require(rsquoquerystringrsquo)

url = require(rsquourlrsquo)

server = httpcreateServer(function (req res)

var path = urlparse(requrl)

parameter = qsparse(pathquery)

consoledir(parameter)

reswriteHead(200 rsquoContent-Typersquo rsquotextplainrsquo)

reswrite(rsquoBrowser test GET parameternrsquo)

resend()

)

45 nodejs http GET資料擷取 39

Nodejs中文電子書

serverlisten(port ip)

consolelog(rdquoServer running at httprdquo + ip + rdquordquo + port)

程式運作之後由瀏覽器輸入要求網址之後nodejs伺服器端回應資料為

Server running at http1270011337

send rsquo1rsquo test rsquo1rsquo

46 本章結語

前面所解說的部份一大部分主要是處理 http伺服器基本問題雖然在某些部分有牽扯到 http 伺服器基本運作原理主要還是希望可以藉由這些基本範例練習 nodejs練習回應函式與語法串接的特點習慣編寫javascript風格程式當然直接這樣開發 nodejs是非常辛苦的接下來在模組實戰開發的部份將會介紹特定的模組一步一步帶領各位從無到有進行

nodejs應用程式開發

40 4 Nodejs基礎

5

NPM套件管理工具

npm全名為 Node Package Manager是 Nodejs的套件(package)管理工具類似 Perl的 ppm或 PHP的 PEAR等安裝 npm後使用npm install

module name指令即可安裝新套件維護管理套件的工作會更加輕鬆

npm可以讓Nodejs的開發者直接利用擴充線上的套件庫(packagesregistry)加速軟體專案的開發npm提供很友善的搜尋功能可以快速找到安裝需要的套件當這些套件發行新版本時npm也可以協助開發者自動更新這些套件

npm不僅可用於安裝新的套件它也支援搜尋列出已安裝模組及更新的功能

51 安裝 NPM

Nodejs在 063版本開始內建 npm讀者安裝的版本若是此版本或更新的版本就可以略過以下安裝說明

若要檢查 npm是否正確安裝可以使用以下的指令

npm -v

41

Nodejs中文電子書

執行結果說明

若 npm正確安裝執行 npm -v將會看到類似 110-2的版本訊息

若讀者安裝的 Nodejs版本比較舊或是有興趣嘗試自己動手安裝 npm工具則可以參考以下的說明

安裝於Windows系統

Nodejs for Windows 於 062 版開始內建 npm使用 nodejsorg 官方提供的安裝程式不需要進一步的設定就可以立即使用 npm指令對於Windows的開發者來說大幅降低環境設定的問題與門檻

除了使用 Nodejs內建的 npm讀者也可以從 npm官方提供的以下網址

httpnpmjsorgdist

這是由 npm提供的 Fancy Windows Install版本請下載壓縮檔(例如npm-110-3zip)並將壓縮檔內容解壓縮至 Nodejs的安裝路徑(例如CProgram Filesnodejs)

解壓縮後在 Nodejs的安裝路徑下應該有以下的檔案及資料夾

bull npmcmd(檔案)

bull node modules(資料夾)

安裝於 Linux系統

Ubuntu Linux的使用者可以加入 NPM Unoffcial PPA這個 repository即可使用 apt-get完成 npm安裝

42 5 NPM套件管理工具

Nodejs中文電子書

Ubuntu Linux使用 apt-get安裝 npm

sudo apt-get install python-software-properties

sudo add-apt-repository ppagias-kay-leenpm

sudo apt-get update

sudo apt-get install npm

npm官方提供的安裝程式 installsh可以適用於大多數的 Linux系統使用這個安裝程式請先確認

1 系統已安裝 curl工具(請使用 curl --version查看版本訊息)

2 已安裝 Nodejs並且 PATH正確設置

3 Nodejs的版本必須大於 04x

以下為 npm提供的安裝指令

curl httpnpmjsorginstallsh | sh

安裝成功會看到如下訊息

installsh安裝成功的訊息

npm10105 homeUSERNAMElocalnodelibnode_modulesnpm

It worked

安裝於Mac OS X

建議採用與 Nodejs相同的方式進行 npm的安裝例如使用MacPorts安裝 Nodejs就同樣使用MacPorts安裝 npm這樣對日後的維護才會更方便容易

使用 MacPorts安裝 npm是本書比較建議的方式它可以讓 npm的安裝移除及更新工作自動化將會幫助開發者節省寶貴時間

51 安裝 NPM 43

Nodejs中文電子書

安裝MacPorts的提示

在MacPorts網站可以取得 OS X系統版本對應的安裝程式(例如 106或 107)httpwwwmacportsorg安裝過程會詢問系統管理者密碼使用預設的選項完成安裝即可安

裝MacPorts之後在終端機執行 port -v將會看到MacPorts的版本訊息

安裝 npm之前先更新MacPorts的套件清單以確保安裝的 npm是最新版本

sudo port -d selfupdate

接著安裝 npm

sudo port install npm

若讀者的 Nodejs並非使用MacPorts安裝則不建議使用MacPorts安裝npm因為 MacPorts會自動檢查並安裝相依套件而 npm相依 nodejs所以MacPorts也會一併將 nodejs套件安裝造成先前讀者使用其它方式安裝的 nodejs被覆蓋

讀者可以先使用 MacPorts安裝 curl(sudo port install curl)

再參考 Linux的 installsh安裝方式即可使用 npm官方提供的安裝程式

NPM安裝後測試

npm是指令列工具(command-line tool)使用時請先打開系統的文字終端機工具

測試 npm安裝與設定是否正確請輸入指令如下

npm -v

或是

npm --version

如果 npm已經正確安裝設定就會顯示版本訊息

44 5 NPM套件管理工具

Nodejs中文電子書

執行結果(範例)

110-2

52 使用 NPM安裝套件

npm目前擁有超過 6000種套件(packages)可以在 npm registry使用關鍵字搜尋套件

httpsearchnpmjsorg

舉例來說在關鍵字欄位輸入「coffee-script」下方的清單就會自動列出包含 coffee-script關鍵字的套件

接著我們回到終端機模式的操作npm的指令工具本身就可以完成套

件搜尋的任務

例如以下的指令同樣可以找出 coffee-script相關套件

npm search coffee-script

以下是搜尋結果的參考畫面

52 使用 NPM安裝套件 45

Nodejs中文電子書

找到需要的套件後(例如 express)即可使用以下指令安裝

npm install coffee-script

值得注意的一點是使用 npm install會將指定的套件安裝在工

作目錄(Working Directory)的 node modules資料夾下

以Windows為例如果執行 npm install的目錄位於

Cproject1

那麼 npm將會自動建立一個 node modules的子目錄(如果不存在)

Cproject1node modules

並且將下載的套件放置於這個子目錄例如

Cproject1node modulescoffee-script

這個設計讓專案可以個別管理相依的套件並且可以在專案佈署或發

行時將這些套件(位於 node modules)一併打包方便其它專案的使用者不必再重新下載套件

這個 npm install的預設安裝模式為 local(本地)只會變更當前專案的資料夾不會影響系統

另一種安裝模式稱為 global(全域)這種模式會將套件安裝到系統資

料夾也就是 npm安裝路徑的 node modules資料夾例如

CProgram Filesnodejsnode modules

46 5 NPM套件管理工具

Nodejs中文電子書

是否要使用全域安裝可以依照套件是否提供新指令來判斷舉例來說express 套件提供 express 這個指令而 coffee-script 則提供 coffee

指令

在 local 安 裝 模 式 中這 些 指 令 的 程 式 檔 案會 被 安 裝 到node modules的 bin這個隱藏資料夾下除非將bin的路徑加入 PATH環境變數否則要執行這些指令將會相當不便

為了方便指令的執行我們可以在 npm install 加上 -g 或 --

global參數啟用 global安裝模式例如

npm install -g coffee-script

npm install -g express

使用 global安裝模式需要注意執行權限的問題若權限不足可能會出現類似以下的錯誤訊息

npm ERR Error EACCES permission denied rsquorsquo

npm ERR

npm ERR Please try running this command again as rootAdministrator

要獲得足夠得執行權限請參考以下說明

bull Windows 7 或 2008 以上在「命令提示字元」的捷徑按右鍵選擇「以系統管理員身分執行」執行 npm指令時就會具有 Administrator身分

bull Mac OS X或 Linux系統可以使用 sudo指令例如

sudo npm install -g express

bull Linux系統可以使用 root權限登入或是以「sudo su -」切換成 root身分(使用 root權限操作系統相當危險因此並不建議使用這種方式)

若加上 -g參數使用 npm install -g coffee-script完成安裝

後就可以在終端機執行 coffee指令例如

coffee -v

52 使用 NPM安裝套件 47

Nodejs中文電子書

執行結果(範例)

CoffeeScript version 120

53 套件的更新及維護

除了前一節說明的 search 及 install 用法npm 還提供其他許多指令(commands)

使用 npm help可以查詢可用的指令

npm help

執行結果(部分)

where ltcommandgt is one of

adduser apihelp author bin bugs c cache completion

config deprecate docs edit explore faq find get

help help-search home i info init install la link

list ll ln login ls outdated owner pack prefix

prune publish r rb rebuild remove restart rm root

run-script s se search set show star start stop

submodule tag test un uninstall unlink unpublish

unstar up update version view whoami

使用 npm help command可以查詢指令的詳細用法例如

npm help list

接下來本節要介紹開發過程常用的 npm指令

使用 list可以列出已安裝套件

npm list

48 5 NPM套件管理工具

Nodejs中文電子書

執行結果(範例)

-- coffee-script120

- express256

- connect185

| -- formidable108

-- mime124

-- mkdirp007

-- qs041

檢視某個套件的詳細資訊例如

npm show express

升級所有套件(如果該套件已發佈更新版本)

npm update

升級指定的套件

npm update express

移除指定的套件

npm uninstall express

54 使用 packagejson

對於正式的 Nodejs專案可以建立一個命名為 packagejson的設

定檔(純文字格式)檔案內容參考範例如下

54 使用 packagejson 49

Nodejs中文電子書

packagejson(範例)

rdquonamerdquo rdquoapplication-namerdquo

rdquoversionrdquo rdquo001rdquo

rdquoprivaterdquo true

rdquodependenciesrdquo

rdquoexpressrdquo rdquo255rdquo

rdquocoffee-scriptrdquo rdquolatestrdquo

rdquomongooserdquo rdquogt= 253rdquo

其中 name與 version依照專案的需求設置

需要注意的是 dependencies的設定它用於指定專案相依的套件名

稱及版本

bull rdquoexpressrdquo rdquo255rdquo

代表此專案相依版本 255的 express套件

bull rdquocoffee-scriptrdquo rdquolatestrdquo

使用最新版的 coffee-script套件(每次更新都會檢查新版)

bull rdquomongooserdquo rdquogt= 253rdquo

使用版本大於 253的 mongoose套件

假設某個套件的新版可能造成專案無法正常運作就必須指定套件的

版本避免專案的程式碼來不及更新以相容新版套件通常在開發初期的

專案需要盡可能維持新套件的相容性(以取得套件的更新或修正)可以

用「gt=」設定最低相容的版本或是使用「latest」設定永遠保持最新

套件

50 5 NPM套件管理工具

6

Express介紹

在前面的 nodejs基礎當中介紹許多許多開設 http的使用方法及介紹以及許多基本的 nodejs基本應用

接下來要介紹一個套件稱為 express [Express](httpexpressjscom)這個套件主要幫忙解決許多 nodejs http server所需要的基本服務讓開發 httpservice變得更為容易不需要像之前需要透過層層模組(module)才有辦法開始編寫自己的程式

這個套件是由 TJ Holowaychuk 製作而成的套件裡面包含基本的路由處理 (route)http資料處理(GETPOSTPUT)另外還與樣板套件(jshtml template engine)搭配同時也可以處理許多複雜化的問題

61 Express安裝

安裝方式十分簡單只要透過之前介紹的 NPM就可以使用簡單的指令安裝指令如下

這邊建議需要將此套件安裝成為全域模組方便日後使用

51

Nodejs中文電子書

62 Express基本操作

express的使用也十分簡單先來建立一個基本的 hello world

var app = require(rsquoexpressrsquo)createServer()

port = 1337

applisten(port)

appget(rsquorsquo function(req res)

ressend(rsquohello worldrsquo)

)

consolelog(rsquostart express servernrsquo)

可以從上面的程式碼發現基本操作與 nodejs http的建立方式沒有太大差異主要差在當我們設定路由時可以直接透過 appget方式設定回應與接受方式

63 Express路由處理

Express對於 http服務上有許多包裝讓開發者使用及設定上更為方便例如有幾個路由設定那我們就統一藉由 appget來處理

Create http server

appget(rsquorsquo function(req res)

ressend(rsquohello worldrsquo)

)

appget(rsquotestrsquo function(req res)

ressend(rsquotest renderrsquo)

)

appget(rsquouserrsquo function(req res)

ressend(rsquouser pagersquo)

)

52 6 Express介紹

Nodejs中文電子書

如上面的程式碼所表示appget可以帶入兩個參數第一個是路徑名稱設定第二個為回應函式 (call back function)回應函式裡面就如同之前的 createServer方法裡面包含 requestresponse兩個物件可供使用使用者就可以透過瀏覽器輸入不同的 url切換到不同的頁面顯示不同的結果

路由設定上也有基本的配對方式讓使用者從瀏覽器輸入的網址可以

是一個變數只要符合型態就可以有對應的頁面產出例如

Create http server

appget(rsquouseridrsquo function(req res)

ressend(rsquouser rsquo + reqparamsid)

)

appget(rsquonumberrsquo function(req res)

ressend(rsquonumber rsquo + reqparamsnumber)

)

裡 面 使 用 到number 從 網 址 輸 入 之 後 就 可 以 直 接 使 用reqparamsnumber 取得所輸入的資料變成 url 參數使用當然前面也是可以加上路徑的設定userid在瀏覽器上路徑必須符合userxxx透

過 reqparamsid就可以取到 xxx這個字串值

另外express參數處理也提供了路由參數配對處理也可以透過正規表示法作為參數設定

var app = require(rsquoexpressrsquo)createServer()

port = 1337

applisten(port)

appget(^ip((d23)((d23))((d23))((d23))) function(req res)

ressend(reqparams)

)

上面程式碼可以發現後面路由設定的型態是正規表示法裡面設定

格式為ip之後必須要加上 ip型態才會符合資料格式同時取得 ip資料已經由正規表示法將資料做分群因此可以取得 ip的四個數字

此程式執行之後可以透過瀏覽器測試輸入網址為 localhost3000ip25525510010可以從頁面獲得資料

63 Express路由處理 53

Nodejs中文電子書

此章節全部範例程式碼如下

overview

author Caesar Chi

blog clonnblogspotcom

version 20120226

create server

var app = require(rsquoexpressrsquo)createServer()

port = 1337

applisten(port)

normal style

appget(rsquorsquo function(req res)

ressend(rsquohello worldrsquo)

)

appget(rsquotestrsquo function(req res)

ressend(rsquotest renderrsquo)

)

parameter style

appget(rsquouseridrsquo function(req res)

ressend(rsquouser rsquo + reqparamsid)

)

appget(rsquonumberrsquo function(req res)

ressend(rsquonumber rsquo + reqparamsnumber)

)

REGX style

appget(^ip((d23)((d23))((d23))) function(req res)

ressend(reqparams)

)

appget(rsquorsquo function(req res)

ressend(rsquoPage not foundrsquo 404)

54 6 Express介紹

Nodejs中文電子書

)

consolelog(rsquostart express servernrsquo)

64 Express middleware

Express 裡面有一個十分好用的應用概念稱為 middleware可以透過middleware做出複雜的效果同時上面也有介紹 next方法參數傳遞就是靠 middleware的概念來傳遞參數讓開發者可以明確的控制程式邏輯

create http server

appuse(expressbodyParser())

appuse(expressmethodOverride())

appuse(expresssession())

上面都是一種 middleware的使用方式透過 appuse方式裡面載入函式執行方法回應函式會包含三個基本參數responserequestnext其中next表示下一個 middleware執行函式同時會自動將預設三個參數繼續帶往下個函式執行底下有個實驗

上面的片段程式執行後開啟瀏覽器連結上 localhost1337會發現伺服器回應結果順序如下

first middle ware

second middle ware

execute middle ware

end middleware function

從上面的結果可以得知剛才設定的 middleware都生效了在 appuse設定的 middleware是所有 url皆會執行方法如果有指定特定方法就可以使用 appget的 middleware設定在 appget函式的第二個參數就可以帶入函式或者是匿名函式只要函式裡面最後會接受 request response next這三個參數同時也有正確指定 next函式的執行時機最後都會執行到最後一個方法當然開發者也可以評估程式邏輯要執行到哪一個階段讓邏輯

可以更為分明

64 Express middleware 55

Nodejs中文電子書

65 Express路由應用

在實際開發上可能會遇到需要使用參數等方式混和變數一起使用

express裡面提供了一個很棒的處理方法 appall這個方式可以先採用基本路由配對再將設定為每個不同的處理方式開發者可以透過這個方式簡

化自己的程式邏輯

overview

author

version 20120226

create server

var app = require(rsquoexpressrsquo)createServer()

port = 1337

users = [

name rsquoClonnrsquo

name rsquoChirsquo

]

applisten(port)

appall(rsquouseridoprsquo function(req res next)

requser = users[reqparamsid]

if (requser)

next()

else

next(new Error(rsquocannot find user rsquo + reqparamsid))

)

appget(rsquouseridrsquo function(req res)

ressend(rsquoviewing rsquo + requsername)

)

appget(rsquouserideditrsquo function(req res)

ressend(rsquoediting rsquo + requsername)

)

56 6 Express介紹

Nodejs中文電子書

appget(rsquouseriddeletersquo function(req res)

ressend(rsquodeleting rsquo + requsername)

)

appget(rsquorsquo function(req res)

ressend(rsquoPage not foundrsquo 404)

)

consolelog(rsquostart express servernrsquo)

內部宣告一組預設的使用者分別給予名稱設定藉由 appall這個方法可以先將路由雛形建立再接下來設定 appget的路徑格式只要符合格式就會分配進入對應的方法中像上面的程式當中如果使用者輸入路徑為user0除了執行 appall程式之後執行 next方法就會對應到路徑設定為userid的這個方法當中如果使用者輸入路徑為user0edit就會執行到useridedit的對應方法

66 Express GET應用範例

我們準備一個使用 GET方法傳送資料的表單

ltDOCTYPE htmlgt

lthtmlgt

ltheadgt

ltmeta http-equiv=rdquoContent-Typerdquo content=rdquotexthtml charset=utf8rdquogt

lttitlegtNodejs 菜鳥筆記 (1)lttitlegt

ltlink rel=rdquostylesheetrdquo href=rdquocssstylecssrdquo type=rdquotextcssrdquo media=rdquoallrdquo gt

ltheadgt

ltbodygt

lth1gtNodejs 菜鳥筆記-註冊lth1gt

ltform id=rdquosignuprdquo method=rdquoGETrdquo action=rdquohttplocalhost3000Signuprdquogt

ltlabelgt 使用者名稱ltlabelgtltinput type=rdquotextrdquo id=rdquousernamerdquo name=rdquousernamerdquo gtltbrgt

ltlabelgt 電子郵件ltlabelgtltinput type=rdquotextrdquo id=rdquoemailrdquo name=rdquoemailrdquo gtltbrgt

ltinput type=rdquosubmitrdquo value=rdquo 註冊我的帳號rdquo gtltbrgt

ltformgt

ltbodygt

lthtmlgt

66 Express GET應用範例 57

Nodejs中文電子書

這個表單沒有什麼特別的地方我們只需要看第 9 行form 使用的method是 GET然後 action是rdquohttplocalhost3000Signupldquo等一下我們要來撰寫Signup這個 URL Path的處理程式

處理 Signup行為

我們知道所謂的 GET方法會透過 URL來把表單的值給帶過去以上面的表單來說到時候 URL會以這樣的形式傳遞

httplocalhost3000Signupusername=xxxampemail=xxx

所以要能處理這樣的資料必須有以下功能

bull 解析 URL

bull 辨別動作是 Signup

bull 解析出 username和 email

一旦能取得 username和 email的值程式就能加以應用了

處理 Signup的程式碼雛形

load module

var url = require(rsquourlrsquo)

urlData = urlparse(requrltrue)

action = urlDatapathname

reswriteHead(200 rdquoContent-Typerdquordquotexthtml charset=utf-8rdquo)

if (action === rdquoSignuprdquo)

user = urlDataquery

resend(rdquolth1gtrdquo + userusername + rdquo 歡迎您的加入 lth1gtltpgt 我們已經將會員啟用信寄至rdquo + useremail + rdquoltpgtrdquo)

首先需要加載 url module它是用來協助我們解析 URL的模組接著使用 urlparse方法第一個傳入 url字串變數也就是 requrl另外第二個參數的用意是設為 ture則引進 querystring模組來協助處理預設是 false它影響到的是 urlDataquery設為 true會傳回物件不然就只是一般的字串urlparse會將字串內容整理成一個物件我們把它指定給 urlData

action變數作為記錄 pathname這是我們稍後要來判斷目前網頁的動作是什麼接著先將 html表頭資訊 (Header)準備好再來判斷路徑邏輯如

58 6 Express介紹

Nodejs中文電子書

果是 Signup這個動作就把 urlDataquery裡的資料指定給 user然後輸出userusername和 useremail把使用者從表單註冊的資料顯示於頁面中

最後進行程式測試啟動 nodejs主程式之後開啟瀏覽器就會看到表單填寫完畢按下送出就可以看到結果了

完整 nodejs程式碼如下

var http = require(rsquohttprsquo)

url = require(rsquourlrsquo)

fs = require(rdquofsrdquo)

server

server = httpcreateServer(function (reqres)

var urlData

encode = rdquoutf8rdquo

filePath = rdquoviewexpress_get_example_formhtmlrdquo

action

urlData = urlparse(requrltrue)

action = urlDatapathname

reswriteHead(200 rdquoContent-Typerdquordquotexthtml charset=utf-8rdquo)

if (action === rdquoSignuprdquo)

user = urlDataquery

resend(rdquolth1gtrdquo + userusername + rdquo 歡迎您的加入 lth1gtltpgt 我們已經將會員啟用信寄至rdquo + useremail + rdquoltpgtrdquo)

else

fsreadFile(filePath encode function(err file)

reswrite(file)

resend()

)

)

serverlisten(3000)

consolelog(rsquoServer 跑起來了現在時間是rsquo + new Date())

66 Express GET應用範例 59

Nodejs中文電子書

67 Express POST應用範例

一開始準備基本的 html表單傳送內容以 POST方式form的 action屬性設定為 POST其餘 html內容與前一個範例應用相同

ltDOCTYPE htmlgt

lthtmlgt

ltheadgt

ltmeta http-equiv=rdquoContent-Typerdquo content=rdquotexthtml charset=utf8rdquogt

lttitlegtNodejs 菜鳥筆記 (1)lttitlegt

ltlink rel=rdquostylesheetrdquo href=rdquocssstylecssrdquo type=rdquotextcssrdquo media=rdquoallrdquo gt

ltheadgt

ltbodygt

lth1gtNodejs 菜鳥筆記-註冊lth1gt

ltform id=rdquosignuprdquo method=rdquoPOSTrdquo action=rdquohttplocalhost3000Signuprdquogt

ltlabelgt 使用者名稱ltlabelgtltinput type=rdquotextrdquo id=rdquousernamerdquo name=rdquousernamerdquo gtltbrgt

ltlabelgt 電子郵件ltlabelgtltinput type=rdquotextrdquo id=rdquoemailrdquo name=rdquoemailrdquo gtltbrgt

ltinput type=rdquosubmitrdquo value=rdquo 註冊我的帳號rdquo gtltbrgt

ltformgt

ltbodygt

lthtmlgt

nodejs的程式處理邏輯與前面 GET範例類似部分程式碼如下

qs = require(rsquoquerystringrsquo)

if (action === rdquoSignuprdquo)

formData = rsquorsquo

reqon(rdquodatardquo function (data)

formData += data

)

reqon(rdquoendrdquo function ()

user = qsparse(formData)

resend(rdquolth1gtrdquo + userusername + rdquo 歡迎您的加入 lth1gtltpgt 我們已經將會員啟用信寄至rdquo + useremail + rdquoltpgtrdquo)

)

60 6 Express介紹

Nodejs中文電子書

主要加入了rsquoquerystringrsquo這個moduel方便我們等一下解析由表單 POST回來的資料另外加入一個 formData的變數用來搜集待等一下表單回傳的資料前面的 GET範例我們只從 req拿出 url的資料這次要在利用req身上的事件處理

JavaScript 在訂閱事件時使用 addEventListener而 nodejs 使用的則是on這邊加上了監聽 data的事件會在瀏覽器傳送資料到Web Server時被執行參數是它所接收到的資料型態是字串

接著再增加 end的事件當瀏覽器的請求事件結束時它就會動作

由於瀏覽器使用 POST在上傳資料時會將資料一塊塊地上傳因為我們在監聽 data事件時透過 formData變數將它累加起來lt不過由於我們

上傳的資料很少一次就結束不過如果日後需要傳的是資料比較大的檔

案這個累加動作就很重要

當資料傳完就進到 end 事件中會用到 qsparse 來解析 formDataformData的內容是字串內容是

username=wordsmithampemail=wordsmith40somewhere

而 qsparse可以幫我們把這個 querystring轉成物件的格式也就是

username=wordsmithampemail=wordsmith40somewhere

一旦轉成物件並指定給 user之後其他的事情就和 GET方法時操作的一樣寫 response的表頭將內容回傳並將 userusername和 useremail代入到內容中

修改完成後接著執行 nodejs程式啟動 web server開啟瀏覽器進入表單測試看看POST的方式能否順利運作

完整程式碼如下

var http = require(rsquohttprsquo)

url = require(rsquourlrsquo)

fs = require(rdquofsrdquo)

qs = require(rsquoquerystringrsquo)

server

server = httpcreateServer(function (reqres)

var urlData

67 Express POST應用範例 61

Nodejs中文電子書

encode = rdquoutf8rdquo

filePath = rdquoviewexpress_post_example_formhtmlrdquo

formData

action

urlData = urlparse(requrltrue)

action = urlDatapathname

reswriteHead(200 rdquoContent-Typerdquordquotexthtml charset=utf-8rdquo)

if (action === rdquoSignuprdquo)

formData = rsquorsquo

reqon(rdquodatardquo function (data)

formData += data

)

reqon(rdquoendrdquo function ()

user = qsparse(formData)

resend(rdquolth1gtrdquo + userusername + rdquo 歡迎您的加入 lth1gtltpgt 我們已經將會員啟用信寄至rdquo + useremail + rdquoltpgtrdquo)

)

else

fsreadFile(filePath encode function(err file)

reswrite(file)

resend()

)

)

serverlisten(3000)

consolelog(rsquoServer 跑起來了現在時間是rsquo + new Date())

62 6 Express介紹

Nodejs中文電子書

68 Express AJAX應用範例

在 Nodejs要使用 Ajax傳送資料並且與之互動在接受資料的部份沒有太大的差別client端不是用 GET就是用 POST來傳資料重點在處理完後用 JSON格式回傳當然 Ajax不見得只傳 JSON格式有時是回傳一段 HTML碼不過後者對伺服器來說基本上就和前兩篇沒有差別了所以我們還是以回傳 JSON做為這一回的主題

這一回其實大多數的工作都會落在前端 Ajax上面前端要負責發送與接收資料並在接收資料後撤掉原先發送資料的表單並將取得的資料

改成 HTML格式之後放上頁面

首先先準備 HTML靜態頁面資料

ltDOCTYPE htmlgt

lthtmlgt

ltheadgt

lttitlegtNodejs 菜鳥筆記 (3)lttitlegt

ltlink rel=rdquostylesheetrdquo href=rdquocssstylecssrdquo type=rdquotextcssrdquo media=rdquoallrdquo gt

ltscript src=rdquohttpsajaxgoogleapiscomajaxlibsjquery171jqueryminjsrdquogtltscriptgt

ltheadgt

ltbodygt

lth1gtNodejs 菜鳥筆記-註冊 (用 Ajax)lth1gt

ltform id=rdquosignuprdquo action=rdquoSignuprdquo method=rdquoPOSTrdquogt

ltlabelgt 使用者名稱ltlabelgtltinput type=rdquotextrdquo id=rdquousernamerdquo name=rdquousernamerdquo gtltbrgt

ltlabelgt 電子郵件ltlabelgtltinput type=rdquotextrdquo id=rdquoemailrdquo name=rdquoemailrdquo gtltbrgt

ltinput type=rdquosubmitrdquo value=rdquo 註冊我的帳號rdquo gtltbrgt

ltformgt

ltdiv id=rdquoresultrdquogt

ltdivgt

ltscript type=rdquotextjavascriptrdquogt

$(function()$(rsquosignup input[type=rdquosubmitrdquo]rsquo)click(function(e)

var user =

userusername = $(rdquousernamerdquo)val()useremail = $(rdquoemailrdquo)val()

$post(rdquoSignuprdquouserfunction(data)greet(data)

)

68 Express AJAX應用範例 63

Nodejs中文電子書

epreventDefault()

)

var greet = function(msg)

var resultNode = $(rsquoresultrsquo)greeting = $(rsquolth2gtHirsquo+msgusername+rsquo 你的會員 id 是rsquo+ msgid +rsquo 我們會將會員啟動信寄至rsquo+msgemail+rsquolth2gtrsquo)

$(rsquosignuprsquo)hide()resultNodehtml(rdquordquo)

resultNodeappend(greeting)

)

ltscriptgt

ltbodygt

lthtmlgt

HTML頁面上準備了一個表單用來傳送註冊資料接著直接引用了Google CDN來載入 jQuery用來幫我們處理 Ajax的工作這次要傳送和接收的工作很大的變動都在 HTML頁面上的 JavaScript當中我們要做的事有 (相關 jQuery處理這邊不多做贅述指提起主要功能解說)

bull 用 jQUery取得 submit按鈕綁定它的 click動作

bull 取得表單 username和 email的值存放在 user這個物件中

bull 用 jQuery的 $post方法將 user的資料傳到 Server

bull 一旦成功取得資料後透過 greet這個 function組成回報給 user的訊息

bull 清空原本給使用者填資料的表單

bull 將 Server回傳的 usernameemail和 id這 3個資料組成回應的訊息

bull 將訊息放到原本表單的位置

經過以上的處理後一個 Ajax的表單的基本功能已經完成

接著進行 nodejs主要程式的編輯部分程式碼如下

var fs = require(rdquofsrdquo)

64 6 Express介紹

Nodejs中文電子書

qs = require(rsquoquerystringrsquo)

if (action === rdquoSignuprdquo)

formData = rsquorsquo

reqon(rdquodatardquo function (data)

formData += data

)

reqon(rdquoendrdquo function ()

var msg

user = qsparse(formData)

userid = rdquo123456rdquo

msg = JSONstringify(user)

reswriteHead(200 rdquoContent-Typerdquordquoapplicationjson charset=utf-8rdquordquoContent-Lengthrdquomsglength)

resend(msg)

)

這裡的程式和前面 POST範例基本上大同小異差別在

bull 幫 user的資料加上 id隨意存放一些文字進去讓 Server回傳的資料多於 Client端傳上來的不然會覺得 Server都沒做事

bull 增加了 msg 這個變數存放將 user 物件 JSON 文字化的結果JSONstringify這個轉換函式是 V8引擎所提供的如果你好奇的話

bull 大重點來了我們要告訴 Client端這次回傳的資料格式是 JSON所在 Content-type和 Content-Length要提供給 Client

Server很輕鬆就完成任務了最後進行程式測試啟動 nodejs主程式之後開啟瀏覽器就會看到表單填寫完畢按下送出就可以看到結果了

最後 nodejs本篇範例程式碼如下

var http = require(rsquohttprsquo)

url = require(rsquourlrsquo)

fs = require(rdquofsrdquo)

qs = require(rsquoquerystringrsquo)

server

68 Express AJAX應用範例 65

Nodejs中文電子書

server = httpcreateServer(function (reqres)

var urlData

encode = rdquoutf8rdquo

filePath = rdquoviewexpress_ajax_example_formhtmlrdquo

formData

action

urlData = urlparse(requrltrue)

action = urlDatapathname

if (action === rdquoSignuprdquo)

formData = rsquorsquo

reqon(rdquodatardquo function (data)

formData += data

)

reqon(rdquoendrdquo function ()

var msg

user = qsparse(formData)

userid = rdquo123456rdquo

msg = JSONstringify(user)

reswriteHead(200 rdquoContent-TyperdquordquoapplicationjsonrdquordquoContent-Lengthrdquomsglength)

resend(msg)

)

else

fsreadFile(filePath encode function(err file)

reswriteHead(200 rdquoContent-Typerdquordquotexthtml charset=utf-8rdquo)

reswrite(file)

resend()

)

)

serverlisten(3000)

66 6 Express介紹

Nodejs中文電子書

consolelog(rsquoServer 跑起來了現在時間是rsquo + new Date())

69 原始資料提供

bull [NodeJS初學者筆記 (1)-用 GET傳送資料] (httpithelpithomecomtwquestion10087402)

bull [NodeJS初學者筆記 (2)-用 POST傳送資料] (httpithelpithomecomtwquestion10087489)

bull [NodeJS 初學者筆記 (3)-用 Ajax 傳送資料] (httpithelpithomecomtwquestion10087627)

69 原始資料提供 67

Nodejs中文電子書

68 6 Express介紹

7

CoffeeScript

bull Spine

bull Hem

bull Spineapp

Letrsquos Have a Cup of CoffeeScript

bull es5-shim ECMAScript 5 compatibility shims for legacy JavaScript engines

ESnext

node-iform - A middleware for node-validator httpsgithubcomguileennode-iform

69

Nodejs中文電子書

70 7 CoffeeScript

8

製作一個 Hubot的 Plurk Adapter

81 應用事項提醒

此應用範例以CoffeeScript編寫主要程式架構採用Github釋放的Hubot專案以下有幾個主要注意事項請讀者注意

bull Hubot 一開始的架構除了內建的兩個 Adapter 之外其餘都要以Nodejs的Module方式才能運作

bull Hubot的 binhubot裡面寫著 npm install所以不管你怎麼改原始碼也不能改變第一點的狀況

其實上述都在討論同一件事情NodeJS的模組而且模組不能設定相對路徑之類的來安裝一定要包裝成 npm相符和格式才有辦法正確執行

82 建立 Adapter

首先需要準備兩個檔案

bull packagejson

bull plurkcoffee

71

Nodejs中文電子書

有這兩個就足以變成 NodeJS的模組了在開始 Coding之前先來設定一下 packagejson相依性

rdquonamerdquo rdquohubot-plurkrdquo

rdquoversionrdquo rdquo011rdquo

rdquomainrdquo rdquoplurkrdquo

rdquodependenciesrdquo

rdquohubotrdquo rdquogt=205rdquo

rdquooauthrdquo rdquordquo

rdquocronrdquordquordquo

這邊會使用到NodeJS的 cron模組(Module)而登入 Plurk需要OAuth標準授權才行所以也需要加載 OAuth模組

注意Hubot在讀取非內建模組時會自動在前面加上 hubot-的前置

83 建立 Robot跟 API

本篇應用基本上程式碼大部分參考 Hubot - Twitter Adapter來製作主要差異只有在使用 OAuth這個模組Twitter有 Streaming可用而 Plurk則得用 Comet方式來達到即時讀取

plurkcoffee程式碼

Robot = require(rdquohubotrdquo)robot()

Adapter = require(rdquohubtordquo)adapter()

EventEmitter = require(rdquoeventsrdquo)EventEmitter

oauth = require(rdquooauthrdquo)

cronJob = require(rdquocronrdquo)CronJob

class Plurk exntends Adapter

class PlurkStreaming exnteds EventEmitter

72 8 製作一個 Hubot的 Plurk Adapter

Nodejs中文電子書

先弄個基本架構主要 Class皆參考 Twitter Adapter命名方式接著再將 Plurk這個 Class增加幾個Method基本上只要有 run send reply就夠了而 run用來做初始化的部分

class Plurk entends Adapter

send (plurk id strings⋯) -gt

reply (plurk id strings⋯) -gt

run -gt

看起來有點東西接著來處理主要的 Plurk API結合部分

class PlurkStreaming extends EventEmitter

consuctor (options) -gt

plurk (callback) -gt

觀察河道

getChannel -gt

取得 Comet 網址

reply (plurk id message) -gt

回噗

acceptFriends -gt

接受好友

get (path callback) -gt

GET 請求

post (path body callback)-gt

POST 請求(其實是裝飾)

request (method path body callback)-gt

主要的 OAuth 請求

comet (server callback)-gt

噗浪的 Comet 傳回是 JavaScript Callback 要另外處理後才會變成 JSON

然後把注意力集中到 constructor上先把建構子弄好

constructor (options) -gt

super()

if optionskey and optionssecret and optionstoken and optionstoken secret

key = optionskey

secret = optionssecret

token = optionstoken

token secret = optionstoken secret

83 建立 Robot跟 API 73

Nodejs中文電子書

建立 OAuth 連接

consumer = new oauthOAuth(

rdquohttpwwwplurkcomOAuthrequest tokenrdquo

rdquohttpwwwplurkcomOAuthaccess tokenrdquo

key

secret

rdquo10rdquo

rdquohttpwwwplurkcomOAuthauthorizerdquo

rdquoHMAC-SHA1rdquo

)

domain = rdquowwwplurkcomrdquo

初始化取得 Comet 網址

do getChannel

else

throw new Error(rdquo 參數不足需要 Key Secret Token Token Secretrdquo)

接著來處理 request這個 method

request (method path body callback) -gt

記錄一下這次的 Request

consolelog(rdquohttpdomainpathrdquo)

Callback 這邊先不丟進去要用另一種方式處理

request = consumerget(rdquohttpdomainpathrdquo token token secret null)

requeston rdquoresponserdquo (res) -gt

reson rdquodatardquo (chunk) -gt

parseResponse(chunk+rsquorsquo callback)

reson rdquoendrdquo (data) -gt

consolelog rdquoEnd Request pathrdquo

reson rdquoerrorrdquo (data) -gt

consolelog rdquoError rdquo + data

requestend()

處理資料

parseResponse = (data callback) -gt

if datalength gt 0

用 TryCatch 避免處理 JSON 出錯導致整個中斷

try

callback null JSONparse(data)

catch err

74 8 製作一個 Hubot的 Plurk Adapter

Nodejs中文電子書

consolelog(rdquoError Parse JSONrdquo + data err)

繼續執行

callback null data ||

大致上就是這樣上面程式的架構已經將整個 Hubot Plurk Adapter完成因為在測試時竟然因為噗浪 Lag而沒讀到完整的 Comet資料然後造成程式異常為了避免這個問題發生因此需要加上為了完美呈現需要再

加上 Comet的處理所以要使用到 EventEmitter的功能

comet (server callback) -gt

在 Callback 裡面會找不到自身所以設定區域變數

self =

記錄一下這次的 Request

consolelog(rdquo[Comet] serverrdquo)

Callback 這邊先不丟進去要用另一種方式處理

request = consumerget(rdquohttpdomainpathrdquo token token secret null)

requeston rdquoresponserdquo (res) -gt

reson rdquodatardquo (chunk) -gt

parseResponse(chunk+rsquorsquo callback)

reson rdquoendrdquo (data) -gt

consolelog rdquoEnd Request pathrdquo

請求結束發出事件通知可以進行下一次請求

selfemit rdquonextPlurkrdquo

reson rdquoerrorrdquo (data) -gt

consolelog rdquoError rdquo + data

requestend()

處理資料

parseResponse = (data callback) -gt

if datalength gt 0

用 trycatch 避免失敗中斷

try

去掉 JavaScript 的 Callback

data = datamatch(CometChannelscriptCallback((+))s)

jsonData = rdquordquo

if data

83 建立 Robot跟 API 75

Nodejs中文電子書

jsonData = JSONparse(data[1])

else

如果沒有任何 Match 嘗試直接 parse

jsonData = JSONparse(data)

catch err

consolelog(rdquo[Comet] Errorrdquo data err)

用 TryCatch 避免處理 JSON 出錯導致整個中斷

try

只傳入 json 的 data 部分

callback null jsonDatadata

catch err

consolelog(rdquo[Comet]Error Parse JSONrdquo + data err)

繼續執行

callback null data ||

後面的 get跟 post就簡單多了

get (path callback) -gt

request(rdquoGETrdquo path null callback)

post (path body callback) -gt

request(rdquoPOSTrdquo path body callback)

接著處理取的 Comet網址的 getChannel

getChannel -gt

self =

get rdquoAPPRealtimegetUserChannelrdquo (error data) -gt

if error

檢查是否有 comet server

if datacomet server

selfchannel = datacomet server

如果沒有 Channel Ready 就嘗試連接會失敗

selfemit(rsquochannel readyrsquo)

那麼先來處理 Plurk Adaper好處理的部份

send (plruk id strings⋯)-gt

跟 Reply 一樣直接交給 reply 做

reply plurk id strings⋯

76 8 製作一個 Hubot的 Plurk Adapter

Nodejs中文電子書

reply (plurk id strings⋯) -gt

stringsforEach (message) =gt

botreply(plruk id message)

接著把 run處理好就可以上線運作摟

run -gt

self =

options =

key processenvHUBOT PLURK KEY

secret processenvHUBOT PLURK SECRET

token processenvHUBOT PLURK TOKEN

token secret processenvHUBOT PLURK TOKEN SECRET

創建剛剛的 API

bot = new PlurkStreaming(options)

依照 Twitter 的 new RobotTextMessage 會沒有反應所以參考 hubot-minecraft 的方式

r = robotconstructor

處理噗浪河道訊息

doPlurk = (data)-gt

檢查是否為回噗

if dataresponse

datacontent raw = dataresponsecontent raw

datauser id = dataresponseuser id

確定有噗浪 ID 跟訊息

if dataplurk id and datacontent raw

selfreceive new rTextMessage(dataplurk id datacontent raw)

取得 Comet Server 完成開始第一次 Comet 連接

boton rdquochannel readyrdquo () -gt

botplurk selfdoPlurk

上一次 Comet 完成繼續 Polling

boton rdquonextPlurkrdquo ()-gt

botplurk selfdoPlurk

定時接受好友邀請

do botacceptFriends

bot = bot

83 建立 Robot跟 API 77

Nodejs中文電子書

終於完成 AdapterHubot專案裡面的 scripts資料夾內是互動部分不需要像 Adapter如此大費周章處理只新增檔案並且設計好對白之後就會回噗了我開發用的機器人在此大家可以去跟他玩玩[httpplurkcomelct9620 bot](httpplurkcomelct9620 bot)

84 原始資料提供

bull [製 作 一 個 Hubot 的 噗 浪 Adapter](http revo-skillfrosttw blog20120318create-a-hubot-plurk-adapter)

78 8 製作一個 Hubot的 Plurk Adapter

9

Nodejs好用工具介紹

91 logio

Real-time log monitoring in your browser

httplogioorg

nodejs (gt=0412 lt=0611)

npm config set unsafe-perm true

npm install -g --prefix=usrlocal logio

logio server start

httplocalhost8998

79

Nodejs中文電子書

80 9 Nodejs好用工具介紹

10

精選文章

101 Express

bull 使用 NodeJS + Express從 GETPOST Request取值 [邀稿中]

bull link2 [邀稿中]

bull link3 [邀稿中]

81

Nodejs中文電子書

82 10 精選文章

11

參考資源

111 Nodejs書籍

bull Programming Nodejs April 2012 (est) Travis Swicegood Pragmatic Bookshelf

bull e Little Book on CoffeeScript January 2012 Alex MacCaw OrsquoReilly Media

bull Node for Front-End Developers January 2012 Garann Means OrsquoReilly Media

bull Building Hypermedia APIs with HTML5 and Node November 2011 MikeAmundsen OrsquoReilly Media

bull Node Web Development August 2011 David Herron PacktPub

bull CoffeeScript Accelerated JavaScript Development July 2011 Trevor BurnhamPragmatic Bookshelf

bull Getting Started with GEO CouchDB and Nodejs July 2011 Mick omp-son OrsquoReilly Media

bull What Is Node July 2011 Brett McLaughlin OrsquoReilly Media

bull Node Up and Running May 2011 Tom Hughes-Croucher Mike WilsonOrsquoReilly Media

83

Nodejs中文電子書

bull Hands-on Nodejs e Nodejs introduction and API reference May 2011 Pe-dro Teixeira LeanPub

bull e Node Beginner Book Manuel Kiessling

bull Mastering Nodejs visionmedia

112 Nodejs影音教學

bull e Node Sessions e Best of OSCON 2011 August 2011 OrsquoReilly Media(Video)

bull Tom Hughes-Croucher on Node March 2011 OrsquoReilly Media (Video)

113 Nodejs教學網站

bull Node Tuts

114 Nodejs課程

bull Introduction To Nodejs Van Nguyen CodeLesson

84 11 參考資源

Page 23: Nodejs Wiki

Nodejs中文電子書

25 函數返回函數與 Currying

前面的 cps範例裡面使用了函數返回函數這是為了把 cps callback傳遞給 onreadystatechange事件處理函數的方法(因為這個事件處理函數並沒有設計好會傳送接收這樣的參數)實際會執行的事件處理函數其實是內層返回的那個函數之外包覆的這個函數主要是為了利用 Closure把 next傳給內層的事件處理函數這個方法更常使用的地方是為了解決一些

scope問題例如

ltscriptgt var accu=0count=10 for(var i=0 iltcount i++)

setTimeout(

function() countndash accu+=i if(countlt=0)

consolelog(accu)

50)

ltscriptgt

最後得出的結果會是 100而不是想像中的 45這是因為等到 setTime-out指定的函數執行時變數 i已經變成 10而離開迴圈了要解決這個問題就需要透過 Closure來保存變數 i

ltscriptgt var accu=0count=10 for(var i=0 iltcount i++)

setTimeout(

function(i) return function() countndash

accu+=i if(countlt=0)

consolelog(accu)

(i)

50)

25 函數返回函數與 Currying 17

Nodejs中文電子書

淺藍色底色的部份是跟上面例子不一樣的地方 ltscriptgt

函數返回函數的另外一個用途是可以暫緩函數執行例如

function add(m n) return m+n

var a = add(20 10) consolelog(a)

add這個函數必須同時輸入兩個參數才有辦法執行如果我希望這個函數可以先給它一個參數等一些處理過後再給一個參數然後得到結

果就必須用函數返回函數的方式做修改

function add(m)

return function(n) return m+n

var wait another arg = add(20)先給一個參數 var a = function(arr)

var ret=0 for(var i=0iltarrlengthi++) ret+=arr[i] return ret

([1234])計算一下另一個參數 var b = wait another arg(a)然後再繼續執行 consolelog(b)

像這樣利用函數返回函數使得原本接受多個參數的函數可以一次

接受一個參數直到參數接收完成才執行得到結果的方式有一個學名就

叫做Currying

綜合以上許多奇技淫巧就可以透過用函數來處理函數的方式調整

程式流程接下來看看

26 流程控制

(以 sync方式使用 async函數避開巢狀 callback循序呼叫 async callback等奇技淫巧)

建議參考

bull httphowtonodeorgcontrol- ow

bull httphowtonodeorgcontrol- ow-part-ii

18 2 JavaScript與 NodeJS

Nodejs中文電子書

bull httphowtonodeorgcontrol- ow-part-iii

bull httpblogmixunet20110202essential-node-js-patterns-and-snippets

這幾篇都是非常經典的 NodeJSJavascript流程控制好文章(阿mixu是在介紹一些 pattern時提到這方面的主題)不過我還是用幾個簡單的程式介紹一下做法跟概念

並發與等待

下面的程式參考了 mixu文章中的做法

var wait = function(callbacks done) consolelog(lsquowait startrsquo) var counter = call-backslength var results = [] var next = function(result) 接收函數執行結果並判斷是否結束執行 resultspush(result) if(ndashcounter == 0) done(results)如果結束執行就把所有執行結果傳給指定的 callback處理 for(var i = 0 i lt callbackslength i++) 依次呼叫所有要執行的函數 callbacks[i](next) consolelog(lsquowait endrsquo)

wait( [ function(next) setTimeout(function() consolelog(lsquodone arsquo) var result =500 next(result) 500) function(next) setTimeout(function() con-solelog(lsquodone brsquo) var result = 1000 next(result) 1000) function(next)setTimeout(function() consolelog(lsquodone crsquo) var result = 1500 next(1500) 1500) ] function(results) var ret = 0 i=0 for( iltresultslength i++) ret+= results[i] consolelog(lsquodone all result lsquo+ret)

)

執行結果wait start wait end done a done b done c done all result 3000

可以看出來其實 wait並不是真的等到所有函數執行完才結束執行而是在所有傳給他的函數執行完畢後(不論同步非同步)才執行處理結

果的函數(也就是 done())

不過這樣的寫法還不夠實用因為沒辦法實際讓函數可以等待執行

完畢又能當作事件處理函數來實際使用上面參考到的 Tim Caswell的文

26 流程控制 19

Nodejs中文電子書

章裡面有一種解法不過還需要額外包裝(在他的例子中)NodeJS核心的 fs物件把一些函數(例如 readFile)用 Currying處理類似像這樣

var fs = require(lsquofsrsquo) var readFile = function(path)

return function(callback errback)

fsreadFile(path function(err data)

if(err) errback()

else callback(data)

)

其他部份可以參考 Tim Caswell的文章他的 Doparallel跟上面的 wait差不多意思這裡只提示一下他沒說到的地方

另外一種做法是去修飾一下 callback當他作為事件處理函數執行後再用 cps的方式取得結果

ltscriptgt function Wait(fns done)

var count = 0 var results = [] thisgetCallback = function(index)

count++ return (function(waitback)

return function() var i=0args=[]for(iltargumentslengthi++)

argspush(arguments[i])

argspush(waitback) fns[index]apply(thisargs)

)(function(result) resultspush(result) if(ndashcount == 0)

20 2 JavaScript與 NodeJS

Nodejs中文電子書

done(results)

)

var a = new Wait(

[ function(waitback) consolelog(lsquodone arsquo) var result = 500 wait-back(result) function(waitback) consolelog(lsquodone brsquo) var result =1000 waitback(result) function(waitback) consolelog(lsquodone crsquo) varresult = 1500 waitback(result) ] function(results) var ret = 0 i=0for( iltresultslength i++) ret += results[i] consolelog(lsquodone allresult lsquo+ret)

) var callbacks = [agetCallback(0)agetCallback(1)agetCallback(0)agetCallback(2)]一次取出要使用的 callbacks避免結果提早送出 setTimeout(callbacks[0]500) setTimeout(callbacks[1] 1000) setTimeout(callbacks[2] 1500) setTime-out(callbacks[3] 2000) 當所有取出的 callbacks執行完畢就呼叫 done()來處理結果 ltscriptgt

執行結果

done a done b done a done c done all result 3500

上面只是一些小實驗更成熟的作品是 Tim Caswell 的 stephttpsgithubcomcreationixstep

如果希望真正使用同步的方式寫非同步則需要使用 Promisejs這一類的 library來轉換非同步函數不過他結構比較複雜 XD(見仁見智不過有些人認為 Promise有點過頭了)httpblogsmsdncombrbucktonarchive20110815promise-js-2-0-promise-framework-for-javascriptaspx

如果想不透過其他 Library做轉換又能直接用同步方式執行非同步函數大概就要使用一些需要額外 compile原始程式碼的方法了例如 BrunoJouhier的 streamlinejshttpsgithubcomSagestreamlinejs

26 流程控制 21

Nodejs中文電子書

循序執行

循序執行可以協助把非常深的巢狀 callback結構攤平例如用這樣的簡單模組來做(serialjs)

moduleexports = function(funs) var c = 0 if(isArrayOfFunctions(funs))

throw(lsquoArgument type was not matched Should be array of func-tionsrsquo)

return function()

var args = Arrayprototypeslicecall(arguments 0) if((cgt=funslength))

c++ return funs[c-1]apply(this args)

function isArrayOfFunctions(f ) if(typeof f == lsquoobjectrsquo) return false if(flength)return false if(fconcat) return false if(fsplice) return false var i = 0 for(iltflength i++)

if(typeof f[i] == lsquofunctionrsquo) return false

return true

簡單的測試範例(testSerialjs)使用 fs 模組確定某個 path 是檔案然後讀取印出檔案內容這樣會用到兩層的 callback所以測試中有使用serial的版本與 nested callbacks的版本做對照

var serial = require(lsquoserialrsquo) fs = require(lsquofsrsquo) path = lsquodclientjsrsquo cb = serial([ func-tion(err data)

if(err)

if(dataisFile) fsreadFile(path cb)

22 2 JavaScript與 NodeJS

Nodejs中文電子書

else consolelog(err)

function(err data)

if(err) consolelog(lsquo[ attened by searial]rsquo) con-solelog(datatoString(lsquoutf8rsquo))

else consolelog(err)

]) fsstat(path cb)

fsstat(path function(err data) 第一層 callback if(err)

if(dataisFile)

fsreadFile(path function(err data) 第二層 callback if(err)

consolelog(lsquo[nested callbacks]rsquo) con-solelog(datatoString(lsquoutf8rsquo))

else consolelog(err)

)

else consolelog(err)

)

關鍵在於這些 callback的執行是有順序性的所以利用 serial返回的一個函數 cb來取代這些 callback然後在 cb中控制每次會循序呼叫的函數

26 流程控制 23

Nodejs中文電子書

就可以把巢狀的 callback攤平成循序的 function陣列(就是傳給 serial函數的參數)

測試中的dclientjs 是一個簡單的 dnode 測試程式放在跟 testSerialjs同一個目錄

var dnode = require(lsquodnodersquo)

dnodeconnect(8000 lsquolocalhostrsquo function(remote)

remoterestart(function(str) consolelog(str) processexit()

)

)

執行測試程式後出現結果

[ attened by searial] var dnode = require(lsquodnodersquo)

dnodeconnect(8000 lsquolocalhostrsquo function(remote)

remoterestart(function(str) consolelog(str) processexit()

)

)

[nested callbacks] var dnode = require(lsquodnodersquo)

dnodeconnect(8000 lsquolocalhostrsquo function(remote)

remoterestart(function(str) consolelog(str) processexit()

)

)

對照起來看兩種寫法的結果其實是一樣的但是利用 serialjs巢狀的 callback結構就會消失

不過這樣也只限於順序單純的狀況如果函數執行的順序比較複雜

(不只是一直線)還是需要用功能更完整的流程控制模組比較好例如

httpsgithubcomcaolanasync

24 2 JavaScript與 NodeJS

3

Nodejs安裝與設定

本篇將講解如何在各個不同 OS建立 NodeJS環境目前 NodeJS 048版本環境架設方式需依賴 Linux指令才可編譯完成當然在不同作業系統中也已經有 NodeJS package可以直接使用指令快速架設以下各不同作業系統解說如何安裝 NodeJS

31 Ubuntu Linux

更新推薦使用 nvm

git clone gitgithubcomcreationixnvmgit ~nvm

echo rdquo ~nvmnvmshrdquo gtgt ~bashrc

nvm install v0614

nvm alias default v0614

以 上 可 參 考 http dreamerslabcom blog tw how-to-setup-a-node-js-development-environment-on-ubuntu-11-04

使用 APT套件管理工具是常見的方法以下是使用社群提供的 PPA安裝方式

sudo apt-get install python-software-properties

sudo add-apt-repository ppachris-leanodejs-devel

25

Nodejs中文電子書

sudo apt-get update

sudo apt-get install nodejs

32 Other Linux

Linux很適合作為 NodeJS的伺服器作業系統及開發環境安裝前請先確認以下套件已正確安裝

bull curl (wget)用來下載檔案的工具

bull git先進的版本控制工具

bull g++ GNU C++軟體編譯工具

bull make GNU軟體專案建置工具

安裝指令如下如設有權限問題請在指令前面加上 sudo

git clone httpsgithubcomjoyentnodegit

cd node

git checkout v067

configure

make

sudo make install

接著測試 nodeJS是否正常執行

node --version

出現版本訊息即表示安裝成功

33 Windows

nodeJS 在 v060 版本之後開始正式支援 windows native直接使用nodeexe 就可以執行程式支援性完全與 linux 相同更棒的部份就是不需經過編譯經過下載之後簡單設定完成立即開發 node程式

26 3 Nodejs安裝與設定

Nodejs中文電子書

下載 nodejs安裝檔案 lthttpnodejsorgdownloadgt

如此完成 windows native nodeexe安裝接著可以進入 command line執行測試在 command line輸入rdquonoderdquo會出現 nodejs版本訊息畫面表示安裝完成

33 Windows 27

Nodejs中文電子書

28 3 Nodejs安裝與設定

4

Nodejs基礎

前篇文章已經由介紹安裝至設定都有完整介紹nodeJS 內部除了javascript常用的函式 (function)物件 (object)之外也有許多不同的自訂物件nodeJS預設建立這些物件為核心物件是為了要讓開發流程更為這些資料在官方文件已經具有許多具體說明接下來將會介紹在開發 nodeJS程式時常見的物件特性與使用方法

41 nodejs http伺服器建立

在 lsquonodejs官方網站 lthttpnodejsorggtlsquo裡面有舉一個最簡單的 HTTP伺服器建立一開始初步就是建立一個伺服器平台讓 nodejs可以與瀏覽器互相行為每種語言一開始的程式建立都是以 Hello world開始最初也從 Hello world帶各位進入 nodejs的世界

輸入以下程式碼儲存檔案為 node basic http hello worldjs

var server

ip = rdquo127001rdquo

port = 1337

http = require(rsquohttprsquo)

server = httpcreateServer(function (req res)

29

Nodejs中文電子書

reswriteHead(200 rsquoContent-Typersquo rsquotextplainrsquo)

resend(rsquoHello Worldnrsquo)

)

serverlisten(port ip)

consolelog(rdquoServer running at httprdquo + ip + rdquordquo + port)

程式碼解講一開始需要有幾個基本的變數

bull ip 機器本身的 ip位置因為使用本地端因此設定為 127001

bull port 需要開通的阜號通常設定為 http port 80因範例不希望與基本 port相衝隨意設定為 1337

在 nodejs 的程式中有許多預設的模組可以使用因此需要使用require方法將模組引入在這邊我們需要使用 http這個模組因此將 http載入Http模組裡面內建有許多方法可以使用這邊採用 createServer創建一個基本的 http伺服器再將 http伺服器給予一個 server變數裡面的回呼函式 (call back function)可以載入 http伺服器的資料與回應方法 (requestresponse)在程式裡面就可以看到我們直接回應給瀏覽器端所需的 Header回應內容

reswriteHead(200 rsquoContent-Typersquo rsquotextplainrsquo)

resend(rsquoHello Worldnrsquo)

Http伺服器需要設定 port ip在最後需要設定 Http監聽需要使用到listen事件監聽所有 Http伺服器行為

httplisten(port ip)

所有事情都完成之後需要確認伺服器正確執行因此使用 console在javascript裡就有這個原生物件console所印出的資料都會顯示於 nodejs伺服器頁面這邊印出的資料並不會傳送到使用者頁面上之後許多除壞

(debug)都會用到 console物件

consolelog(rdquoServer running at httprdquo + ip + rdquordquo + port)

30 4 Nodejs基礎

Nodejs中文電子書

42 nodejs http路徑建立

前面已經介紹如何建立一個簡單的 http伺服器接下來本章節將會介紹如何處理伺服器路徑 (rout)問題在 http的協定下所有從瀏覽器發出的要求 (request)都需要經過處理路徑上的建立也是如此

路徑就是指伺服器 ip位置或者是網域名稱之後對於伺服器給予的要求修改剛才的 hello world檔案修改如下

server = httpcreateServer(function (req res)

consolelog(requrl)

reswriteHead(200 rsquoContent-Typersquo rsquotextplainrsquo)

resend(rsquohello worldnrsquo)

)

重新啟動 nodejs程式後在瀏覽器端測試一下路徑行為結果如下圖

當在瀏覽器輸入 http1270011337test 在伺服器端會收到兩個要求一個是我們輸入的test要求另外一個則是faviconicotest的路徑要求http伺服器本身需要經過程式設定才有辦法回應給瀏覽器端所需要的回應在伺服器中所有的路徑要求都是需要被解析才有辦法取得資料從

42 nodejs http路徑建立 31

Nodejs中文電子書

上面解說可以了解到在 nodejs當中所有的路徑都需要經過設定未經過設定的路由會讓瀏覽器無法取得任何資料導致錯誤頁面的發生底下將會解

說如何設定路由同時避免發生錯誤情形先前 nodejs程式需要增加一些修改才能讓使用者透過瀏覽器在不同路徑時有不同的結果根據剛才

的程式做如下的修改

var server

ip = rdquo127001rdquo

port = 1337

http = require(rsquohttprsquo)

url = require(rsquourlrsquo)

path

server = httpcreateServer(function (req res)

path = urlparse(requrl)

reswriteHead(200 rsquoContent-Typersquo rsquotextplainrsquo)

switch (pathpathname)

case rdquoindexrdquo

resend(rsquoI am indexnrsquo)

break

case rdquotestrdquo

resend(rsquothis is test pagenrsquo)

break

default

resend(rsquodefault pagenrsquo)

break

)

serverlisten(port ip)

consolelog(rdquoServer running at httprdquo + ip + rdquordquo + port)

程式做了片段的修改首先載入 url模組另外增加一個 path變數url模組就跟如同他的命名一般專門處理 url字串處理裡面提供了許多方法來解決路徑上的問題因為從瀏覽器發出的要求路徑可能會帶有多種需求

或者 GET參數組合等因此我們需要將路徑單純化取用路徑部分的資料即可例如使用者可能會送出 http1270011337testsend=1如果直接

32 4 Nodejs基礎

Nodejs中文電子書

信任 requrl就會收到結果為testsend=1所以需要透過 url模組的方法將路徑資料過濾

在這邊使用 urlparse的方法裡面帶入網址格式資料會回傳路徑資料為了後需方便使用將回傳的資料設定到 path變數當中在回傳的路徑資料裡面包含資訊如下圖

這邊只需要使用單純的路徑要求直接取用 pathpathname就可以達到我們的目的

最後要做路徑的判別在不同的路徑可以指定不同的輸出在範例中

有三個可能結果第一個從瀏覽器輸入index就會顯示 index結果test 就會呈現出 test頁面最後如果都不符合預期的輸入會直接顯示 default的頁面最後的預防可以讓瀏覽器不會出現非預期結果讓程式的可靠性提昇

底下為測試結果

42 nodejs http路徑建立 33

Nodejs中文電子書

43 nodejs檔案讀取

前面已經介紹如何使用路由(rount)做出不同的回應實際應用只有在瀏覽器只有輸出幾個文字資料總是不夠的在本章節中將介紹如何使

用檔案讀取輸出檔案資料讓使用者在前端瀏覽器也可以讀取到完整的

html css javascript檔案輸出

檔案管理最重要的部分就是 lsquoFile system lthttpnodejsorgdocslatestapifshtmlgtlsquo這個模組此模組可以針對檔案做管理監控讀取等行為裡面有許多預設的方法底下是檔案輸出的基本範例底下會有兩個檔案

第一個是靜態 html檔案

ltDOCTYPE htmlgt

lthtml xmlns=rdquohttpwwww3org1999xhtmlrdquo xmllang=rdquozh-CNrdquo lang=rdquozh-CNrdquogt

ltheadgt

ltmeta http-equiv=rdquoContent-Typerdquo content=rdquotexthtml charset=UTF-7rdquo gt

lttitlegtnodejs index html filelttitlegt

ltheadgt

ltbodygt

34 4 Nodejs基礎

Nodejs中文電子書

lth1gtnodejs index html filelth1gt

ltbodygt

lthtmlgt

另一個為 nodejs程式

var fs = require(rdquofsrdquo)

filename = rdquostaticindexhtmlrdquo

encode = rdquoutf8rdquo

fsreadFile(filename encode function(err file)

consolelog(file)

)

一開始直接載入 le system模組 載入名稱為 fs讀取檔案主要使

用的方法為 readFile裡面以三個參數路徑 ( le path) 編碼方式 (encoding)

回應函式 (callback)路徑必須要設定為靜態 html所在位置才能指定到正確的檔案靜態檔案的編碼方式也必須正確這邊使用靜態檔案的編

碼為 utf8如果編碼設定錯誤nodejs讀取出來檔案結果會使用 byte raw格式輸出如果錯誤編碼格式會導致輸出資料為 byte raw

回應函式中裡面會使用兩個變數error為錯誤資訊如果讀取的檔案不存在或者發生錯誤error數值會是 true如果成功讀取資料 error將會是 falsecontent則是檔案內容資料讀取後將會把資料全數丟到 content這個變數當中

最後程式的輸出結果畫面如下

43 nodejs檔案讀取 35

Nodejs中文電子書

44 nodejs http靜態檔案輸出

前面已經了解如何讀取本地端檔案接下來將配合 http伺服器路由讓每個路由都能夠輸出相對應的靜態 html檔案首先新增加幾個靜態 html檔案

ltDOCTYPE htmlgt

lthtml xmlns=rdquohttpwwww3org1999xhtmlrdquo xmllang=rdquozh-CNrdquo lang=rdquozh-CNrdquogt

ltheadgt

ltmeta http-equiv=rdquoContent-Typerdquo content=rdquotexthtml charset=UTF-7rdquo gt

lttitlegtnodejs index html filelttitlegt

ltheadgt

ltbodygt

lth1gtnodejs index html filelth1gt

ltbodygt

lthtmlgt

ltDOCTYPE htmlgt

lthtml xmlns=rdquohttpwwww3org1999xhtmlrdquo xmllang=rdquozh-CNrdquo lang=rdquozh-CNrdquogt

ltheadgt

ltmeta http-equiv=rdquoContent-Typerdquo content=rdquotexthtml charset=UTF-7rdquo gt

lttitlegtnodejs test html filelttitlegt

ltheadgt

ltbodygt

lth1gtnodejs test html filelth1gt

ltbodygt

lthtmlgt

ltDOCTYPE htmlgt

lthtml xmlns=rdquohttpwwww3org1999xhtmlrdquo xmllang=rdquozh-CNrdquo lang=rdquozh-CNrdquogt

ltheadgt

ltmeta http-equiv=rdquoContent-Typerdquo content=rdquotexthtml charset=UTF-7rdquo gt

lttitlegtnodejs static html filelttitlegt

ltheadgt

ltbodygt

lth1gtnodejs static html filelth1gt

ltbodygt

lthtmlgt

36 4 Nodejs基礎

Nodejs中文電子書

準備一個包含基本路由功能的 http伺服器

var server

ip = rdquo127001rdquo

port = 1337

http = require(rsquohttprsquo)

url = require(rsquourlrsquo)

server = httpcreateServer(function (req res)

var path = urlparse(requrl)

)

serverlisten(port ip)

consolelog(rdquoServer running at httprdquo + ip + rdquordquo + port)

加入 le system 模組使用 readFile 的功能將這一段程式放置於

createServer的回應函式中

fsreadFile(filePath encode function(err file)

)

readFile的回應函式裡面加入頁面輸出讓瀏覽器可以正確讀到檔案在這邊我們設定讀取的檔案為 html 靜態檔案所以 Content-type 設定為texthtml讀取到檔案的內容將會正確輸出成 html靜態檔案

fsreadFile(filePath encode function(err file)

reswriteHead(200 rsquoContent-Typersquo rsquotexthtmlrsquo)

reswrite(file)

resend()

)

到這邊為止基本的程式內容都已經完成剩下一些細節的調整首先

路徑上必須做調整目前的靜態檔案全部都放置於 static資料夾底下設定一個變數來記住資料夾位置

接著將瀏覽器發出要求路徑與資料夾組合讀取正確 html靜態檔案使用者有可能會輸入錯誤路徑所以在讀取檔案的時候要加入錯誤處理

同時回應 404伺服器無法正確回應的 http header格式

加入這些細節的修改一個基本的 http 靜態 html輸出伺服器就完成了完整程式碼如下

44 nodejs http靜態檔案輸出 37

Nodejs中文電子書

var server

ip = rdquo127001rdquo

port = 1337

http = require(rsquohttprsquo)

fs = require(rdquofsrdquo)

folderPath = rdquostaticrdquo

url = require(rsquourlrsquo)

path

filePath

encode = rdquoutf8rdquo

server = httpcreateServer(function (req res)

path = urlparse(requrl)

filePath = folderPath + pathpathname

fsreadFile(filePath encode function(err file)

if (err)

reswriteHead(404 rsquoContent-Typersquo rsquotextplainrsquo)

resend()

return

reswriteHead(200 rsquoContent-Typersquo rsquotextapplicationrsquo)

reswrite(file)

resend()

)

)

serverlisten(port ip)

consolelog(rdquoServer running at httprdquo + ip + rdquordquo + port)

45 nodejs http GET資料擷取

http伺服器中除了路由之外另一個最常使用的方法就是擷取 GET資料本單元將會介紹如何透過基本 http伺服器擷取瀏覽器傳來的要求擷取 GET資料

在 http協定中GET參數都是藉由 URL從瀏覽器發出要求送至伺服器

38 4 Nodejs基礎

Nodejs中文電子書

端基本的傳送網址格式可能如下

http127001testsend=1amptest=2

上面這段網址裡面的 GET參數就是 send而這個變數的數值就為 1如果想要在 http伺服器取得 GET資料需要在瀏覽器給予的要求 (request)做處理

首先需要載入 query string這個模組這個模組主要是用來將字串資料

過濾後轉換成javascript物件

qs = require(rsquoquerystringrsquo)

接著在第一階段利用 url模組過濾瀏覽器發出的 URL資料後將回應的物件裡面的 query這個變數是一個字串值資料過濾後如下

send=1amptest=2

透過 query string使用 parse這個方法將資料轉換成 javascript物件就表示 GET的資料已經被伺服器端正式擷取下來

path = urlparse(requrl)

parameter = qsparse(pathquery)

整個 nodejs http GET參數完整擷取程式碼如下

var server

ip = rdquo127001rdquo

port = 1337

http = require(rsquohttprsquo)

qs = require(rsquoquerystringrsquo)

url = require(rsquourlrsquo)

server = httpcreateServer(function (req res)

var path = urlparse(requrl)

parameter = qsparse(pathquery)

consoledir(parameter)

reswriteHead(200 rsquoContent-Typersquo rsquotextplainrsquo)

reswrite(rsquoBrowser test GET parameternrsquo)

resend()

)

45 nodejs http GET資料擷取 39

Nodejs中文電子書

serverlisten(port ip)

consolelog(rdquoServer running at httprdquo + ip + rdquordquo + port)

程式運作之後由瀏覽器輸入要求網址之後nodejs伺服器端回應資料為

Server running at http1270011337

send rsquo1rsquo test rsquo1rsquo

46 本章結語

前面所解說的部份一大部分主要是處理 http伺服器基本問題雖然在某些部分有牽扯到 http 伺服器基本運作原理主要還是希望可以藉由這些基本範例練習 nodejs練習回應函式與語法串接的特點習慣編寫javascript風格程式當然直接這樣開發 nodejs是非常辛苦的接下來在模組實戰開發的部份將會介紹特定的模組一步一步帶領各位從無到有進行

nodejs應用程式開發

40 4 Nodejs基礎

5

NPM套件管理工具

npm全名為 Node Package Manager是 Nodejs的套件(package)管理工具類似 Perl的 ppm或 PHP的 PEAR等安裝 npm後使用npm install

module name指令即可安裝新套件維護管理套件的工作會更加輕鬆

npm可以讓Nodejs的開發者直接利用擴充線上的套件庫(packagesregistry)加速軟體專案的開發npm提供很友善的搜尋功能可以快速找到安裝需要的套件當這些套件發行新版本時npm也可以協助開發者自動更新這些套件

npm不僅可用於安裝新的套件它也支援搜尋列出已安裝模組及更新的功能

51 安裝 NPM

Nodejs在 063版本開始內建 npm讀者安裝的版本若是此版本或更新的版本就可以略過以下安裝說明

若要檢查 npm是否正確安裝可以使用以下的指令

npm -v

41

Nodejs中文電子書

執行結果說明

若 npm正確安裝執行 npm -v將會看到類似 110-2的版本訊息

若讀者安裝的 Nodejs版本比較舊或是有興趣嘗試自己動手安裝 npm工具則可以參考以下的說明

安裝於Windows系統

Nodejs for Windows 於 062 版開始內建 npm使用 nodejsorg 官方提供的安裝程式不需要進一步的設定就可以立即使用 npm指令對於Windows的開發者來說大幅降低環境設定的問題與門檻

除了使用 Nodejs內建的 npm讀者也可以從 npm官方提供的以下網址

httpnpmjsorgdist

這是由 npm提供的 Fancy Windows Install版本請下載壓縮檔(例如npm-110-3zip)並將壓縮檔內容解壓縮至 Nodejs的安裝路徑(例如CProgram Filesnodejs)

解壓縮後在 Nodejs的安裝路徑下應該有以下的檔案及資料夾

bull npmcmd(檔案)

bull node modules(資料夾)

安裝於 Linux系統

Ubuntu Linux的使用者可以加入 NPM Unoffcial PPA這個 repository即可使用 apt-get完成 npm安裝

42 5 NPM套件管理工具

Nodejs中文電子書

Ubuntu Linux使用 apt-get安裝 npm

sudo apt-get install python-software-properties

sudo add-apt-repository ppagias-kay-leenpm

sudo apt-get update

sudo apt-get install npm

npm官方提供的安裝程式 installsh可以適用於大多數的 Linux系統使用這個安裝程式請先確認

1 系統已安裝 curl工具(請使用 curl --version查看版本訊息)

2 已安裝 Nodejs並且 PATH正確設置

3 Nodejs的版本必須大於 04x

以下為 npm提供的安裝指令

curl httpnpmjsorginstallsh | sh

安裝成功會看到如下訊息

installsh安裝成功的訊息

npm10105 homeUSERNAMElocalnodelibnode_modulesnpm

It worked

安裝於Mac OS X

建議採用與 Nodejs相同的方式進行 npm的安裝例如使用MacPorts安裝 Nodejs就同樣使用MacPorts安裝 npm這樣對日後的維護才會更方便容易

使用 MacPorts安裝 npm是本書比較建議的方式它可以讓 npm的安裝移除及更新工作自動化將會幫助開發者節省寶貴時間

51 安裝 NPM 43

Nodejs中文電子書

安裝MacPorts的提示

在MacPorts網站可以取得 OS X系統版本對應的安裝程式(例如 106或 107)httpwwwmacportsorg安裝過程會詢問系統管理者密碼使用預設的選項完成安裝即可安

裝MacPorts之後在終端機執行 port -v將會看到MacPorts的版本訊息

安裝 npm之前先更新MacPorts的套件清單以確保安裝的 npm是最新版本

sudo port -d selfupdate

接著安裝 npm

sudo port install npm

若讀者的 Nodejs並非使用MacPorts安裝則不建議使用MacPorts安裝npm因為 MacPorts會自動檢查並安裝相依套件而 npm相依 nodejs所以MacPorts也會一併將 nodejs套件安裝造成先前讀者使用其它方式安裝的 nodejs被覆蓋

讀者可以先使用 MacPorts安裝 curl(sudo port install curl)

再參考 Linux的 installsh安裝方式即可使用 npm官方提供的安裝程式

NPM安裝後測試

npm是指令列工具(command-line tool)使用時請先打開系統的文字終端機工具

測試 npm安裝與設定是否正確請輸入指令如下

npm -v

或是

npm --version

如果 npm已經正確安裝設定就會顯示版本訊息

44 5 NPM套件管理工具

Nodejs中文電子書

執行結果(範例)

110-2

52 使用 NPM安裝套件

npm目前擁有超過 6000種套件(packages)可以在 npm registry使用關鍵字搜尋套件

httpsearchnpmjsorg

舉例來說在關鍵字欄位輸入「coffee-script」下方的清單就會自動列出包含 coffee-script關鍵字的套件

接著我們回到終端機模式的操作npm的指令工具本身就可以完成套

件搜尋的任務

例如以下的指令同樣可以找出 coffee-script相關套件

npm search coffee-script

以下是搜尋結果的參考畫面

52 使用 NPM安裝套件 45

Nodejs中文電子書

找到需要的套件後(例如 express)即可使用以下指令安裝

npm install coffee-script

值得注意的一點是使用 npm install會將指定的套件安裝在工

作目錄(Working Directory)的 node modules資料夾下

以Windows為例如果執行 npm install的目錄位於

Cproject1

那麼 npm將會自動建立一個 node modules的子目錄(如果不存在)

Cproject1node modules

並且將下載的套件放置於這個子目錄例如

Cproject1node modulescoffee-script

這個設計讓專案可以個別管理相依的套件並且可以在專案佈署或發

行時將這些套件(位於 node modules)一併打包方便其它專案的使用者不必再重新下載套件

這個 npm install的預設安裝模式為 local(本地)只會變更當前專案的資料夾不會影響系統

另一種安裝模式稱為 global(全域)這種模式會將套件安裝到系統資

料夾也就是 npm安裝路徑的 node modules資料夾例如

CProgram Filesnodejsnode modules

46 5 NPM套件管理工具

Nodejs中文電子書

是否要使用全域安裝可以依照套件是否提供新指令來判斷舉例來說express 套件提供 express 這個指令而 coffee-script 則提供 coffee

指令

在 local 安 裝 模 式 中這 些 指 令 的 程 式 檔 案會 被 安 裝 到node modules的 bin這個隱藏資料夾下除非將bin的路徑加入 PATH環境變數否則要執行這些指令將會相當不便

為了方便指令的執行我們可以在 npm install 加上 -g 或 --

global參數啟用 global安裝模式例如

npm install -g coffee-script

npm install -g express

使用 global安裝模式需要注意執行權限的問題若權限不足可能會出現類似以下的錯誤訊息

npm ERR Error EACCES permission denied rsquorsquo

npm ERR

npm ERR Please try running this command again as rootAdministrator

要獲得足夠得執行權限請參考以下說明

bull Windows 7 或 2008 以上在「命令提示字元」的捷徑按右鍵選擇「以系統管理員身分執行」執行 npm指令時就會具有 Administrator身分

bull Mac OS X或 Linux系統可以使用 sudo指令例如

sudo npm install -g express

bull Linux系統可以使用 root權限登入或是以「sudo su -」切換成 root身分(使用 root權限操作系統相當危險因此並不建議使用這種方式)

若加上 -g參數使用 npm install -g coffee-script完成安裝

後就可以在終端機執行 coffee指令例如

coffee -v

52 使用 NPM安裝套件 47

Nodejs中文電子書

執行結果(範例)

CoffeeScript version 120

53 套件的更新及維護

除了前一節說明的 search 及 install 用法npm 還提供其他許多指令(commands)

使用 npm help可以查詢可用的指令

npm help

執行結果(部分)

where ltcommandgt is one of

adduser apihelp author bin bugs c cache completion

config deprecate docs edit explore faq find get

help help-search home i info init install la link

list ll ln login ls outdated owner pack prefix

prune publish r rb rebuild remove restart rm root

run-script s se search set show star start stop

submodule tag test un uninstall unlink unpublish

unstar up update version view whoami

使用 npm help command可以查詢指令的詳細用法例如

npm help list

接下來本節要介紹開發過程常用的 npm指令

使用 list可以列出已安裝套件

npm list

48 5 NPM套件管理工具

Nodejs中文電子書

執行結果(範例)

-- coffee-script120

- express256

- connect185

| -- formidable108

-- mime124

-- mkdirp007

-- qs041

檢視某個套件的詳細資訊例如

npm show express

升級所有套件(如果該套件已發佈更新版本)

npm update

升級指定的套件

npm update express

移除指定的套件

npm uninstall express

54 使用 packagejson

對於正式的 Nodejs專案可以建立一個命名為 packagejson的設

定檔(純文字格式)檔案內容參考範例如下

54 使用 packagejson 49

Nodejs中文電子書

packagejson(範例)

rdquonamerdquo rdquoapplication-namerdquo

rdquoversionrdquo rdquo001rdquo

rdquoprivaterdquo true

rdquodependenciesrdquo

rdquoexpressrdquo rdquo255rdquo

rdquocoffee-scriptrdquo rdquolatestrdquo

rdquomongooserdquo rdquogt= 253rdquo

其中 name與 version依照專案的需求設置

需要注意的是 dependencies的設定它用於指定專案相依的套件名

稱及版本

bull rdquoexpressrdquo rdquo255rdquo

代表此專案相依版本 255的 express套件

bull rdquocoffee-scriptrdquo rdquolatestrdquo

使用最新版的 coffee-script套件(每次更新都會檢查新版)

bull rdquomongooserdquo rdquogt= 253rdquo

使用版本大於 253的 mongoose套件

假設某個套件的新版可能造成專案無法正常運作就必須指定套件的

版本避免專案的程式碼來不及更新以相容新版套件通常在開發初期的

專案需要盡可能維持新套件的相容性(以取得套件的更新或修正)可以

用「gt=」設定最低相容的版本或是使用「latest」設定永遠保持最新

套件

50 5 NPM套件管理工具

6

Express介紹

在前面的 nodejs基礎當中介紹許多許多開設 http的使用方法及介紹以及許多基本的 nodejs基本應用

接下來要介紹一個套件稱為 express [Express](httpexpressjscom)這個套件主要幫忙解決許多 nodejs http server所需要的基本服務讓開發 httpservice變得更為容易不需要像之前需要透過層層模組(module)才有辦法開始編寫自己的程式

這個套件是由 TJ Holowaychuk 製作而成的套件裡面包含基本的路由處理 (route)http資料處理(GETPOSTPUT)另外還與樣板套件(jshtml template engine)搭配同時也可以處理許多複雜化的問題

61 Express安裝

安裝方式十分簡單只要透過之前介紹的 NPM就可以使用簡單的指令安裝指令如下

這邊建議需要將此套件安裝成為全域模組方便日後使用

51

Nodejs中文電子書

62 Express基本操作

express的使用也十分簡單先來建立一個基本的 hello world

var app = require(rsquoexpressrsquo)createServer()

port = 1337

applisten(port)

appget(rsquorsquo function(req res)

ressend(rsquohello worldrsquo)

)

consolelog(rsquostart express servernrsquo)

可以從上面的程式碼發現基本操作與 nodejs http的建立方式沒有太大差異主要差在當我們設定路由時可以直接透過 appget方式設定回應與接受方式

63 Express路由處理

Express對於 http服務上有許多包裝讓開發者使用及設定上更為方便例如有幾個路由設定那我們就統一藉由 appget來處理

Create http server

appget(rsquorsquo function(req res)

ressend(rsquohello worldrsquo)

)

appget(rsquotestrsquo function(req res)

ressend(rsquotest renderrsquo)

)

appget(rsquouserrsquo function(req res)

ressend(rsquouser pagersquo)

)

52 6 Express介紹

Nodejs中文電子書

如上面的程式碼所表示appget可以帶入兩個參數第一個是路徑名稱設定第二個為回應函式 (call back function)回應函式裡面就如同之前的 createServer方法裡面包含 requestresponse兩個物件可供使用使用者就可以透過瀏覽器輸入不同的 url切換到不同的頁面顯示不同的結果

路由設定上也有基本的配對方式讓使用者從瀏覽器輸入的網址可以

是一個變數只要符合型態就可以有對應的頁面產出例如

Create http server

appget(rsquouseridrsquo function(req res)

ressend(rsquouser rsquo + reqparamsid)

)

appget(rsquonumberrsquo function(req res)

ressend(rsquonumber rsquo + reqparamsnumber)

)

裡 面 使 用 到number 從 網 址 輸 入 之 後 就 可 以 直 接 使 用reqparamsnumber 取得所輸入的資料變成 url 參數使用當然前面也是可以加上路徑的設定userid在瀏覽器上路徑必須符合userxxx透

過 reqparamsid就可以取到 xxx這個字串值

另外express參數處理也提供了路由參數配對處理也可以透過正規表示法作為參數設定

var app = require(rsquoexpressrsquo)createServer()

port = 1337

applisten(port)

appget(^ip((d23)((d23))((d23))((d23))) function(req res)

ressend(reqparams)

)

上面程式碼可以發現後面路由設定的型態是正規表示法裡面設定

格式為ip之後必須要加上 ip型態才會符合資料格式同時取得 ip資料已經由正規表示法將資料做分群因此可以取得 ip的四個數字

此程式執行之後可以透過瀏覽器測試輸入網址為 localhost3000ip25525510010可以從頁面獲得資料

63 Express路由處理 53

Nodejs中文電子書

此章節全部範例程式碼如下

overview

author Caesar Chi

blog clonnblogspotcom

version 20120226

create server

var app = require(rsquoexpressrsquo)createServer()

port = 1337

applisten(port)

normal style

appget(rsquorsquo function(req res)

ressend(rsquohello worldrsquo)

)

appget(rsquotestrsquo function(req res)

ressend(rsquotest renderrsquo)

)

parameter style

appget(rsquouseridrsquo function(req res)

ressend(rsquouser rsquo + reqparamsid)

)

appget(rsquonumberrsquo function(req res)

ressend(rsquonumber rsquo + reqparamsnumber)

)

REGX style

appget(^ip((d23)((d23))((d23))) function(req res)

ressend(reqparams)

)

appget(rsquorsquo function(req res)

ressend(rsquoPage not foundrsquo 404)

54 6 Express介紹

Nodejs中文電子書

)

consolelog(rsquostart express servernrsquo)

64 Express middleware

Express 裡面有一個十分好用的應用概念稱為 middleware可以透過middleware做出複雜的效果同時上面也有介紹 next方法參數傳遞就是靠 middleware的概念來傳遞參數讓開發者可以明確的控制程式邏輯

create http server

appuse(expressbodyParser())

appuse(expressmethodOverride())

appuse(expresssession())

上面都是一種 middleware的使用方式透過 appuse方式裡面載入函式執行方法回應函式會包含三個基本參數responserequestnext其中next表示下一個 middleware執行函式同時會自動將預設三個參數繼續帶往下個函式執行底下有個實驗

上面的片段程式執行後開啟瀏覽器連結上 localhost1337會發現伺服器回應結果順序如下

first middle ware

second middle ware

execute middle ware

end middleware function

從上面的結果可以得知剛才設定的 middleware都生效了在 appuse設定的 middleware是所有 url皆會執行方法如果有指定特定方法就可以使用 appget的 middleware設定在 appget函式的第二個參數就可以帶入函式或者是匿名函式只要函式裡面最後會接受 request response next這三個參數同時也有正確指定 next函式的執行時機最後都會執行到最後一個方法當然開發者也可以評估程式邏輯要執行到哪一個階段讓邏輯

可以更為分明

64 Express middleware 55

Nodejs中文電子書

65 Express路由應用

在實際開發上可能會遇到需要使用參數等方式混和變數一起使用

express裡面提供了一個很棒的處理方法 appall這個方式可以先採用基本路由配對再將設定為每個不同的處理方式開發者可以透過這個方式簡

化自己的程式邏輯

overview

author

version 20120226

create server

var app = require(rsquoexpressrsquo)createServer()

port = 1337

users = [

name rsquoClonnrsquo

name rsquoChirsquo

]

applisten(port)

appall(rsquouseridoprsquo function(req res next)

requser = users[reqparamsid]

if (requser)

next()

else

next(new Error(rsquocannot find user rsquo + reqparamsid))

)

appget(rsquouseridrsquo function(req res)

ressend(rsquoviewing rsquo + requsername)

)

appget(rsquouserideditrsquo function(req res)

ressend(rsquoediting rsquo + requsername)

)

56 6 Express介紹

Nodejs中文電子書

appget(rsquouseriddeletersquo function(req res)

ressend(rsquodeleting rsquo + requsername)

)

appget(rsquorsquo function(req res)

ressend(rsquoPage not foundrsquo 404)

)

consolelog(rsquostart express servernrsquo)

內部宣告一組預設的使用者分別給予名稱設定藉由 appall這個方法可以先將路由雛形建立再接下來設定 appget的路徑格式只要符合格式就會分配進入對應的方法中像上面的程式當中如果使用者輸入路徑為user0除了執行 appall程式之後執行 next方法就會對應到路徑設定為userid的這個方法當中如果使用者輸入路徑為user0edit就會執行到useridedit的對應方法

66 Express GET應用範例

我們準備一個使用 GET方法傳送資料的表單

ltDOCTYPE htmlgt

lthtmlgt

ltheadgt

ltmeta http-equiv=rdquoContent-Typerdquo content=rdquotexthtml charset=utf8rdquogt

lttitlegtNodejs 菜鳥筆記 (1)lttitlegt

ltlink rel=rdquostylesheetrdquo href=rdquocssstylecssrdquo type=rdquotextcssrdquo media=rdquoallrdquo gt

ltheadgt

ltbodygt

lth1gtNodejs 菜鳥筆記-註冊lth1gt

ltform id=rdquosignuprdquo method=rdquoGETrdquo action=rdquohttplocalhost3000Signuprdquogt

ltlabelgt 使用者名稱ltlabelgtltinput type=rdquotextrdquo id=rdquousernamerdquo name=rdquousernamerdquo gtltbrgt

ltlabelgt 電子郵件ltlabelgtltinput type=rdquotextrdquo id=rdquoemailrdquo name=rdquoemailrdquo gtltbrgt

ltinput type=rdquosubmitrdquo value=rdquo 註冊我的帳號rdquo gtltbrgt

ltformgt

ltbodygt

lthtmlgt

66 Express GET應用範例 57

Nodejs中文電子書

這個表單沒有什麼特別的地方我們只需要看第 9 行form 使用的method是 GET然後 action是rdquohttplocalhost3000Signupldquo等一下我們要來撰寫Signup這個 URL Path的處理程式

處理 Signup行為

我們知道所謂的 GET方法會透過 URL來把表單的值給帶過去以上面的表單來說到時候 URL會以這樣的形式傳遞

httplocalhost3000Signupusername=xxxampemail=xxx

所以要能處理這樣的資料必須有以下功能

bull 解析 URL

bull 辨別動作是 Signup

bull 解析出 username和 email

一旦能取得 username和 email的值程式就能加以應用了

處理 Signup的程式碼雛形

load module

var url = require(rsquourlrsquo)

urlData = urlparse(requrltrue)

action = urlDatapathname

reswriteHead(200 rdquoContent-Typerdquordquotexthtml charset=utf-8rdquo)

if (action === rdquoSignuprdquo)

user = urlDataquery

resend(rdquolth1gtrdquo + userusername + rdquo 歡迎您的加入 lth1gtltpgt 我們已經將會員啟用信寄至rdquo + useremail + rdquoltpgtrdquo)

首先需要加載 url module它是用來協助我們解析 URL的模組接著使用 urlparse方法第一個傳入 url字串變數也就是 requrl另外第二個參數的用意是設為 ture則引進 querystring模組來協助處理預設是 false它影響到的是 urlDataquery設為 true會傳回物件不然就只是一般的字串urlparse會將字串內容整理成一個物件我們把它指定給 urlData

action變數作為記錄 pathname這是我們稍後要來判斷目前網頁的動作是什麼接著先將 html表頭資訊 (Header)準備好再來判斷路徑邏輯如

58 6 Express介紹

Nodejs中文電子書

果是 Signup這個動作就把 urlDataquery裡的資料指定給 user然後輸出userusername和 useremail把使用者從表單註冊的資料顯示於頁面中

最後進行程式測試啟動 nodejs主程式之後開啟瀏覽器就會看到表單填寫完畢按下送出就可以看到結果了

完整 nodejs程式碼如下

var http = require(rsquohttprsquo)

url = require(rsquourlrsquo)

fs = require(rdquofsrdquo)

server

server = httpcreateServer(function (reqres)

var urlData

encode = rdquoutf8rdquo

filePath = rdquoviewexpress_get_example_formhtmlrdquo

action

urlData = urlparse(requrltrue)

action = urlDatapathname

reswriteHead(200 rdquoContent-Typerdquordquotexthtml charset=utf-8rdquo)

if (action === rdquoSignuprdquo)

user = urlDataquery

resend(rdquolth1gtrdquo + userusername + rdquo 歡迎您的加入 lth1gtltpgt 我們已經將會員啟用信寄至rdquo + useremail + rdquoltpgtrdquo)

else

fsreadFile(filePath encode function(err file)

reswrite(file)

resend()

)

)

serverlisten(3000)

consolelog(rsquoServer 跑起來了現在時間是rsquo + new Date())

66 Express GET應用範例 59

Nodejs中文電子書

67 Express POST應用範例

一開始準備基本的 html表單傳送內容以 POST方式form的 action屬性設定為 POST其餘 html內容與前一個範例應用相同

ltDOCTYPE htmlgt

lthtmlgt

ltheadgt

ltmeta http-equiv=rdquoContent-Typerdquo content=rdquotexthtml charset=utf8rdquogt

lttitlegtNodejs 菜鳥筆記 (1)lttitlegt

ltlink rel=rdquostylesheetrdquo href=rdquocssstylecssrdquo type=rdquotextcssrdquo media=rdquoallrdquo gt

ltheadgt

ltbodygt

lth1gtNodejs 菜鳥筆記-註冊lth1gt

ltform id=rdquosignuprdquo method=rdquoPOSTrdquo action=rdquohttplocalhost3000Signuprdquogt

ltlabelgt 使用者名稱ltlabelgtltinput type=rdquotextrdquo id=rdquousernamerdquo name=rdquousernamerdquo gtltbrgt

ltlabelgt 電子郵件ltlabelgtltinput type=rdquotextrdquo id=rdquoemailrdquo name=rdquoemailrdquo gtltbrgt

ltinput type=rdquosubmitrdquo value=rdquo 註冊我的帳號rdquo gtltbrgt

ltformgt

ltbodygt

lthtmlgt

nodejs的程式處理邏輯與前面 GET範例類似部分程式碼如下

qs = require(rsquoquerystringrsquo)

if (action === rdquoSignuprdquo)

formData = rsquorsquo

reqon(rdquodatardquo function (data)

formData += data

)

reqon(rdquoendrdquo function ()

user = qsparse(formData)

resend(rdquolth1gtrdquo + userusername + rdquo 歡迎您的加入 lth1gtltpgt 我們已經將會員啟用信寄至rdquo + useremail + rdquoltpgtrdquo)

)

60 6 Express介紹

Nodejs中文電子書

主要加入了rsquoquerystringrsquo這個moduel方便我們等一下解析由表單 POST回來的資料另外加入一個 formData的變數用來搜集待等一下表單回傳的資料前面的 GET範例我們只從 req拿出 url的資料這次要在利用req身上的事件處理

JavaScript 在訂閱事件時使用 addEventListener而 nodejs 使用的則是on這邊加上了監聽 data的事件會在瀏覽器傳送資料到Web Server時被執行參數是它所接收到的資料型態是字串

接著再增加 end的事件當瀏覽器的請求事件結束時它就會動作

由於瀏覽器使用 POST在上傳資料時會將資料一塊塊地上傳因為我們在監聽 data事件時透過 formData變數將它累加起來lt不過由於我們

上傳的資料很少一次就結束不過如果日後需要傳的是資料比較大的檔

案這個累加動作就很重要

當資料傳完就進到 end 事件中會用到 qsparse 來解析 formDataformData的內容是字串內容是

username=wordsmithampemail=wordsmith40somewhere

而 qsparse可以幫我們把這個 querystring轉成物件的格式也就是

username=wordsmithampemail=wordsmith40somewhere

一旦轉成物件並指定給 user之後其他的事情就和 GET方法時操作的一樣寫 response的表頭將內容回傳並將 userusername和 useremail代入到內容中

修改完成後接著執行 nodejs程式啟動 web server開啟瀏覽器進入表單測試看看POST的方式能否順利運作

完整程式碼如下

var http = require(rsquohttprsquo)

url = require(rsquourlrsquo)

fs = require(rdquofsrdquo)

qs = require(rsquoquerystringrsquo)

server

server = httpcreateServer(function (reqres)

var urlData

67 Express POST應用範例 61

Nodejs中文電子書

encode = rdquoutf8rdquo

filePath = rdquoviewexpress_post_example_formhtmlrdquo

formData

action

urlData = urlparse(requrltrue)

action = urlDatapathname

reswriteHead(200 rdquoContent-Typerdquordquotexthtml charset=utf-8rdquo)

if (action === rdquoSignuprdquo)

formData = rsquorsquo

reqon(rdquodatardquo function (data)

formData += data

)

reqon(rdquoendrdquo function ()

user = qsparse(formData)

resend(rdquolth1gtrdquo + userusername + rdquo 歡迎您的加入 lth1gtltpgt 我們已經將會員啟用信寄至rdquo + useremail + rdquoltpgtrdquo)

)

else

fsreadFile(filePath encode function(err file)

reswrite(file)

resend()

)

)

serverlisten(3000)

consolelog(rsquoServer 跑起來了現在時間是rsquo + new Date())

62 6 Express介紹

Nodejs中文電子書

68 Express AJAX應用範例

在 Nodejs要使用 Ajax傳送資料並且與之互動在接受資料的部份沒有太大的差別client端不是用 GET就是用 POST來傳資料重點在處理完後用 JSON格式回傳當然 Ajax不見得只傳 JSON格式有時是回傳一段 HTML碼不過後者對伺服器來說基本上就和前兩篇沒有差別了所以我們還是以回傳 JSON做為這一回的主題

這一回其實大多數的工作都會落在前端 Ajax上面前端要負責發送與接收資料並在接收資料後撤掉原先發送資料的表單並將取得的資料

改成 HTML格式之後放上頁面

首先先準備 HTML靜態頁面資料

ltDOCTYPE htmlgt

lthtmlgt

ltheadgt

lttitlegtNodejs 菜鳥筆記 (3)lttitlegt

ltlink rel=rdquostylesheetrdquo href=rdquocssstylecssrdquo type=rdquotextcssrdquo media=rdquoallrdquo gt

ltscript src=rdquohttpsajaxgoogleapiscomajaxlibsjquery171jqueryminjsrdquogtltscriptgt

ltheadgt

ltbodygt

lth1gtNodejs 菜鳥筆記-註冊 (用 Ajax)lth1gt

ltform id=rdquosignuprdquo action=rdquoSignuprdquo method=rdquoPOSTrdquogt

ltlabelgt 使用者名稱ltlabelgtltinput type=rdquotextrdquo id=rdquousernamerdquo name=rdquousernamerdquo gtltbrgt

ltlabelgt 電子郵件ltlabelgtltinput type=rdquotextrdquo id=rdquoemailrdquo name=rdquoemailrdquo gtltbrgt

ltinput type=rdquosubmitrdquo value=rdquo 註冊我的帳號rdquo gtltbrgt

ltformgt

ltdiv id=rdquoresultrdquogt

ltdivgt

ltscript type=rdquotextjavascriptrdquogt

$(function()$(rsquosignup input[type=rdquosubmitrdquo]rsquo)click(function(e)

var user =

userusername = $(rdquousernamerdquo)val()useremail = $(rdquoemailrdquo)val()

$post(rdquoSignuprdquouserfunction(data)greet(data)

)

68 Express AJAX應用範例 63

Nodejs中文電子書

epreventDefault()

)

var greet = function(msg)

var resultNode = $(rsquoresultrsquo)greeting = $(rsquolth2gtHirsquo+msgusername+rsquo 你的會員 id 是rsquo+ msgid +rsquo 我們會將會員啟動信寄至rsquo+msgemail+rsquolth2gtrsquo)

$(rsquosignuprsquo)hide()resultNodehtml(rdquordquo)

resultNodeappend(greeting)

)

ltscriptgt

ltbodygt

lthtmlgt

HTML頁面上準備了一個表單用來傳送註冊資料接著直接引用了Google CDN來載入 jQuery用來幫我們處理 Ajax的工作這次要傳送和接收的工作很大的變動都在 HTML頁面上的 JavaScript當中我們要做的事有 (相關 jQuery處理這邊不多做贅述指提起主要功能解說)

bull 用 jQUery取得 submit按鈕綁定它的 click動作

bull 取得表單 username和 email的值存放在 user這個物件中

bull 用 jQuery的 $post方法將 user的資料傳到 Server

bull 一旦成功取得資料後透過 greet這個 function組成回報給 user的訊息

bull 清空原本給使用者填資料的表單

bull 將 Server回傳的 usernameemail和 id這 3個資料組成回應的訊息

bull 將訊息放到原本表單的位置

經過以上的處理後一個 Ajax的表單的基本功能已經完成

接著進行 nodejs主要程式的編輯部分程式碼如下

var fs = require(rdquofsrdquo)

64 6 Express介紹

Nodejs中文電子書

qs = require(rsquoquerystringrsquo)

if (action === rdquoSignuprdquo)

formData = rsquorsquo

reqon(rdquodatardquo function (data)

formData += data

)

reqon(rdquoendrdquo function ()

var msg

user = qsparse(formData)

userid = rdquo123456rdquo

msg = JSONstringify(user)

reswriteHead(200 rdquoContent-Typerdquordquoapplicationjson charset=utf-8rdquordquoContent-Lengthrdquomsglength)

resend(msg)

)

這裡的程式和前面 POST範例基本上大同小異差別在

bull 幫 user的資料加上 id隨意存放一些文字進去讓 Server回傳的資料多於 Client端傳上來的不然會覺得 Server都沒做事

bull 增加了 msg 這個變數存放將 user 物件 JSON 文字化的結果JSONstringify這個轉換函式是 V8引擎所提供的如果你好奇的話

bull 大重點來了我們要告訴 Client端這次回傳的資料格式是 JSON所在 Content-type和 Content-Length要提供給 Client

Server很輕鬆就完成任務了最後進行程式測試啟動 nodejs主程式之後開啟瀏覽器就會看到表單填寫完畢按下送出就可以看到結果了

最後 nodejs本篇範例程式碼如下

var http = require(rsquohttprsquo)

url = require(rsquourlrsquo)

fs = require(rdquofsrdquo)

qs = require(rsquoquerystringrsquo)

server

68 Express AJAX應用範例 65

Nodejs中文電子書

server = httpcreateServer(function (reqres)

var urlData

encode = rdquoutf8rdquo

filePath = rdquoviewexpress_ajax_example_formhtmlrdquo

formData

action

urlData = urlparse(requrltrue)

action = urlDatapathname

if (action === rdquoSignuprdquo)

formData = rsquorsquo

reqon(rdquodatardquo function (data)

formData += data

)

reqon(rdquoendrdquo function ()

var msg

user = qsparse(formData)

userid = rdquo123456rdquo

msg = JSONstringify(user)

reswriteHead(200 rdquoContent-TyperdquordquoapplicationjsonrdquordquoContent-Lengthrdquomsglength)

resend(msg)

)

else

fsreadFile(filePath encode function(err file)

reswriteHead(200 rdquoContent-Typerdquordquotexthtml charset=utf-8rdquo)

reswrite(file)

resend()

)

)

serverlisten(3000)

66 6 Express介紹

Nodejs中文電子書

consolelog(rsquoServer 跑起來了現在時間是rsquo + new Date())

69 原始資料提供

bull [NodeJS初學者筆記 (1)-用 GET傳送資料] (httpithelpithomecomtwquestion10087402)

bull [NodeJS初學者筆記 (2)-用 POST傳送資料] (httpithelpithomecomtwquestion10087489)

bull [NodeJS 初學者筆記 (3)-用 Ajax 傳送資料] (httpithelpithomecomtwquestion10087627)

69 原始資料提供 67

Nodejs中文電子書

68 6 Express介紹

7

CoffeeScript

bull Spine

bull Hem

bull Spineapp

Letrsquos Have a Cup of CoffeeScript

bull es5-shim ECMAScript 5 compatibility shims for legacy JavaScript engines

ESnext

node-iform - A middleware for node-validator httpsgithubcomguileennode-iform

69

Nodejs中文電子書

70 7 CoffeeScript

8

製作一個 Hubot的 Plurk Adapter

81 應用事項提醒

此應用範例以CoffeeScript編寫主要程式架構採用Github釋放的Hubot專案以下有幾個主要注意事項請讀者注意

bull Hubot 一開始的架構除了內建的兩個 Adapter 之外其餘都要以Nodejs的Module方式才能運作

bull Hubot的 binhubot裡面寫著 npm install所以不管你怎麼改原始碼也不能改變第一點的狀況

其實上述都在討論同一件事情NodeJS的模組而且模組不能設定相對路徑之類的來安裝一定要包裝成 npm相符和格式才有辦法正確執行

82 建立 Adapter

首先需要準備兩個檔案

bull packagejson

bull plurkcoffee

71

Nodejs中文電子書

有這兩個就足以變成 NodeJS的模組了在開始 Coding之前先來設定一下 packagejson相依性

rdquonamerdquo rdquohubot-plurkrdquo

rdquoversionrdquo rdquo011rdquo

rdquomainrdquo rdquoplurkrdquo

rdquodependenciesrdquo

rdquohubotrdquo rdquogt=205rdquo

rdquooauthrdquo rdquordquo

rdquocronrdquordquordquo

這邊會使用到NodeJS的 cron模組(Module)而登入 Plurk需要OAuth標準授權才行所以也需要加載 OAuth模組

注意Hubot在讀取非內建模組時會自動在前面加上 hubot-的前置

83 建立 Robot跟 API

本篇應用基本上程式碼大部分參考 Hubot - Twitter Adapter來製作主要差異只有在使用 OAuth這個模組Twitter有 Streaming可用而 Plurk則得用 Comet方式來達到即時讀取

plurkcoffee程式碼

Robot = require(rdquohubotrdquo)robot()

Adapter = require(rdquohubtordquo)adapter()

EventEmitter = require(rdquoeventsrdquo)EventEmitter

oauth = require(rdquooauthrdquo)

cronJob = require(rdquocronrdquo)CronJob

class Plurk exntends Adapter

class PlurkStreaming exnteds EventEmitter

72 8 製作一個 Hubot的 Plurk Adapter

Nodejs中文電子書

先弄個基本架構主要 Class皆參考 Twitter Adapter命名方式接著再將 Plurk這個 Class增加幾個Method基本上只要有 run send reply就夠了而 run用來做初始化的部分

class Plurk entends Adapter

send (plurk id strings⋯) -gt

reply (plurk id strings⋯) -gt

run -gt

看起來有點東西接著來處理主要的 Plurk API結合部分

class PlurkStreaming extends EventEmitter

consuctor (options) -gt

plurk (callback) -gt

觀察河道

getChannel -gt

取得 Comet 網址

reply (plurk id message) -gt

回噗

acceptFriends -gt

接受好友

get (path callback) -gt

GET 請求

post (path body callback)-gt

POST 請求(其實是裝飾)

request (method path body callback)-gt

主要的 OAuth 請求

comet (server callback)-gt

噗浪的 Comet 傳回是 JavaScript Callback 要另外處理後才會變成 JSON

然後把注意力集中到 constructor上先把建構子弄好

constructor (options) -gt

super()

if optionskey and optionssecret and optionstoken and optionstoken secret

key = optionskey

secret = optionssecret

token = optionstoken

token secret = optionstoken secret

83 建立 Robot跟 API 73

Nodejs中文電子書

建立 OAuth 連接

consumer = new oauthOAuth(

rdquohttpwwwplurkcomOAuthrequest tokenrdquo

rdquohttpwwwplurkcomOAuthaccess tokenrdquo

key

secret

rdquo10rdquo

rdquohttpwwwplurkcomOAuthauthorizerdquo

rdquoHMAC-SHA1rdquo

)

domain = rdquowwwplurkcomrdquo

初始化取得 Comet 網址

do getChannel

else

throw new Error(rdquo 參數不足需要 Key Secret Token Token Secretrdquo)

接著來處理 request這個 method

request (method path body callback) -gt

記錄一下這次的 Request

consolelog(rdquohttpdomainpathrdquo)

Callback 這邊先不丟進去要用另一種方式處理

request = consumerget(rdquohttpdomainpathrdquo token token secret null)

requeston rdquoresponserdquo (res) -gt

reson rdquodatardquo (chunk) -gt

parseResponse(chunk+rsquorsquo callback)

reson rdquoendrdquo (data) -gt

consolelog rdquoEnd Request pathrdquo

reson rdquoerrorrdquo (data) -gt

consolelog rdquoError rdquo + data

requestend()

處理資料

parseResponse = (data callback) -gt

if datalength gt 0

用 TryCatch 避免處理 JSON 出錯導致整個中斷

try

callback null JSONparse(data)

catch err

74 8 製作一個 Hubot的 Plurk Adapter

Nodejs中文電子書

consolelog(rdquoError Parse JSONrdquo + data err)

繼續執行

callback null data ||

大致上就是這樣上面程式的架構已經將整個 Hubot Plurk Adapter完成因為在測試時竟然因為噗浪 Lag而沒讀到完整的 Comet資料然後造成程式異常為了避免這個問題發生因此需要加上為了完美呈現需要再

加上 Comet的處理所以要使用到 EventEmitter的功能

comet (server callback) -gt

在 Callback 裡面會找不到自身所以設定區域變數

self =

記錄一下這次的 Request

consolelog(rdquo[Comet] serverrdquo)

Callback 這邊先不丟進去要用另一種方式處理

request = consumerget(rdquohttpdomainpathrdquo token token secret null)

requeston rdquoresponserdquo (res) -gt

reson rdquodatardquo (chunk) -gt

parseResponse(chunk+rsquorsquo callback)

reson rdquoendrdquo (data) -gt

consolelog rdquoEnd Request pathrdquo

請求結束發出事件通知可以進行下一次請求

selfemit rdquonextPlurkrdquo

reson rdquoerrorrdquo (data) -gt

consolelog rdquoError rdquo + data

requestend()

處理資料

parseResponse = (data callback) -gt

if datalength gt 0

用 trycatch 避免失敗中斷

try

去掉 JavaScript 的 Callback

data = datamatch(CometChannelscriptCallback((+))s)

jsonData = rdquordquo

if data

83 建立 Robot跟 API 75

Nodejs中文電子書

jsonData = JSONparse(data[1])

else

如果沒有任何 Match 嘗試直接 parse

jsonData = JSONparse(data)

catch err

consolelog(rdquo[Comet] Errorrdquo data err)

用 TryCatch 避免處理 JSON 出錯導致整個中斷

try

只傳入 json 的 data 部分

callback null jsonDatadata

catch err

consolelog(rdquo[Comet]Error Parse JSONrdquo + data err)

繼續執行

callback null data ||

後面的 get跟 post就簡單多了

get (path callback) -gt

request(rdquoGETrdquo path null callback)

post (path body callback) -gt

request(rdquoPOSTrdquo path body callback)

接著處理取的 Comet網址的 getChannel

getChannel -gt

self =

get rdquoAPPRealtimegetUserChannelrdquo (error data) -gt

if error

檢查是否有 comet server

if datacomet server

selfchannel = datacomet server

如果沒有 Channel Ready 就嘗試連接會失敗

selfemit(rsquochannel readyrsquo)

那麼先來處理 Plurk Adaper好處理的部份

send (plruk id strings⋯)-gt

跟 Reply 一樣直接交給 reply 做

reply plurk id strings⋯

76 8 製作一個 Hubot的 Plurk Adapter

Nodejs中文電子書

reply (plurk id strings⋯) -gt

stringsforEach (message) =gt

botreply(plruk id message)

接著把 run處理好就可以上線運作摟

run -gt

self =

options =

key processenvHUBOT PLURK KEY

secret processenvHUBOT PLURK SECRET

token processenvHUBOT PLURK TOKEN

token secret processenvHUBOT PLURK TOKEN SECRET

創建剛剛的 API

bot = new PlurkStreaming(options)

依照 Twitter 的 new RobotTextMessage 會沒有反應所以參考 hubot-minecraft 的方式

r = robotconstructor

處理噗浪河道訊息

doPlurk = (data)-gt

檢查是否為回噗

if dataresponse

datacontent raw = dataresponsecontent raw

datauser id = dataresponseuser id

確定有噗浪 ID 跟訊息

if dataplurk id and datacontent raw

selfreceive new rTextMessage(dataplurk id datacontent raw)

取得 Comet Server 完成開始第一次 Comet 連接

boton rdquochannel readyrdquo () -gt

botplurk selfdoPlurk

上一次 Comet 完成繼續 Polling

boton rdquonextPlurkrdquo ()-gt

botplurk selfdoPlurk

定時接受好友邀請

do botacceptFriends

bot = bot

83 建立 Robot跟 API 77

Nodejs中文電子書

終於完成 AdapterHubot專案裡面的 scripts資料夾內是互動部分不需要像 Adapter如此大費周章處理只新增檔案並且設計好對白之後就會回噗了我開發用的機器人在此大家可以去跟他玩玩[httpplurkcomelct9620 bot](httpplurkcomelct9620 bot)

84 原始資料提供

bull [製 作 一 個 Hubot 的 噗 浪 Adapter](http revo-skillfrosttw blog20120318create-a-hubot-plurk-adapter)

78 8 製作一個 Hubot的 Plurk Adapter

9

Nodejs好用工具介紹

91 logio

Real-time log monitoring in your browser

httplogioorg

nodejs (gt=0412 lt=0611)

npm config set unsafe-perm true

npm install -g --prefix=usrlocal logio

logio server start

httplocalhost8998

79

Nodejs中文電子書

80 9 Nodejs好用工具介紹

10

精選文章

101 Express

bull 使用 NodeJS + Express從 GETPOST Request取值 [邀稿中]

bull link2 [邀稿中]

bull link3 [邀稿中]

81

Nodejs中文電子書

82 10 精選文章

11

參考資源

111 Nodejs書籍

bull Programming Nodejs April 2012 (est) Travis Swicegood Pragmatic Bookshelf

bull e Little Book on CoffeeScript January 2012 Alex MacCaw OrsquoReilly Media

bull Node for Front-End Developers January 2012 Garann Means OrsquoReilly Media

bull Building Hypermedia APIs with HTML5 and Node November 2011 MikeAmundsen OrsquoReilly Media

bull Node Web Development August 2011 David Herron PacktPub

bull CoffeeScript Accelerated JavaScript Development July 2011 Trevor BurnhamPragmatic Bookshelf

bull Getting Started with GEO CouchDB and Nodejs July 2011 Mick omp-son OrsquoReilly Media

bull What Is Node July 2011 Brett McLaughlin OrsquoReilly Media

bull Node Up and Running May 2011 Tom Hughes-Croucher Mike WilsonOrsquoReilly Media

83

Nodejs中文電子書

bull Hands-on Nodejs e Nodejs introduction and API reference May 2011 Pe-dro Teixeira LeanPub

bull e Node Beginner Book Manuel Kiessling

bull Mastering Nodejs visionmedia

112 Nodejs影音教學

bull e Node Sessions e Best of OSCON 2011 August 2011 OrsquoReilly Media(Video)

bull Tom Hughes-Croucher on Node March 2011 OrsquoReilly Media (Video)

113 Nodejs教學網站

bull Node Tuts

114 Nodejs課程

bull Introduction To Nodejs Van Nguyen CodeLesson

84 11 參考資源

Page 24: Nodejs Wiki

Nodejs中文電子書

淺藍色底色的部份是跟上面例子不一樣的地方 ltscriptgt

函數返回函數的另外一個用途是可以暫緩函數執行例如

function add(m n) return m+n

var a = add(20 10) consolelog(a)

add這個函數必須同時輸入兩個參數才有辦法執行如果我希望這個函數可以先給它一個參數等一些處理過後再給一個參數然後得到結

果就必須用函數返回函數的方式做修改

function add(m)

return function(n) return m+n

var wait another arg = add(20)先給一個參數 var a = function(arr)

var ret=0 for(var i=0iltarrlengthi++) ret+=arr[i] return ret

([1234])計算一下另一個參數 var b = wait another arg(a)然後再繼續執行 consolelog(b)

像這樣利用函數返回函數使得原本接受多個參數的函數可以一次

接受一個參數直到參數接收完成才執行得到結果的方式有一個學名就

叫做Currying

綜合以上許多奇技淫巧就可以透過用函數來處理函數的方式調整

程式流程接下來看看

26 流程控制

(以 sync方式使用 async函數避開巢狀 callback循序呼叫 async callback等奇技淫巧)

建議參考

bull httphowtonodeorgcontrol- ow

bull httphowtonodeorgcontrol- ow-part-ii

18 2 JavaScript與 NodeJS

Nodejs中文電子書

bull httphowtonodeorgcontrol- ow-part-iii

bull httpblogmixunet20110202essential-node-js-patterns-and-snippets

這幾篇都是非常經典的 NodeJSJavascript流程控制好文章(阿mixu是在介紹一些 pattern時提到這方面的主題)不過我還是用幾個簡單的程式介紹一下做法跟概念

並發與等待

下面的程式參考了 mixu文章中的做法

var wait = function(callbacks done) consolelog(lsquowait startrsquo) var counter = call-backslength var results = [] var next = function(result) 接收函數執行結果並判斷是否結束執行 resultspush(result) if(ndashcounter == 0) done(results)如果結束執行就把所有執行結果傳給指定的 callback處理 for(var i = 0 i lt callbackslength i++) 依次呼叫所有要執行的函數 callbacks[i](next) consolelog(lsquowait endrsquo)

wait( [ function(next) setTimeout(function() consolelog(lsquodone arsquo) var result =500 next(result) 500) function(next) setTimeout(function() con-solelog(lsquodone brsquo) var result = 1000 next(result) 1000) function(next)setTimeout(function() consolelog(lsquodone crsquo) var result = 1500 next(1500) 1500) ] function(results) var ret = 0 i=0 for( iltresultslength i++) ret+= results[i] consolelog(lsquodone all result lsquo+ret)

)

執行結果wait start wait end done a done b done c done all result 3000

可以看出來其實 wait並不是真的等到所有函數執行完才結束執行而是在所有傳給他的函數執行完畢後(不論同步非同步)才執行處理結

果的函數(也就是 done())

不過這樣的寫法還不夠實用因為沒辦法實際讓函數可以等待執行

完畢又能當作事件處理函數來實際使用上面參考到的 Tim Caswell的文

26 流程控制 19

Nodejs中文電子書

章裡面有一種解法不過還需要額外包裝(在他的例子中)NodeJS核心的 fs物件把一些函數(例如 readFile)用 Currying處理類似像這樣

var fs = require(lsquofsrsquo) var readFile = function(path)

return function(callback errback)

fsreadFile(path function(err data)

if(err) errback()

else callback(data)

)

其他部份可以參考 Tim Caswell的文章他的 Doparallel跟上面的 wait差不多意思這裡只提示一下他沒說到的地方

另外一種做法是去修飾一下 callback當他作為事件處理函數執行後再用 cps的方式取得結果

ltscriptgt function Wait(fns done)

var count = 0 var results = [] thisgetCallback = function(index)

count++ return (function(waitback)

return function() var i=0args=[]for(iltargumentslengthi++)

argspush(arguments[i])

argspush(waitback) fns[index]apply(thisargs)

)(function(result) resultspush(result) if(ndashcount == 0)

20 2 JavaScript與 NodeJS

Nodejs中文電子書

done(results)

)

var a = new Wait(

[ function(waitback) consolelog(lsquodone arsquo) var result = 500 wait-back(result) function(waitback) consolelog(lsquodone brsquo) var result =1000 waitback(result) function(waitback) consolelog(lsquodone crsquo) varresult = 1500 waitback(result) ] function(results) var ret = 0 i=0for( iltresultslength i++) ret += results[i] consolelog(lsquodone allresult lsquo+ret)

) var callbacks = [agetCallback(0)agetCallback(1)agetCallback(0)agetCallback(2)]一次取出要使用的 callbacks避免結果提早送出 setTimeout(callbacks[0]500) setTimeout(callbacks[1] 1000) setTimeout(callbacks[2] 1500) setTime-out(callbacks[3] 2000) 當所有取出的 callbacks執行完畢就呼叫 done()來處理結果 ltscriptgt

執行結果

done a done b done a done c done all result 3500

上面只是一些小實驗更成熟的作品是 Tim Caswell 的 stephttpsgithubcomcreationixstep

如果希望真正使用同步的方式寫非同步則需要使用 Promisejs這一類的 library來轉換非同步函數不過他結構比較複雜 XD(見仁見智不過有些人認為 Promise有點過頭了)httpblogsmsdncombrbucktonarchive20110815promise-js-2-0-promise-framework-for-javascriptaspx

如果想不透過其他 Library做轉換又能直接用同步方式執行非同步函數大概就要使用一些需要額外 compile原始程式碼的方法了例如 BrunoJouhier的 streamlinejshttpsgithubcomSagestreamlinejs

26 流程控制 21

Nodejs中文電子書

循序執行

循序執行可以協助把非常深的巢狀 callback結構攤平例如用這樣的簡單模組來做(serialjs)

moduleexports = function(funs) var c = 0 if(isArrayOfFunctions(funs))

throw(lsquoArgument type was not matched Should be array of func-tionsrsquo)

return function()

var args = Arrayprototypeslicecall(arguments 0) if((cgt=funslength))

c++ return funs[c-1]apply(this args)

function isArrayOfFunctions(f ) if(typeof f == lsquoobjectrsquo) return false if(flength)return false if(fconcat) return false if(fsplice) return false var i = 0 for(iltflength i++)

if(typeof f[i] == lsquofunctionrsquo) return false

return true

簡單的測試範例(testSerialjs)使用 fs 模組確定某個 path 是檔案然後讀取印出檔案內容這樣會用到兩層的 callback所以測試中有使用serial的版本與 nested callbacks的版本做對照

var serial = require(lsquoserialrsquo) fs = require(lsquofsrsquo) path = lsquodclientjsrsquo cb = serial([ func-tion(err data)

if(err)

if(dataisFile) fsreadFile(path cb)

22 2 JavaScript與 NodeJS

Nodejs中文電子書

else consolelog(err)

function(err data)

if(err) consolelog(lsquo[ attened by searial]rsquo) con-solelog(datatoString(lsquoutf8rsquo))

else consolelog(err)

]) fsstat(path cb)

fsstat(path function(err data) 第一層 callback if(err)

if(dataisFile)

fsreadFile(path function(err data) 第二層 callback if(err)

consolelog(lsquo[nested callbacks]rsquo) con-solelog(datatoString(lsquoutf8rsquo))

else consolelog(err)

)

else consolelog(err)

)

關鍵在於這些 callback的執行是有順序性的所以利用 serial返回的一個函數 cb來取代這些 callback然後在 cb中控制每次會循序呼叫的函數

26 流程控制 23

Nodejs中文電子書

就可以把巢狀的 callback攤平成循序的 function陣列(就是傳給 serial函數的參數)

測試中的dclientjs 是一個簡單的 dnode 測試程式放在跟 testSerialjs同一個目錄

var dnode = require(lsquodnodersquo)

dnodeconnect(8000 lsquolocalhostrsquo function(remote)

remoterestart(function(str) consolelog(str) processexit()

)

)

執行測試程式後出現結果

[ attened by searial] var dnode = require(lsquodnodersquo)

dnodeconnect(8000 lsquolocalhostrsquo function(remote)

remoterestart(function(str) consolelog(str) processexit()

)

)

[nested callbacks] var dnode = require(lsquodnodersquo)

dnodeconnect(8000 lsquolocalhostrsquo function(remote)

remoterestart(function(str) consolelog(str) processexit()

)

)

對照起來看兩種寫法的結果其實是一樣的但是利用 serialjs巢狀的 callback結構就會消失

不過這樣也只限於順序單純的狀況如果函數執行的順序比較複雜

(不只是一直線)還是需要用功能更完整的流程控制模組比較好例如

httpsgithubcomcaolanasync

24 2 JavaScript與 NodeJS

3

Nodejs安裝與設定

本篇將講解如何在各個不同 OS建立 NodeJS環境目前 NodeJS 048版本環境架設方式需依賴 Linux指令才可編譯完成當然在不同作業系統中也已經有 NodeJS package可以直接使用指令快速架設以下各不同作業系統解說如何安裝 NodeJS

31 Ubuntu Linux

更新推薦使用 nvm

git clone gitgithubcomcreationixnvmgit ~nvm

echo rdquo ~nvmnvmshrdquo gtgt ~bashrc

nvm install v0614

nvm alias default v0614

以 上 可 參 考 http dreamerslabcom blog tw how-to-setup-a-node-js-development-environment-on-ubuntu-11-04

使用 APT套件管理工具是常見的方法以下是使用社群提供的 PPA安裝方式

sudo apt-get install python-software-properties

sudo add-apt-repository ppachris-leanodejs-devel

25

Nodejs中文電子書

sudo apt-get update

sudo apt-get install nodejs

32 Other Linux

Linux很適合作為 NodeJS的伺服器作業系統及開發環境安裝前請先確認以下套件已正確安裝

bull curl (wget)用來下載檔案的工具

bull git先進的版本控制工具

bull g++ GNU C++軟體編譯工具

bull make GNU軟體專案建置工具

安裝指令如下如設有權限問題請在指令前面加上 sudo

git clone httpsgithubcomjoyentnodegit

cd node

git checkout v067

configure

make

sudo make install

接著測試 nodeJS是否正常執行

node --version

出現版本訊息即表示安裝成功

33 Windows

nodeJS 在 v060 版本之後開始正式支援 windows native直接使用nodeexe 就可以執行程式支援性完全與 linux 相同更棒的部份就是不需經過編譯經過下載之後簡單設定完成立即開發 node程式

26 3 Nodejs安裝與設定

Nodejs中文電子書

下載 nodejs安裝檔案 lthttpnodejsorgdownloadgt

如此完成 windows native nodeexe安裝接著可以進入 command line執行測試在 command line輸入rdquonoderdquo會出現 nodejs版本訊息畫面表示安裝完成

33 Windows 27

Nodejs中文電子書

28 3 Nodejs安裝與設定

4

Nodejs基礎

前篇文章已經由介紹安裝至設定都有完整介紹nodeJS 內部除了javascript常用的函式 (function)物件 (object)之外也有許多不同的自訂物件nodeJS預設建立這些物件為核心物件是為了要讓開發流程更為這些資料在官方文件已經具有許多具體說明接下來將會介紹在開發 nodeJS程式時常見的物件特性與使用方法

41 nodejs http伺服器建立

在 lsquonodejs官方網站 lthttpnodejsorggtlsquo裡面有舉一個最簡單的 HTTP伺服器建立一開始初步就是建立一個伺服器平台讓 nodejs可以與瀏覽器互相行為每種語言一開始的程式建立都是以 Hello world開始最初也從 Hello world帶各位進入 nodejs的世界

輸入以下程式碼儲存檔案為 node basic http hello worldjs

var server

ip = rdquo127001rdquo

port = 1337

http = require(rsquohttprsquo)

server = httpcreateServer(function (req res)

29

Nodejs中文電子書

reswriteHead(200 rsquoContent-Typersquo rsquotextplainrsquo)

resend(rsquoHello Worldnrsquo)

)

serverlisten(port ip)

consolelog(rdquoServer running at httprdquo + ip + rdquordquo + port)

程式碼解講一開始需要有幾個基本的變數

bull ip 機器本身的 ip位置因為使用本地端因此設定為 127001

bull port 需要開通的阜號通常設定為 http port 80因範例不希望與基本 port相衝隨意設定為 1337

在 nodejs 的程式中有許多預設的模組可以使用因此需要使用require方法將模組引入在這邊我們需要使用 http這個模組因此將 http載入Http模組裡面內建有許多方法可以使用這邊採用 createServer創建一個基本的 http伺服器再將 http伺服器給予一個 server變數裡面的回呼函式 (call back function)可以載入 http伺服器的資料與回應方法 (requestresponse)在程式裡面就可以看到我們直接回應給瀏覽器端所需的 Header回應內容

reswriteHead(200 rsquoContent-Typersquo rsquotextplainrsquo)

resend(rsquoHello Worldnrsquo)

Http伺服器需要設定 port ip在最後需要設定 Http監聽需要使用到listen事件監聽所有 Http伺服器行為

httplisten(port ip)

所有事情都完成之後需要確認伺服器正確執行因此使用 console在javascript裡就有這個原生物件console所印出的資料都會顯示於 nodejs伺服器頁面這邊印出的資料並不會傳送到使用者頁面上之後許多除壞

(debug)都會用到 console物件

consolelog(rdquoServer running at httprdquo + ip + rdquordquo + port)

30 4 Nodejs基礎

Nodejs中文電子書

42 nodejs http路徑建立

前面已經介紹如何建立一個簡單的 http伺服器接下來本章節將會介紹如何處理伺服器路徑 (rout)問題在 http的協定下所有從瀏覽器發出的要求 (request)都需要經過處理路徑上的建立也是如此

路徑就是指伺服器 ip位置或者是網域名稱之後對於伺服器給予的要求修改剛才的 hello world檔案修改如下

server = httpcreateServer(function (req res)

consolelog(requrl)

reswriteHead(200 rsquoContent-Typersquo rsquotextplainrsquo)

resend(rsquohello worldnrsquo)

)

重新啟動 nodejs程式後在瀏覽器端測試一下路徑行為結果如下圖

當在瀏覽器輸入 http1270011337test 在伺服器端會收到兩個要求一個是我們輸入的test要求另外一個則是faviconicotest的路徑要求http伺服器本身需要經過程式設定才有辦法回應給瀏覽器端所需要的回應在伺服器中所有的路徑要求都是需要被解析才有辦法取得資料從

42 nodejs http路徑建立 31

Nodejs中文電子書

上面解說可以了解到在 nodejs當中所有的路徑都需要經過設定未經過設定的路由會讓瀏覽器無法取得任何資料導致錯誤頁面的發生底下將會解

說如何設定路由同時避免發生錯誤情形先前 nodejs程式需要增加一些修改才能讓使用者透過瀏覽器在不同路徑時有不同的結果根據剛才

的程式做如下的修改

var server

ip = rdquo127001rdquo

port = 1337

http = require(rsquohttprsquo)

url = require(rsquourlrsquo)

path

server = httpcreateServer(function (req res)

path = urlparse(requrl)

reswriteHead(200 rsquoContent-Typersquo rsquotextplainrsquo)

switch (pathpathname)

case rdquoindexrdquo

resend(rsquoI am indexnrsquo)

break

case rdquotestrdquo

resend(rsquothis is test pagenrsquo)

break

default

resend(rsquodefault pagenrsquo)

break

)

serverlisten(port ip)

consolelog(rdquoServer running at httprdquo + ip + rdquordquo + port)

程式做了片段的修改首先載入 url模組另外增加一個 path變數url模組就跟如同他的命名一般專門處理 url字串處理裡面提供了許多方法來解決路徑上的問題因為從瀏覽器發出的要求路徑可能會帶有多種需求

或者 GET參數組合等因此我們需要將路徑單純化取用路徑部分的資料即可例如使用者可能會送出 http1270011337testsend=1如果直接

32 4 Nodejs基礎

Nodejs中文電子書

信任 requrl就會收到結果為testsend=1所以需要透過 url模組的方法將路徑資料過濾

在這邊使用 urlparse的方法裡面帶入網址格式資料會回傳路徑資料為了後需方便使用將回傳的資料設定到 path變數當中在回傳的路徑資料裡面包含資訊如下圖

這邊只需要使用單純的路徑要求直接取用 pathpathname就可以達到我們的目的

最後要做路徑的判別在不同的路徑可以指定不同的輸出在範例中

有三個可能結果第一個從瀏覽器輸入index就會顯示 index結果test 就會呈現出 test頁面最後如果都不符合預期的輸入會直接顯示 default的頁面最後的預防可以讓瀏覽器不會出現非預期結果讓程式的可靠性提昇

底下為測試結果

42 nodejs http路徑建立 33

Nodejs中文電子書

43 nodejs檔案讀取

前面已經介紹如何使用路由(rount)做出不同的回應實際應用只有在瀏覽器只有輸出幾個文字資料總是不夠的在本章節中將介紹如何使

用檔案讀取輸出檔案資料讓使用者在前端瀏覽器也可以讀取到完整的

html css javascript檔案輸出

檔案管理最重要的部分就是 lsquoFile system lthttpnodejsorgdocslatestapifshtmlgtlsquo這個模組此模組可以針對檔案做管理監控讀取等行為裡面有許多預設的方法底下是檔案輸出的基本範例底下會有兩個檔案

第一個是靜態 html檔案

ltDOCTYPE htmlgt

lthtml xmlns=rdquohttpwwww3org1999xhtmlrdquo xmllang=rdquozh-CNrdquo lang=rdquozh-CNrdquogt

ltheadgt

ltmeta http-equiv=rdquoContent-Typerdquo content=rdquotexthtml charset=UTF-7rdquo gt

lttitlegtnodejs index html filelttitlegt

ltheadgt

ltbodygt

34 4 Nodejs基礎

Nodejs中文電子書

lth1gtnodejs index html filelth1gt

ltbodygt

lthtmlgt

另一個為 nodejs程式

var fs = require(rdquofsrdquo)

filename = rdquostaticindexhtmlrdquo

encode = rdquoutf8rdquo

fsreadFile(filename encode function(err file)

consolelog(file)

)

一開始直接載入 le system模組 載入名稱為 fs讀取檔案主要使

用的方法為 readFile裡面以三個參數路徑 ( le path) 編碼方式 (encoding)

回應函式 (callback)路徑必須要設定為靜態 html所在位置才能指定到正確的檔案靜態檔案的編碼方式也必須正確這邊使用靜態檔案的編

碼為 utf8如果編碼設定錯誤nodejs讀取出來檔案結果會使用 byte raw格式輸出如果錯誤編碼格式會導致輸出資料為 byte raw

回應函式中裡面會使用兩個變數error為錯誤資訊如果讀取的檔案不存在或者發生錯誤error數值會是 true如果成功讀取資料 error將會是 falsecontent則是檔案內容資料讀取後將會把資料全數丟到 content這個變數當中

最後程式的輸出結果畫面如下

43 nodejs檔案讀取 35

Nodejs中文電子書

44 nodejs http靜態檔案輸出

前面已經了解如何讀取本地端檔案接下來將配合 http伺服器路由讓每個路由都能夠輸出相對應的靜態 html檔案首先新增加幾個靜態 html檔案

ltDOCTYPE htmlgt

lthtml xmlns=rdquohttpwwww3org1999xhtmlrdquo xmllang=rdquozh-CNrdquo lang=rdquozh-CNrdquogt

ltheadgt

ltmeta http-equiv=rdquoContent-Typerdquo content=rdquotexthtml charset=UTF-7rdquo gt

lttitlegtnodejs index html filelttitlegt

ltheadgt

ltbodygt

lth1gtnodejs index html filelth1gt

ltbodygt

lthtmlgt

ltDOCTYPE htmlgt

lthtml xmlns=rdquohttpwwww3org1999xhtmlrdquo xmllang=rdquozh-CNrdquo lang=rdquozh-CNrdquogt

ltheadgt

ltmeta http-equiv=rdquoContent-Typerdquo content=rdquotexthtml charset=UTF-7rdquo gt

lttitlegtnodejs test html filelttitlegt

ltheadgt

ltbodygt

lth1gtnodejs test html filelth1gt

ltbodygt

lthtmlgt

ltDOCTYPE htmlgt

lthtml xmlns=rdquohttpwwww3org1999xhtmlrdquo xmllang=rdquozh-CNrdquo lang=rdquozh-CNrdquogt

ltheadgt

ltmeta http-equiv=rdquoContent-Typerdquo content=rdquotexthtml charset=UTF-7rdquo gt

lttitlegtnodejs static html filelttitlegt

ltheadgt

ltbodygt

lth1gtnodejs static html filelth1gt

ltbodygt

lthtmlgt

36 4 Nodejs基礎

Nodejs中文電子書

準備一個包含基本路由功能的 http伺服器

var server

ip = rdquo127001rdquo

port = 1337

http = require(rsquohttprsquo)

url = require(rsquourlrsquo)

server = httpcreateServer(function (req res)

var path = urlparse(requrl)

)

serverlisten(port ip)

consolelog(rdquoServer running at httprdquo + ip + rdquordquo + port)

加入 le system 模組使用 readFile 的功能將這一段程式放置於

createServer的回應函式中

fsreadFile(filePath encode function(err file)

)

readFile的回應函式裡面加入頁面輸出讓瀏覽器可以正確讀到檔案在這邊我們設定讀取的檔案為 html 靜態檔案所以 Content-type 設定為texthtml讀取到檔案的內容將會正確輸出成 html靜態檔案

fsreadFile(filePath encode function(err file)

reswriteHead(200 rsquoContent-Typersquo rsquotexthtmlrsquo)

reswrite(file)

resend()

)

到這邊為止基本的程式內容都已經完成剩下一些細節的調整首先

路徑上必須做調整目前的靜態檔案全部都放置於 static資料夾底下設定一個變數來記住資料夾位置

接著將瀏覽器發出要求路徑與資料夾組合讀取正確 html靜態檔案使用者有可能會輸入錯誤路徑所以在讀取檔案的時候要加入錯誤處理

同時回應 404伺服器無法正確回應的 http header格式

加入這些細節的修改一個基本的 http 靜態 html輸出伺服器就完成了完整程式碼如下

44 nodejs http靜態檔案輸出 37

Nodejs中文電子書

var server

ip = rdquo127001rdquo

port = 1337

http = require(rsquohttprsquo)

fs = require(rdquofsrdquo)

folderPath = rdquostaticrdquo

url = require(rsquourlrsquo)

path

filePath

encode = rdquoutf8rdquo

server = httpcreateServer(function (req res)

path = urlparse(requrl)

filePath = folderPath + pathpathname

fsreadFile(filePath encode function(err file)

if (err)

reswriteHead(404 rsquoContent-Typersquo rsquotextplainrsquo)

resend()

return

reswriteHead(200 rsquoContent-Typersquo rsquotextapplicationrsquo)

reswrite(file)

resend()

)

)

serverlisten(port ip)

consolelog(rdquoServer running at httprdquo + ip + rdquordquo + port)

45 nodejs http GET資料擷取

http伺服器中除了路由之外另一個最常使用的方法就是擷取 GET資料本單元將會介紹如何透過基本 http伺服器擷取瀏覽器傳來的要求擷取 GET資料

在 http協定中GET參數都是藉由 URL從瀏覽器發出要求送至伺服器

38 4 Nodejs基礎

Nodejs中文電子書

端基本的傳送網址格式可能如下

http127001testsend=1amptest=2

上面這段網址裡面的 GET參數就是 send而這個變數的數值就為 1如果想要在 http伺服器取得 GET資料需要在瀏覽器給予的要求 (request)做處理

首先需要載入 query string這個模組這個模組主要是用來將字串資料

過濾後轉換成javascript物件

qs = require(rsquoquerystringrsquo)

接著在第一階段利用 url模組過濾瀏覽器發出的 URL資料後將回應的物件裡面的 query這個變數是一個字串值資料過濾後如下

send=1amptest=2

透過 query string使用 parse這個方法將資料轉換成 javascript物件就表示 GET的資料已經被伺服器端正式擷取下來

path = urlparse(requrl)

parameter = qsparse(pathquery)

整個 nodejs http GET參數完整擷取程式碼如下

var server

ip = rdquo127001rdquo

port = 1337

http = require(rsquohttprsquo)

qs = require(rsquoquerystringrsquo)

url = require(rsquourlrsquo)

server = httpcreateServer(function (req res)

var path = urlparse(requrl)

parameter = qsparse(pathquery)

consoledir(parameter)

reswriteHead(200 rsquoContent-Typersquo rsquotextplainrsquo)

reswrite(rsquoBrowser test GET parameternrsquo)

resend()

)

45 nodejs http GET資料擷取 39

Nodejs中文電子書

serverlisten(port ip)

consolelog(rdquoServer running at httprdquo + ip + rdquordquo + port)

程式運作之後由瀏覽器輸入要求網址之後nodejs伺服器端回應資料為

Server running at http1270011337

send rsquo1rsquo test rsquo1rsquo

46 本章結語

前面所解說的部份一大部分主要是處理 http伺服器基本問題雖然在某些部分有牽扯到 http 伺服器基本運作原理主要還是希望可以藉由這些基本範例練習 nodejs練習回應函式與語法串接的特點習慣編寫javascript風格程式當然直接這樣開發 nodejs是非常辛苦的接下來在模組實戰開發的部份將會介紹特定的模組一步一步帶領各位從無到有進行

nodejs應用程式開發

40 4 Nodejs基礎

5

NPM套件管理工具

npm全名為 Node Package Manager是 Nodejs的套件(package)管理工具類似 Perl的 ppm或 PHP的 PEAR等安裝 npm後使用npm install

module name指令即可安裝新套件維護管理套件的工作會更加輕鬆

npm可以讓Nodejs的開發者直接利用擴充線上的套件庫(packagesregistry)加速軟體專案的開發npm提供很友善的搜尋功能可以快速找到安裝需要的套件當這些套件發行新版本時npm也可以協助開發者自動更新這些套件

npm不僅可用於安裝新的套件它也支援搜尋列出已安裝模組及更新的功能

51 安裝 NPM

Nodejs在 063版本開始內建 npm讀者安裝的版本若是此版本或更新的版本就可以略過以下安裝說明

若要檢查 npm是否正確安裝可以使用以下的指令

npm -v

41

Nodejs中文電子書

執行結果說明

若 npm正確安裝執行 npm -v將會看到類似 110-2的版本訊息

若讀者安裝的 Nodejs版本比較舊或是有興趣嘗試自己動手安裝 npm工具則可以參考以下的說明

安裝於Windows系統

Nodejs for Windows 於 062 版開始內建 npm使用 nodejsorg 官方提供的安裝程式不需要進一步的設定就可以立即使用 npm指令對於Windows的開發者來說大幅降低環境設定的問題與門檻

除了使用 Nodejs內建的 npm讀者也可以從 npm官方提供的以下網址

httpnpmjsorgdist

這是由 npm提供的 Fancy Windows Install版本請下載壓縮檔(例如npm-110-3zip)並將壓縮檔內容解壓縮至 Nodejs的安裝路徑(例如CProgram Filesnodejs)

解壓縮後在 Nodejs的安裝路徑下應該有以下的檔案及資料夾

bull npmcmd(檔案)

bull node modules(資料夾)

安裝於 Linux系統

Ubuntu Linux的使用者可以加入 NPM Unoffcial PPA這個 repository即可使用 apt-get完成 npm安裝

42 5 NPM套件管理工具

Nodejs中文電子書

Ubuntu Linux使用 apt-get安裝 npm

sudo apt-get install python-software-properties

sudo add-apt-repository ppagias-kay-leenpm

sudo apt-get update

sudo apt-get install npm

npm官方提供的安裝程式 installsh可以適用於大多數的 Linux系統使用這個安裝程式請先確認

1 系統已安裝 curl工具(請使用 curl --version查看版本訊息)

2 已安裝 Nodejs並且 PATH正確設置

3 Nodejs的版本必須大於 04x

以下為 npm提供的安裝指令

curl httpnpmjsorginstallsh | sh

安裝成功會看到如下訊息

installsh安裝成功的訊息

npm10105 homeUSERNAMElocalnodelibnode_modulesnpm

It worked

安裝於Mac OS X

建議採用與 Nodejs相同的方式進行 npm的安裝例如使用MacPorts安裝 Nodejs就同樣使用MacPorts安裝 npm這樣對日後的維護才會更方便容易

使用 MacPorts安裝 npm是本書比較建議的方式它可以讓 npm的安裝移除及更新工作自動化將會幫助開發者節省寶貴時間

51 安裝 NPM 43

Nodejs中文電子書

安裝MacPorts的提示

在MacPorts網站可以取得 OS X系統版本對應的安裝程式(例如 106或 107)httpwwwmacportsorg安裝過程會詢問系統管理者密碼使用預設的選項完成安裝即可安

裝MacPorts之後在終端機執行 port -v將會看到MacPorts的版本訊息

安裝 npm之前先更新MacPorts的套件清單以確保安裝的 npm是最新版本

sudo port -d selfupdate

接著安裝 npm

sudo port install npm

若讀者的 Nodejs並非使用MacPorts安裝則不建議使用MacPorts安裝npm因為 MacPorts會自動檢查並安裝相依套件而 npm相依 nodejs所以MacPorts也會一併將 nodejs套件安裝造成先前讀者使用其它方式安裝的 nodejs被覆蓋

讀者可以先使用 MacPorts安裝 curl(sudo port install curl)

再參考 Linux的 installsh安裝方式即可使用 npm官方提供的安裝程式

NPM安裝後測試

npm是指令列工具(command-line tool)使用時請先打開系統的文字終端機工具

測試 npm安裝與設定是否正確請輸入指令如下

npm -v

或是

npm --version

如果 npm已經正確安裝設定就會顯示版本訊息

44 5 NPM套件管理工具

Nodejs中文電子書

執行結果(範例)

110-2

52 使用 NPM安裝套件

npm目前擁有超過 6000種套件(packages)可以在 npm registry使用關鍵字搜尋套件

httpsearchnpmjsorg

舉例來說在關鍵字欄位輸入「coffee-script」下方的清單就會自動列出包含 coffee-script關鍵字的套件

接著我們回到終端機模式的操作npm的指令工具本身就可以完成套

件搜尋的任務

例如以下的指令同樣可以找出 coffee-script相關套件

npm search coffee-script

以下是搜尋結果的參考畫面

52 使用 NPM安裝套件 45

Nodejs中文電子書

找到需要的套件後(例如 express)即可使用以下指令安裝

npm install coffee-script

值得注意的一點是使用 npm install會將指定的套件安裝在工

作目錄(Working Directory)的 node modules資料夾下

以Windows為例如果執行 npm install的目錄位於

Cproject1

那麼 npm將會自動建立一個 node modules的子目錄(如果不存在)

Cproject1node modules

並且將下載的套件放置於這個子目錄例如

Cproject1node modulescoffee-script

這個設計讓專案可以個別管理相依的套件並且可以在專案佈署或發

行時將這些套件(位於 node modules)一併打包方便其它專案的使用者不必再重新下載套件

這個 npm install的預設安裝模式為 local(本地)只會變更當前專案的資料夾不會影響系統

另一種安裝模式稱為 global(全域)這種模式會將套件安裝到系統資

料夾也就是 npm安裝路徑的 node modules資料夾例如

CProgram Filesnodejsnode modules

46 5 NPM套件管理工具

Nodejs中文電子書

是否要使用全域安裝可以依照套件是否提供新指令來判斷舉例來說express 套件提供 express 這個指令而 coffee-script 則提供 coffee

指令

在 local 安 裝 模 式 中這 些 指 令 的 程 式 檔 案會 被 安 裝 到node modules的 bin這個隱藏資料夾下除非將bin的路徑加入 PATH環境變數否則要執行這些指令將會相當不便

為了方便指令的執行我們可以在 npm install 加上 -g 或 --

global參數啟用 global安裝模式例如

npm install -g coffee-script

npm install -g express

使用 global安裝模式需要注意執行權限的問題若權限不足可能會出現類似以下的錯誤訊息

npm ERR Error EACCES permission denied rsquorsquo

npm ERR

npm ERR Please try running this command again as rootAdministrator

要獲得足夠得執行權限請參考以下說明

bull Windows 7 或 2008 以上在「命令提示字元」的捷徑按右鍵選擇「以系統管理員身分執行」執行 npm指令時就會具有 Administrator身分

bull Mac OS X或 Linux系統可以使用 sudo指令例如

sudo npm install -g express

bull Linux系統可以使用 root權限登入或是以「sudo su -」切換成 root身分(使用 root權限操作系統相當危險因此並不建議使用這種方式)

若加上 -g參數使用 npm install -g coffee-script完成安裝

後就可以在終端機執行 coffee指令例如

coffee -v

52 使用 NPM安裝套件 47

Nodejs中文電子書

執行結果(範例)

CoffeeScript version 120

53 套件的更新及維護

除了前一節說明的 search 及 install 用法npm 還提供其他許多指令(commands)

使用 npm help可以查詢可用的指令

npm help

執行結果(部分)

where ltcommandgt is one of

adduser apihelp author bin bugs c cache completion

config deprecate docs edit explore faq find get

help help-search home i info init install la link

list ll ln login ls outdated owner pack prefix

prune publish r rb rebuild remove restart rm root

run-script s se search set show star start stop

submodule tag test un uninstall unlink unpublish

unstar up update version view whoami

使用 npm help command可以查詢指令的詳細用法例如

npm help list

接下來本節要介紹開發過程常用的 npm指令

使用 list可以列出已安裝套件

npm list

48 5 NPM套件管理工具

Nodejs中文電子書

執行結果(範例)

-- coffee-script120

- express256

- connect185

| -- formidable108

-- mime124

-- mkdirp007

-- qs041

檢視某個套件的詳細資訊例如

npm show express

升級所有套件(如果該套件已發佈更新版本)

npm update

升級指定的套件

npm update express

移除指定的套件

npm uninstall express

54 使用 packagejson

對於正式的 Nodejs專案可以建立一個命名為 packagejson的設

定檔(純文字格式)檔案內容參考範例如下

54 使用 packagejson 49

Nodejs中文電子書

packagejson(範例)

rdquonamerdquo rdquoapplication-namerdquo

rdquoversionrdquo rdquo001rdquo

rdquoprivaterdquo true

rdquodependenciesrdquo

rdquoexpressrdquo rdquo255rdquo

rdquocoffee-scriptrdquo rdquolatestrdquo

rdquomongooserdquo rdquogt= 253rdquo

其中 name與 version依照專案的需求設置

需要注意的是 dependencies的設定它用於指定專案相依的套件名

稱及版本

bull rdquoexpressrdquo rdquo255rdquo

代表此專案相依版本 255的 express套件

bull rdquocoffee-scriptrdquo rdquolatestrdquo

使用最新版的 coffee-script套件(每次更新都會檢查新版)

bull rdquomongooserdquo rdquogt= 253rdquo

使用版本大於 253的 mongoose套件

假設某個套件的新版可能造成專案無法正常運作就必須指定套件的

版本避免專案的程式碼來不及更新以相容新版套件通常在開發初期的

專案需要盡可能維持新套件的相容性(以取得套件的更新或修正)可以

用「gt=」設定最低相容的版本或是使用「latest」設定永遠保持最新

套件

50 5 NPM套件管理工具

6

Express介紹

在前面的 nodejs基礎當中介紹許多許多開設 http的使用方法及介紹以及許多基本的 nodejs基本應用

接下來要介紹一個套件稱為 express [Express](httpexpressjscom)這個套件主要幫忙解決許多 nodejs http server所需要的基本服務讓開發 httpservice變得更為容易不需要像之前需要透過層層模組(module)才有辦法開始編寫自己的程式

這個套件是由 TJ Holowaychuk 製作而成的套件裡面包含基本的路由處理 (route)http資料處理(GETPOSTPUT)另外還與樣板套件(jshtml template engine)搭配同時也可以處理許多複雜化的問題

61 Express安裝

安裝方式十分簡單只要透過之前介紹的 NPM就可以使用簡單的指令安裝指令如下

這邊建議需要將此套件安裝成為全域模組方便日後使用

51

Nodejs中文電子書

62 Express基本操作

express的使用也十分簡單先來建立一個基本的 hello world

var app = require(rsquoexpressrsquo)createServer()

port = 1337

applisten(port)

appget(rsquorsquo function(req res)

ressend(rsquohello worldrsquo)

)

consolelog(rsquostart express servernrsquo)

可以從上面的程式碼發現基本操作與 nodejs http的建立方式沒有太大差異主要差在當我們設定路由時可以直接透過 appget方式設定回應與接受方式

63 Express路由處理

Express對於 http服務上有許多包裝讓開發者使用及設定上更為方便例如有幾個路由設定那我們就統一藉由 appget來處理

Create http server

appget(rsquorsquo function(req res)

ressend(rsquohello worldrsquo)

)

appget(rsquotestrsquo function(req res)

ressend(rsquotest renderrsquo)

)

appget(rsquouserrsquo function(req res)

ressend(rsquouser pagersquo)

)

52 6 Express介紹

Nodejs中文電子書

如上面的程式碼所表示appget可以帶入兩個參數第一個是路徑名稱設定第二個為回應函式 (call back function)回應函式裡面就如同之前的 createServer方法裡面包含 requestresponse兩個物件可供使用使用者就可以透過瀏覽器輸入不同的 url切換到不同的頁面顯示不同的結果

路由設定上也有基本的配對方式讓使用者從瀏覽器輸入的網址可以

是一個變數只要符合型態就可以有對應的頁面產出例如

Create http server

appget(rsquouseridrsquo function(req res)

ressend(rsquouser rsquo + reqparamsid)

)

appget(rsquonumberrsquo function(req res)

ressend(rsquonumber rsquo + reqparamsnumber)

)

裡 面 使 用 到number 從 網 址 輸 入 之 後 就 可 以 直 接 使 用reqparamsnumber 取得所輸入的資料變成 url 參數使用當然前面也是可以加上路徑的設定userid在瀏覽器上路徑必須符合userxxx透

過 reqparamsid就可以取到 xxx這個字串值

另外express參數處理也提供了路由參數配對處理也可以透過正規表示法作為參數設定

var app = require(rsquoexpressrsquo)createServer()

port = 1337

applisten(port)

appget(^ip((d23)((d23))((d23))((d23))) function(req res)

ressend(reqparams)

)

上面程式碼可以發現後面路由設定的型態是正規表示法裡面設定

格式為ip之後必須要加上 ip型態才會符合資料格式同時取得 ip資料已經由正規表示法將資料做分群因此可以取得 ip的四個數字

此程式執行之後可以透過瀏覽器測試輸入網址為 localhost3000ip25525510010可以從頁面獲得資料

63 Express路由處理 53

Nodejs中文電子書

此章節全部範例程式碼如下

overview

author Caesar Chi

blog clonnblogspotcom

version 20120226

create server

var app = require(rsquoexpressrsquo)createServer()

port = 1337

applisten(port)

normal style

appget(rsquorsquo function(req res)

ressend(rsquohello worldrsquo)

)

appget(rsquotestrsquo function(req res)

ressend(rsquotest renderrsquo)

)

parameter style

appget(rsquouseridrsquo function(req res)

ressend(rsquouser rsquo + reqparamsid)

)

appget(rsquonumberrsquo function(req res)

ressend(rsquonumber rsquo + reqparamsnumber)

)

REGX style

appget(^ip((d23)((d23))((d23))) function(req res)

ressend(reqparams)

)

appget(rsquorsquo function(req res)

ressend(rsquoPage not foundrsquo 404)

54 6 Express介紹

Nodejs中文電子書

)

consolelog(rsquostart express servernrsquo)

64 Express middleware

Express 裡面有一個十分好用的應用概念稱為 middleware可以透過middleware做出複雜的效果同時上面也有介紹 next方法參數傳遞就是靠 middleware的概念來傳遞參數讓開發者可以明確的控制程式邏輯

create http server

appuse(expressbodyParser())

appuse(expressmethodOverride())

appuse(expresssession())

上面都是一種 middleware的使用方式透過 appuse方式裡面載入函式執行方法回應函式會包含三個基本參數responserequestnext其中next表示下一個 middleware執行函式同時會自動將預設三個參數繼續帶往下個函式執行底下有個實驗

上面的片段程式執行後開啟瀏覽器連結上 localhost1337會發現伺服器回應結果順序如下

first middle ware

second middle ware

execute middle ware

end middleware function

從上面的結果可以得知剛才設定的 middleware都生效了在 appuse設定的 middleware是所有 url皆會執行方法如果有指定特定方法就可以使用 appget的 middleware設定在 appget函式的第二個參數就可以帶入函式或者是匿名函式只要函式裡面最後會接受 request response next這三個參數同時也有正確指定 next函式的執行時機最後都會執行到最後一個方法當然開發者也可以評估程式邏輯要執行到哪一個階段讓邏輯

可以更為分明

64 Express middleware 55

Nodejs中文電子書

65 Express路由應用

在實際開發上可能會遇到需要使用參數等方式混和變數一起使用

express裡面提供了一個很棒的處理方法 appall這個方式可以先採用基本路由配對再將設定為每個不同的處理方式開發者可以透過這個方式簡

化自己的程式邏輯

overview

author

version 20120226

create server

var app = require(rsquoexpressrsquo)createServer()

port = 1337

users = [

name rsquoClonnrsquo

name rsquoChirsquo

]

applisten(port)

appall(rsquouseridoprsquo function(req res next)

requser = users[reqparamsid]

if (requser)

next()

else

next(new Error(rsquocannot find user rsquo + reqparamsid))

)

appget(rsquouseridrsquo function(req res)

ressend(rsquoviewing rsquo + requsername)

)

appget(rsquouserideditrsquo function(req res)

ressend(rsquoediting rsquo + requsername)

)

56 6 Express介紹

Nodejs中文電子書

appget(rsquouseriddeletersquo function(req res)

ressend(rsquodeleting rsquo + requsername)

)

appget(rsquorsquo function(req res)

ressend(rsquoPage not foundrsquo 404)

)

consolelog(rsquostart express servernrsquo)

內部宣告一組預設的使用者分別給予名稱設定藉由 appall這個方法可以先將路由雛形建立再接下來設定 appget的路徑格式只要符合格式就會分配進入對應的方法中像上面的程式當中如果使用者輸入路徑為user0除了執行 appall程式之後執行 next方法就會對應到路徑設定為userid的這個方法當中如果使用者輸入路徑為user0edit就會執行到useridedit的對應方法

66 Express GET應用範例

我們準備一個使用 GET方法傳送資料的表單

ltDOCTYPE htmlgt

lthtmlgt

ltheadgt

ltmeta http-equiv=rdquoContent-Typerdquo content=rdquotexthtml charset=utf8rdquogt

lttitlegtNodejs 菜鳥筆記 (1)lttitlegt

ltlink rel=rdquostylesheetrdquo href=rdquocssstylecssrdquo type=rdquotextcssrdquo media=rdquoallrdquo gt

ltheadgt

ltbodygt

lth1gtNodejs 菜鳥筆記-註冊lth1gt

ltform id=rdquosignuprdquo method=rdquoGETrdquo action=rdquohttplocalhost3000Signuprdquogt

ltlabelgt 使用者名稱ltlabelgtltinput type=rdquotextrdquo id=rdquousernamerdquo name=rdquousernamerdquo gtltbrgt

ltlabelgt 電子郵件ltlabelgtltinput type=rdquotextrdquo id=rdquoemailrdquo name=rdquoemailrdquo gtltbrgt

ltinput type=rdquosubmitrdquo value=rdquo 註冊我的帳號rdquo gtltbrgt

ltformgt

ltbodygt

lthtmlgt

66 Express GET應用範例 57

Nodejs中文電子書

這個表單沒有什麼特別的地方我們只需要看第 9 行form 使用的method是 GET然後 action是rdquohttplocalhost3000Signupldquo等一下我們要來撰寫Signup這個 URL Path的處理程式

處理 Signup行為

我們知道所謂的 GET方法會透過 URL來把表單的值給帶過去以上面的表單來說到時候 URL會以這樣的形式傳遞

httplocalhost3000Signupusername=xxxampemail=xxx

所以要能處理這樣的資料必須有以下功能

bull 解析 URL

bull 辨別動作是 Signup

bull 解析出 username和 email

一旦能取得 username和 email的值程式就能加以應用了

處理 Signup的程式碼雛形

load module

var url = require(rsquourlrsquo)

urlData = urlparse(requrltrue)

action = urlDatapathname

reswriteHead(200 rdquoContent-Typerdquordquotexthtml charset=utf-8rdquo)

if (action === rdquoSignuprdquo)

user = urlDataquery

resend(rdquolth1gtrdquo + userusername + rdquo 歡迎您的加入 lth1gtltpgt 我們已經將會員啟用信寄至rdquo + useremail + rdquoltpgtrdquo)

首先需要加載 url module它是用來協助我們解析 URL的模組接著使用 urlparse方法第一個傳入 url字串變數也就是 requrl另外第二個參數的用意是設為 ture則引進 querystring模組來協助處理預設是 false它影響到的是 urlDataquery設為 true會傳回物件不然就只是一般的字串urlparse會將字串內容整理成一個物件我們把它指定給 urlData

action變數作為記錄 pathname這是我們稍後要來判斷目前網頁的動作是什麼接著先將 html表頭資訊 (Header)準備好再來判斷路徑邏輯如

58 6 Express介紹

Nodejs中文電子書

果是 Signup這個動作就把 urlDataquery裡的資料指定給 user然後輸出userusername和 useremail把使用者從表單註冊的資料顯示於頁面中

最後進行程式測試啟動 nodejs主程式之後開啟瀏覽器就會看到表單填寫完畢按下送出就可以看到結果了

完整 nodejs程式碼如下

var http = require(rsquohttprsquo)

url = require(rsquourlrsquo)

fs = require(rdquofsrdquo)

server

server = httpcreateServer(function (reqres)

var urlData

encode = rdquoutf8rdquo

filePath = rdquoviewexpress_get_example_formhtmlrdquo

action

urlData = urlparse(requrltrue)

action = urlDatapathname

reswriteHead(200 rdquoContent-Typerdquordquotexthtml charset=utf-8rdquo)

if (action === rdquoSignuprdquo)

user = urlDataquery

resend(rdquolth1gtrdquo + userusername + rdquo 歡迎您的加入 lth1gtltpgt 我們已經將會員啟用信寄至rdquo + useremail + rdquoltpgtrdquo)

else

fsreadFile(filePath encode function(err file)

reswrite(file)

resend()

)

)

serverlisten(3000)

consolelog(rsquoServer 跑起來了現在時間是rsquo + new Date())

66 Express GET應用範例 59

Nodejs中文電子書

67 Express POST應用範例

一開始準備基本的 html表單傳送內容以 POST方式form的 action屬性設定為 POST其餘 html內容與前一個範例應用相同

ltDOCTYPE htmlgt

lthtmlgt

ltheadgt

ltmeta http-equiv=rdquoContent-Typerdquo content=rdquotexthtml charset=utf8rdquogt

lttitlegtNodejs 菜鳥筆記 (1)lttitlegt

ltlink rel=rdquostylesheetrdquo href=rdquocssstylecssrdquo type=rdquotextcssrdquo media=rdquoallrdquo gt

ltheadgt

ltbodygt

lth1gtNodejs 菜鳥筆記-註冊lth1gt

ltform id=rdquosignuprdquo method=rdquoPOSTrdquo action=rdquohttplocalhost3000Signuprdquogt

ltlabelgt 使用者名稱ltlabelgtltinput type=rdquotextrdquo id=rdquousernamerdquo name=rdquousernamerdquo gtltbrgt

ltlabelgt 電子郵件ltlabelgtltinput type=rdquotextrdquo id=rdquoemailrdquo name=rdquoemailrdquo gtltbrgt

ltinput type=rdquosubmitrdquo value=rdquo 註冊我的帳號rdquo gtltbrgt

ltformgt

ltbodygt

lthtmlgt

nodejs的程式處理邏輯與前面 GET範例類似部分程式碼如下

qs = require(rsquoquerystringrsquo)

if (action === rdquoSignuprdquo)

formData = rsquorsquo

reqon(rdquodatardquo function (data)

formData += data

)

reqon(rdquoendrdquo function ()

user = qsparse(formData)

resend(rdquolth1gtrdquo + userusername + rdquo 歡迎您的加入 lth1gtltpgt 我們已經將會員啟用信寄至rdquo + useremail + rdquoltpgtrdquo)

)

60 6 Express介紹

Nodejs中文電子書

主要加入了rsquoquerystringrsquo這個moduel方便我們等一下解析由表單 POST回來的資料另外加入一個 formData的變數用來搜集待等一下表單回傳的資料前面的 GET範例我們只從 req拿出 url的資料這次要在利用req身上的事件處理

JavaScript 在訂閱事件時使用 addEventListener而 nodejs 使用的則是on這邊加上了監聽 data的事件會在瀏覽器傳送資料到Web Server時被執行參數是它所接收到的資料型態是字串

接著再增加 end的事件當瀏覽器的請求事件結束時它就會動作

由於瀏覽器使用 POST在上傳資料時會將資料一塊塊地上傳因為我們在監聽 data事件時透過 formData變數將它累加起來lt不過由於我們

上傳的資料很少一次就結束不過如果日後需要傳的是資料比較大的檔

案這個累加動作就很重要

當資料傳完就進到 end 事件中會用到 qsparse 來解析 formDataformData的內容是字串內容是

username=wordsmithampemail=wordsmith40somewhere

而 qsparse可以幫我們把這個 querystring轉成物件的格式也就是

username=wordsmithampemail=wordsmith40somewhere

一旦轉成物件並指定給 user之後其他的事情就和 GET方法時操作的一樣寫 response的表頭將內容回傳並將 userusername和 useremail代入到內容中

修改完成後接著執行 nodejs程式啟動 web server開啟瀏覽器進入表單測試看看POST的方式能否順利運作

完整程式碼如下

var http = require(rsquohttprsquo)

url = require(rsquourlrsquo)

fs = require(rdquofsrdquo)

qs = require(rsquoquerystringrsquo)

server

server = httpcreateServer(function (reqres)

var urlData

67 Express POST應用範例 61

Nodejs中文電子書

encode = rdquoutf8rdquo

filePath = rdquoviewexpress_post_example_formhtmlrdquo

formData

action

urlData = urlparse(requrltrue)

action = urlDatapathname

reswriteHead(200 rdquoContent-Typerdquordquotexthtml charset=utf-8rdquo)

if (action === rdquoSignuprdquo)

formData = rsquorsquo

reqon(rdquodatardquo function (data)

formData += data

)

reqon(rdquoendrdquo function ()

user = qsparse(formData)

resend(rdquolth1gtrdquo + userusername + rdquo 歡迎您的加入 lth1gtltpgt 我們已經將會員啟用信寄至rdquo + useremail + rdquoltpgtrdquo)

)

else

fsreadFile(filePath encode function(err file)

reswrite(file)

resend()

)

)

serverlisten(3000)

consolelog(rsquoServer 跑起來了現在時間是rsquo + new Date())

62 6 Express介紹

Nodejs中文電子書

68 Express AJAX應用範例

在 Nodejs要使用 Ajax傳送資料並且與之互動在接受資料的部份沒有太大的差別client端不是用 GET就是用 POST來傳資料重點在處理完後用 JSON格式回傳當然 Ajax不見得只傳 JSON格式有時是回傳一段 HTML碼不過後者對伺服器來說基本上就和前兩篇沒有差別了所以我們還是以回傳 JSON做為這一回的主題

這一回其實大多數的工作都會落在前端 Ajax上面前端要負責發送與接收資料並在接收資料後撤掉原先發送資料的表單並將取得的資料

改成 HTML格式之後放上頁面

首先先準備 HTML靜態頁面資料

ltDOCTYPE htmlgt

lthtmlgt

ltheadgt

lttitlegtNodejs 菜鳥筆記 (3)lttitlegt

ltlink rel=rdquostylesheetrdquo href=rdquocssstylecssrdquo type=rdquotextcssrdquo media=rdquoallrdquo gt

ltscript src=rdquohttpsajaxgoogleapiscomajaxlibsjquery171jqueryminjsrdquogtltscriptgt

ltheadgt

ltbodygt

lth1gtNodejs 菜鳥筆記-註冊 (用 Ajax)lth1gt

ltform id=rdquosignuprdquo action=rdquoSignuprdquo method=rdquoPOSTrdquogt

ltlabelgt 使用者名稱ltlabelgtltinput type=rdquotextrdquo id=rdquousernamerdquo name=rdquousernamerdquo gtltbrgt

ltlabelgt 電子郵件ltlabelgtltinput type=rdquotextrdquo id=rdquoemailrdquo name=rdquoemailrdquo gtltbrgt

ltinput type=rdquosubmitrdquo value=rdquo 註冊我的帳號rdquo gtltbrgt

ltformgt

ltdiv id=rdquoresultrdquogt

ltdivgt

ltscript type=rdquotextjavascriptrdquogt

$(function()$(rsquosignup input[type=rdquosubmitrdquo]rsquo)click(function(e)

var user =

userusername = $(rdquousernamerdquo)val()useremail = $(rdquoemailrdquo)val()

$post(rdquoSignuprdquouserfunction(data)greet(data)

)

68 Express AJAX應用範例 63

Nodejs中文電子書

epreventDefault()

)

var greet = function(msg)

var resultNode = $(rsquoresultrsquo)greeting = $(rsquolth2gtHirsquo+msgusername+rsquo 你的會員 id 是rsquo+ msgid +rsquo 我們會將會員啟動信寄至rsquo+msgemail+rsquolth2gtrsquo)

$(rsquosignuprsquo)hide()resultNodehtml(rdquordquo)

resultNodeappend(greeting)

)

ltscriptgt

ltbodygt

lthtmlgt

HTML頁面上準備了一個表單用來傳送註冊資料接著直接引用了Google CDN來載入 jQuery用來幫我們處理 Ajax的工作這次要傳送和接收的工作很大的變動都在 HTML頁面上的 JavaScript當中我們要做的事有 (相關 jQuery處理這邊不多做贅述指提起主要功能解說)

bull 用 jQUery取得 submit按鈕綁定它的 click動作

bull 取得表單 username和 email的值存放在 user這個物件中

bull 用 jQuery的 $post方法將 user的資料傳到 Server

bull 一旦成功取得資料後透過 greet這個 function組成回報給 user的訊息

bull 清空原本給使用者填資料的表單

bull 將 Server回傳的 usernameemail和 id這 3個資料組成回應的訊息

bull 將訊息放到原本表單的位置

經過以上的處理後一個 Ajax的表單的基本功能已經完成

接著進行 nodejs主要程式的編輯部分程式碼如下

var fs = require(rdquofsrdquo)

64 6 Express介紹

Nodejs中文電子書

qs = require(rsquoquerystringrsquo)

if (action === rdquoSignuprdquo)

formData = rsquorsquo

reqon(rdquodatardquo function (data)

formData += data

)

reqon(rdquoendrdquo function ()

var msg

user = qsparse(formData)

userid = rdquo123456rdquo

msg = JSONstringify(user)

reswriteHead(200 rdquoContent-Typerdquordquoapplicationjson charset=utf-8rdquordquoContent-Lengthrdquomsglength)

resend(msg)

)

這裡的程式和前面 POST範例基本上大同小異差別在

bull 幫 user的資料加上 id隨意存放一些文字進去讓 Server回傳的資料多於 Client端傳上來的不然會覺得 Server都沒做事

bull 增加了 msg 這個變數存放將 user 物件 JSON 文字化的結果JSONstringify這個轉換函式是 V8引擎所提供的如果你好奇的話

bull 大重點來了我們要告訴 Client端這次回傳的資料格式是 JSON所在 Content-type和 Content-Length要提供給 Client

Server很輕鬆就完成任務了最後進行程式測試啟動 nodejs主程式之後開啟瀏覽器就會看到表單填寫完畢按下送出就可以看到結果了

最後 nodejs本篇範例程式碼如下

var http = require(rsquohttprsquo)

url = require(rsquourlrsquo)

fs = require(rdquofsrdquo)

qs = require(rsquoquerystringrsquo)

server

68 Express AJAX應用範例 65

Nodejs中文電子書

server = httpcreateServer(function (reqres)

var urlData

encode = rdquoutf8rdquo

filePath = rdquoviewexpress_ajax_example_formhtmlrdquo

formData

action

urlData = urlparse(requrltrue)

action = urlDatapathname

if (action === rdquoSignuprdquo)

formData = rsquorsquo

reqon(rdquodatardquo function (data)

formData += data

)

reqon(rdquoendrdquo function ()

var msg

user = qsparse(formData)

userid = rdquo123456rdquo

msg = JSONstringify(user)

reswriteHead(200 rdquoContent-TyperdquordquoapplicationjsonrdquordquoContent-Lengthrdquomsglength)

resend(msg)

)

else

fsreadFile(filePath encode function(err file)

reswriteHead(200 rdquoContent-Typerdquordquotexthtml charset=utf-8rdquo)

reswrite(file)

resend()

)

)

serverlisten(3000)

66 6 Express介紹

Nodejs中文電子書

consolelog(rsquoServer 跑起來了現在時間是rsquo + new Date())

69 原始資料提供

bull [NodeJS初學者筆記 (1)-用 GET傳送資料] (httpithelpithomecomtwquestion10087402)

bull [NodeJS初學者筆記 (2)-用 POST傳送資料] (httpithelpithomecomtwquestion10087489)

bull [NodeJS 初學者筆記 (3)-用 Ajax 傳送資料] (httpithelpithomecomtwquestion10087627)

69 原始資料提供 67

Nodejs中文電子書

68 6 Express介紹

7

CoffeeScript

bull Spine

bull Hem

bull Spineapp

Letrsquos Have a Cup of CoffeeScript

bull es5-shim ECMAScript 5 compatibility shims for legacy JavaScript engines

ESnext

node-iform - A middleware for node-validator httpsgithubcomguileennode-iform

69

Nodejs中文電子書

70 7 CoffeeScript

8

製作一個 Hubot的 Plurk Adapter

81 應用事項提醒

此應用範例以CoffeeScript編寫主要程式架構採用Github釋放的Hubot專案以下有幾個主要注意事項請讀者注意

bull Hubot 一開始的架構除了內建的兩個 Adapter 之外其餘都要以Nodejs的Module方式才能運作

bull Hubot的 binhubot裡面寫著 npm install所以不管你怎麼改原始碼也不能改變第一點的狀況

其實上述都在討論同一件事情NodeJS的模組而且模組不能設定相對路徑之類的來安裝一定要包裝成 npm相符和格式才有辦法正確執行

82 建立 Adapter

首先需要準備兩個檔案

bull packagejson

bull plurkcoffee

71

Nodejs中文電子書

有這兩個就足以變成 NodeJS的模組了在開始 Coding之前先來設定一下 packagejson相依性

rdquonamerdquo rdquohubot-plurkrdquo

rdquoversionrdquo rdquo011rdquo

rdquomainrdquo rdquoplurkrdquo

rdquodependenciesrdquo

rdquohubotrdquo rdquogt=205rdquo

rdquooauthrdquo rdquordquo

rdquocronrdquordquordquo

這邊會使用到NodeJS的 cron模組(Module)而登入 Plurk需要OAuth標準授權才行所以也需要加載 OAuth模組

注意Hubot在讀取非內建模組時會自動在前面加上 hubot-的前置

83 建立 Robot跟 API

本篇應用基本上程式碼大部分參考 Hubot - Twitter Adapter來製作主要差異只有在使用 OAuth這個模組Twitter有 Streaming可用而 Plurk則得用 Comet方式來達到即時讀取

plurkcoffee程式碼

Robot = require(rdquohubotrdquo)robot()

Adapter = require(rdquohubtordquo)adapter()

EventEmitter = require(rdquoeventsrdquo)EventEmitter

oauth = require(rdquooauthrdquo)

cronJob = require(rdquocronrdquo)CronJob

class Plurk exntends Adapter

class PlurkStreaming exnteds EventEmitter

72 8 製作一個 Hubot的 Plurk Adapter

Nodejs中文電子書

先弄個基本架構主要 Class皆參考 Twitter Adapter命名方式接著再將 Plurk這個 Class增加幾個Method基本上只要有 run send reply就夠了而 run用來做初始化的部分

class Plurk entends Adapter

send (plurk id strings⋯) -gt

reply (plurk id strings⋯) -gt

run -gt

看起來有點東西接著來處理主要的 Plurk API結合部分

class PlurkStreaming extends EventEmitter

consuctor (options) -gt

plurk (callback) -gt

觀察河道

getChannel -gt

取得 Comet 網址

reply (plurk id message) -gt

回噗

acceptFriends -gt

接受好友

get (path callback) -gt

GET 請求

post (path body callback)-gt

POST 請求(其實是裝飾)

request (method path body callback)-gt

主要的 OAuth 請求

comet (server callback)-gt

噗浪的 Comet 傳回是 JavaScript Callback 要另外處理後才會變成 JSON

然後把注意力集中到 constructor上先把建構子弄好

constructor (options) -gt

super()

if optionskey and optionssecret and optionstoken and optionstoken secret

key = optionskey

secret = optionssecret

token = optionstoken

token secret = optionstoken secret

83 建立 Robot跟 API 73

Nodejs中文電子書

建立 OAuth 連接

consumer = new oauthOAuth(

rdquohttpwwwplurkcomOAuthrequest tokenrdquo

rdquohttpwwwplurkcomOAuthaccess tokenrdquo

key

secret

rdquo10rdquo

rdquohttpwwwplurkcomOAuthauthorizerdquo

rdquoHMAC-SHA1rdquo

)

domain = rdquowwwplurkcomrdquo

初始化取得 Comet 網址

do getChannel

else

throw new Error(rdquo 參數不足需要 Key Secret Token Token Secretrdquo)

接著來處理 request這個 method

request (method path body callback) -gt

記錄一下這次的 Request

consolelog(rdquohttpdomainpathrdquo)

Callback 這邊先不丟進去要用另一種方式處理

request = consumerget(rdquohttpdomainpathrdquo token token secret null)

requeston rdquoresponserdquo (res) -gt

reson rdquodatardquo (chunk) -gt

parseResponse(chunk+rsquorsquo callback)

reson rdquoendrdquo (data) -gt

consolelog rdquoEnd Request pathrdquo

reson rdquoerrorrdquo (data) -gt

consolelog rdquoError rdquo + data

requestend()

處理資料

parseResponse = (data callback) -gt

if datalength gt 0

用 TryCatch 避免處理 JSON 出錯導致整個中斷

try

callback null JSONparse(data)

catch err

74 8 製作一個 Hubot的 Plurk Adapter

Nodejs中文電子書

consolelog(rdquoError Parse JSONrdquo + data err)

繼續執行

callback null data ||

大致上就是這樣上面程式的架構已經將整個 Hubot Plurk Adapter完成因為在測試時竟然因為噗浪 Lag而沒讀到完整的 Comet資料然後造成程式異常為了避免這個問題發生因此需要加上為了完美呈現需要再

加上 Comet的處理所以要使用到 EventEmitter的功能

comet (server callback) -gt

在 Callback 裡面會找不到自身所以設定區域變數

self =

記錄一下這次的 Request

consolelog(rdquo[Comet] serverrdquo)

Callback 這邊先不丟進去要用另一種方式處理

request = consumerget(rdquohttpdomainpathrdquo token token secret null)

requeston rdquoresponserdquo (res) -gt

reson rdquodatardquo (chunk) -gt

parseResponse(chunk+rsquorsquo callback)

reson rdquoendrdquo (data) -gt

consolelog rdquoEnd Request pathrdquo

請求結束發出事件通知可以進行下一次請求

selfemit rdquonextPlurkrdquo

reson rdquoerrorrdquo (data) -gt

consolelog rdquoError rdquo + data

requestend()

處理資料

parseResponse = (data callback) -gt

if datalength gt 0

用 trycatch 避免失敗中斷

try

去掉 JavaScript 的 Callback

data = datamatch(CometChannelscriptCallback((+))s)

jsonData = rdquordquo

if data

83 建立 Robot跟 API 75

Nodejs中文電子書

jsonData = JSONparse(data[1])

else

如果沒有任何 Match 嘗試直接 parse

jsonData = JSONparse(data)

catch err

consolelog(rdquo[Comet] Errorrdquo data err)

用 TryCatch 避免處理 JSON 出錯導致整個中斷

try

只傳入 json 的 data 部分

callback null jsonDatadata

catch err

consolelog(rdquo[Comet]Error Parse JSONrdquo + data err)

繼續執行

callback null data ||

後面的 get跟 post就簡單多了

get (path callback) -gt

request(rdquoGETrdquo path null callback)

post (path body callback) -gt

request(rdquoPOSTrdquo path body callback)

接著處理取的 Comet網址的 getChannel

getChannel -gt

self =

get rdquoAPPRealtimegetUserChannelrdquo (error data) -gt

if error

檢查是否有 comet server

if datacomet server

selfchannel = datacomet server

如果沒有 Channel Ready 就嘗試連接會失敗

selfemit(rsquochannel readyrsquo)

那麼先來處理 Plurk Adaper好處理的部份

send (plruk id strings⋯)-gt

跟 Reply 一樣直接交給 reply 做

reply plurk id strings⋯

76 8 製作一個 Hubot的 Plurk Adapter

Nodejs中文電子書

reply (plurk id strings⋯) -gt

stringsforEach (message) =gt

botreply(plruk id message)

接著把 run處理好就可以上線運作摟

run -gt

self =

options =

key processenvHUBOT PLURK KEY

secret processenvHUBOT PLURK SECRET

token processenvHUBOT PLURK TOKEN

token secret processenvHUBOT PLURK TOKEN SECRET

創建剛剛的 API

bot = new PlurkStreaming(options)

依照 Twitter 的 new RobotTextMessage 會沒有反應所以參考 hubot-minecraft 的方式

r = robotconstructor

處理噗浪河道訊息

doPlurk = (data)-gt

檢查是否為回噗

if dataresponse

datacontent raw = dataresponsecontent raw

datauser id = dataresponseuser id

確定有噗浪 ID 跟訊息

if dataplurk id and datacontent raw

selfreceive new rTextMessage(dataplurk id datacontent raw)

取得 Comet Server 完成開始第一次 Comet 連接

boton rdquochannel readyrdquo () -gt

botplurk selfdoPlurk

上一次 Comet 完成繼續 Polling

boton rdquonextPlurkrdquo ()-gt

botplurk selfdoPlurk

定時接受好友邀請

do botacceptFriends

bot = bot

83 建立 Robot跟 API 77

Nodejs中文電子書

終於完成 AdapterHubot專案裡面的 scripts資料夾內是互動部分不需要像 Adapter如此大費周章處理只新增檔案並且設計好對白之後就會回噗了我開發用的機器人在此大家可以去跟他玩玩[httpplurkcomelct9620 bot](httpplurkcomelct9620 bot)

84 原始資料提供

bull [製 作 一 個 Hubot 的 噗 浪 Adapter](http revo-skillfrosttw blog20120318create-a-hubot-plurk-adapter)

78 8 製作一個 Hubot的 Plurk Adapter

9

Nodejs好用工具介紹

91 logio

Real-time log monitoring in your browser

httplogioorg

nodejs (gt=0412 lt=0611)

npm config set unsafe-perm true

npm install -g --prefix=usrlocal logio

logio server start

httplocalhost8998

79

Nodejs中文電子書

80 9 Nodejs好用工具介紹

10

精選文章

101 Express

bull 使用 NodeJS + Express從 GETPOST Request取值 [邀稿中]

bull link2 [邀稿中]

bull link3 [邀稿中]

81

Nodejs中文電子書

82 10 精選文章

11

參考資源

111 Nodejs書籍

bull Programming Nodejs April 2012 (est) Travis Swicegood Pragmatic Bookshelf

bull e Little Book on CoffeeScript January 2012 Alex MacCaw OrsquoReilly Media

bull Node for Front-End Developers January 2012 Garann Means OrsquoReilly Media

bull Building Hypermedia APIs with HTML5 and Node November 2011 MikeAmundsen OrsquoReilly Media

bull Node Web Development August 2011 David Herron PacktPub

bull CoffeeScript Accelerated JavaScript Development July 2011 Trevor BurnhamPragmatic Bookshelf

bull Getting Started with GEO CouchDB and Nodejs July 2011 Mick omp-son OrsquoReilly Media

bull What Is Node July 2011 Brett McLaughlin OrsquoReilly Media

bull Node Up and Running May 2011 Tom Hughes-Croucher Mike WilsonOrsquoReilly Media

83

Nodejs中文電子書

bull Hands-on Nodejs e Nodejs introduction and API reference May 2011 Pe-dro Teixeira LeanPub

bull e Node Beginner Book Manuel Kiessling

bull Mastering Nodejs visionmedia

112 Nodejs影音教學

bull e Node Sessions e Best of OSCON 2011 August 2011 OrsquoReilly Media(Video)

bull Tom Hughes-Croucher on Node March 2011 OrsquoReilly Media (Video)

113 Nodejs教學網站

bull Node Tuts

114 Nodejs課程

bull Introduction To Nodejs Van Nguyen CodeLesson

84 11 參考資源

Page 25: Nodejs Wiki

Nodejs中文電子書

bull httphowtonodeorgcontrol- ow-part-iii

bull httpblogmixunet20110202essential-node-js-patterns-and-snippets

這幾篇都是非常經典的 NodeJSJavascript流程控制好文章(阿mixu是在介紹一些 pattern時提到這方面的主題)不過我還是用幾個簡單的程式介紹一下做法跟概念

並發與等待

下面的程式參考了 mixu文章中的做法

var wait = function(callbacks done) consolelog(lsquowait startrsquo) var counter = call-backslength var results = [] var next = function(result) 接收函數執行結果並判斷是否結束執行 resultspush(result) if(ndashcounter == 0) done(results)如果結束執行就把所有執行結果傳給指定的 callback處理 for(var i = 0 i lt callbackslength i++) 依次呼叫所有要執行的函數 callbacks[i](next) consolelog(lsquowait endrsquo)

wait( [ function(next) setTimeout(function() consolelog(lsquodone arsquo) var result =500 next(result) 500) function(next) setTimeout(function() con-solelog(lsquodone brsquo) var result = 1000 next(result) 1000) function(next)setTimeout(function() consolelog(lsquodone crsquo) var result = 1500 next(1500) 1500) ] function(results) var ret = 0 i=0 for( iltresultslength i++) ret+= results[i] consolelog(lsquodone all result lsquo+ret)

)

執行結果wait start wait end done a done b done c done all result 3000

可以看出來其實 wait並不是真的等到所有函數執行完才結束執行而是在所有傳給他的函數執行完畢後(不論同步非同步)才執行處理結

果的函數(也就是 done())

不過這樣的寫法還不夠實用因為沒辦法實際讓函數可以等待執行

完畢又能當作事件處理函數來實際使用上面參考到的 Tim Caswell的文

26 流程控制 19

Nodejs中文電子書

章裡面有一種解法不過還需要額外包裝(在他的例子中)NodeJS核心的 fs物件把一些函數(例如 readFile)用 Currying處理類似像這樣

var fs = require(lsquofsrsquo) var readFile = function(path)

return function(callback errback)

fsreadFile(path function(err data)

if(err) errback()

else callback(data)

)

其他部份可以參考 Tim Caswell的文章他的 Doparallel跟上面的 wait差不多意思這裡只提示一下他沒說到的地方

另外一種做法是去修飾一下 callback當他作為事件處理函數執行後再用 cps的方式取得結果

ltscriptgt function Wait(fns done)

var count = 0 var results = [] thisgetCallback = function(index)

count++ return (function(waitback)

return function() var i=0args=[]for(iltargumentslengthi++)

argspush(arguments[i])

argspush(waitback) fns[index]apply(thisargs)

)(function(result) resultspush(result) if(ndashcount == 0)

20 2 JavaScript與 NodeJS

Nodejs中文電子書

done(results)

)

var a = new Wait(

[ function(waitback) consolelog(lsquodone arsquo) var result = 500 wait-back(result) function(waitback) consolelog(lsquodone brsquo) var result =1000 waitback(result) function(waitback) consolelog(lsquodone crsquo) varresult = 1500 waitback(result) ] function(results) var ret = 0 i=0for( iltresultslength i++) ret += results[i] consolelog(lsquodone allresult lsquo+ret)

) var callbacks = [agetCallback(0)agetCallback(1)agetCallback(0)agetCallback(2)]一次取出要使用的 callbacks避免結果提早送出 setTimeout(callbacks[0]500) setTimeout(callbacks[1] 1000) setTimeout(callbacks[2] 1500) setTime-out(callbacks[3] 2000) 當所有取出的 callbacks執行完畢就呼叫 done()來處理結果 ltscriptgt

執行結果

done a done b done a done c done all result 3500

上面只是一些小實驗更成熟的作品是 Tim Caswell 的 stephttpsgithubcomcreationixstep

如果希望真正使用同步的方式寫非同步則需要使用 Promisejs這一類的 library來轉換非同步函數不過他結構比較複雜 XD(見仁見智不過有些人認為 Promise有點過頭了)httpblogsmsdncombrbucktonarchive20110815promise-js-2-0-promise-framework-for-javascriptaspx

如果想不透過其他 Library做轉換又能直接用同步方式執行非同步函數大概就要使用一些需要額外 compile原始程式碼的方法了例如 BrunoJouhier的 streamlinejshttpsgithubcomSagestreamlinejs

26 流程控制 21

Nodejs中文電子書

循序執行

循序執行可以協助把非常深的巢狀 callback結構攤平例如用這樣的簡單模組來做(serialjs)

moduleexports = function(funs) var c = 0 if(isArrayOfFunctions(funs))

throw(lsquoArgument type was not matched Should be array of func-tionsrsquo)

return function()

var args = Arrayprototypeslicecall(arguments 0) if((cgt=funslength))

c++ return funs[c-1]apply(this args)

function isArrayOfFunctions(f ) if(typeof f == lsquoobjectrsquo) return false if(flength)return false if(fconcat) return false if(fsplice) return false var i = 0 for(iltflength i++)

if(typeof f[i] == lsquofunctionrsquo) return false

return true

簡單的測試範例(testSerialjs)使用 fs 模組確定某個 path 是檔案然後讀取印出檔案內容這樣會用到兩層的 callback所以測試中有使用serial的版本與 nested callbacks的版本做對照

var serial = require(lsquoserialrsquo) fs = require(lsquofsrsquo) path = lsquodclientjsrsquo cb = serial([ func-tion(err data)

if(err)

if(dataisFile) fsreadFile(path cb)

22 2 JavaScript與 NodeJS

Nodejs中文電子書

else consolelog(err)

function(err data)

if(err) consolelog(lsquo[ attened by searial]rsquo) con-solelog(datatoString(lsquoutf8rsquo))

else consolelog(err)

]) fsstat(path cb)

fsstat(path function(err data) 第一層 callback if(err)

if(dataisFile)

fsreadFile(path function(err data) 第二層 callback if(err)

consolelog(lsquo[nested callbacks]rsquo) con-solelog(datatoString(lsquoutf8rsquo))

else consolelog(err)

)

else consolelog(err)

)

關鍵在於這些 callback的執行是有順序性的所以利用 serial返回的一個函數 cb來取代這些 callback然後在 cb中控制每次會循序呼叫的函數

26 流程控制 23

Nodejs中文電子書

就可以把巢狀的 callback攤平成循序的 function陣列(就是傳給 serial函數的參數)

測試中的dclientjs 是一個簡單的 dnode 測試程式放在跟 testSerialjs同一個目錄

var dnode = require(lsquodnodersquo)

dnodeconnect(8000 lsquolocalhostrsquo function(remote)

remoterestart(function(str) consolelog(str) processexit()

)

)

執行測試程式後出現結果

[ attened by searial] var dnode = require(lsquodnodersquo)

dnodeconnect(8000 lsquolocalhostrsquo function(remote)

remoterestart(function(str) consolelog(str) processexit()

)

)

[nested callbacks] var dnode = require(lsquodnodersquo)

dnodeconnect(8000 lsquolocalhostrsquo function(remote)

remoterestart(function(str) consolelog(str) processexit()

)

)

對照起來看兩種寫法的結果其實是一樣的但是利用 serialjs巢狀的 callback結構就會消失

不過這樣也只限於順序單純的狀況如果函數執行的順序比較複雜

(不只是一直線)還是需要用功能更完整的流程控制模組比較好例如

httpsgithubcomcaolanasync

24 2 JavaScript與 NodeJS

3

Nodejs安裝與設定

本篇將講解如何在各個不同 OS建立 NodeJS環境目前 NodeJS 048版本環境架設方式需依賴 Linux指令才可編譯完成當然在不同作業系統中也已經有 NodeJS package可以直接使用指令快速架設以下各不同作業系統解說如何安裝 NodeJS

31 Ubuntu Linux

更新推薦使用 nvm

git clone gitgithubcomcreationixnvmgit ~nvm

echo rdquo ~nvmnvmshrdquo gtgt ~bashrc

nvm install v0614

nvm alias default v0614

以 上 可 參 考 http dreamerslabcom blog tw how-to-setup-a-node-js-development-environment-on-ubuntu-11-04

使用 APT套件管理工具是常見的方法以下是使用社群提供的 PPA安裝方式

sudo apt-get install python-software-properties

sudo add-apt-repository ppachris-leanodejs-devel

25

Nodejs中文電子書

sudo apt-get update

sudo apt-get install nodejs

32 Other Linux

Linux很適合作為 NodeJS的伺服器作業系統及開發環境安裝前請先確認以下套件已正確安裝

bull curl (wget)用來下載檔案的工具

bull git先進的版本控制工具

bull g++ GNU C++軟體編譯工具

bull make GNU軟體專案建置工具

安裝指令如下如設有權限問題請在指令前面加上 sudo

git clone httpsgithubcomjoyentnodegit

cd node

git checkout v067

configure

make

sudo make install

接著測試 nodeJS是否正常執行

node --version

出現版本訊息即表示安裝成功

33 Windows

nodeJS 在 v060 版本之後開始正式支援 windows native直接使用nodeexe 就可以執行程式支援性完全與 linux 相同更棒的部份就是不需經過編譯經過下載之後簡單設定完成立即開發 node程式

26 3 Nodejs安裝與設定

Nodejs中文電子書

下載 nodejs安裝檔案 lthttpnodejsorgdownloadgt

如此完成 windows native nodeexe安裝接著可以進入 command line執行測試在 command line輸入rdquonoderdquo會出現 nodejs版本訊息畫面表示安裝完成

33 Windows 27

Nodejs中文電子書

28 3 Nodejs安裝與設定

4

Nodejs基礎

前篇文章已經由介紹安裝至設定都有完整介紹nodeJS 內部除了javascript常用的函式 (function)物件 (object)之外也有許多不同的自訂物件nodeJS預設建立這些物件為核心物件是為了要讓開發流程更為這些資料在官方文件已經具有許多具體說明接下來將會介紹在開發 nodeJS程式時常見的物件特性與使用方法

41 nodejs http伺服器建立

在 lsquonodejs官方網站 lthttpnodejsorggtlsquo裡面有舉一個最簡單的 HTTP伺服器建立一開始初步就是建立一個伺服器平台讓 nodejs可以與瀏覽器互相行為每種語言一開始的程式建立都是以 Hello world開始最初也從 Hello world帶各位進入 nodejs的世界

輸入以下程式碼儲存檔案為 node basic http hello worldjs

var server

ip = rdquo127001rdquo

port = 1337

http = require(rsquohttprsquo)

server = httpcreateServer(function (req res)

29

Nodejs中文電子書

reswriteHead(200 rsquoContent-Typersquo rsquotextplainrsquo)

resend(rsquoHello Worldnrsquo)

)

serverlisten(port ip)

consolelog(rdquoServer running at httprdquo + ip + rdquordquo + port)

程式碼解講一開始需要有幾個基本的變數

bull ip 機器本身的 ip位置因為使用本地端因此設定為 127001

bull port 需要開通的阜號通常設定為 http port 80因範例不希望與基本 port相衝隨意設定為 1337

在 nodejs 的程式中有許多預設的模組可以使用因此需要使用require方法將模組引入在這邊我們需要使用 http這個模組因此將 http載入Http模組裡面內建有許多方法可以使用這邊採用 createServer創建一個基本的 http伺服器再將 http伺服器給予一個 server變數裡面的回呼函式 (call back function)可以載入 http伺服器的資料與回應方法 (requestresponse)在程式裡面就可以看到我們直接回應給瀏覽器端所需的 Header回應內容

reswriteHead(200 rsquoContent-Typersquo rsquotextplainrsquo)

resend(rsquoHello Worldnrsquo)

Http伺服器需要設定 port ip在最後需要設定 Http監聽需要使用到listen事件監聽所有 Http伺服器行為

httplisten(port ip)

所有事情都完成之後需要確認伺服器正確執行因此使用 console在javascript裡就有這個原生物件console所印出的資料都會顯示於 nodejs伺服器頁面這邊印出的資料並不會傳送到使用者頁面上之後許多除壞

(debug)都會用到 console物件

consolelog(rdquoServer running at httprdquo + ip + rdquordquo + port)

30 4 Nodejs基礎

Nodejs中文電子書

42 nodejs http路徑建立

前面已經介紹如何建立一個簡單的 http伺服器接下來本章節將會介紹如何處理伺服器路徑 (rout)問題在 http的協定下所有從瀏覽器發出的要求 (request)都需要經過處理路徑上的建立也是如此

路徑就是指伺服器 ip位置或者是網域名稱之後對於伺服器給予的要求修改剛才的 hello world檔案修改如下

server = httpcreateServer(function (req res)

consolelog(requrl)

reswriteHead(200 rsquoContent-Typersquo rsquotextplainrsquo)

resend(rsquohello worldnrsquo)

)

重新啟動 nodejs程式後在瀏覽器端測試一下路徑行為結果如下圖

當在瀏覽器輸入 http1270011337test 在伺服器端會收到兩個要求一個是我們輸入的test要求另外一個則是faviconicotest的路徑要求http伺服器本身需要經過程式設定才有辦法回應給瀏覽器端所需要的回應在伺服器中所有的路徑要求都是需要被解析才有辦法取得資料從

42 nodejs http路徑建立 31

Nodejs中文電子書

上面解說可以了解到在 nodejs當中所有的路徑都需要經過設定未經過設定的路由會讓瀏覽器無法取得任何資料導致錯誤頁面的發生底下將會解

說如何設定路由同時避免發生錯誤情形先前 nodejs程式需要增加一些修改才能讓使用者透過瀏覽器在不同路徑時有不同的結果根據剛才

的程式做如下的修改

var server

ip = rdquo127001rdquo

port = 1337

http = require(rsquohttprsquo)

url = require(rsquourlrsquo)

path

server = httpcreateServer(function (req res)

path = urlparse(requrl)

reswriteHead(200 rsquoContent-Typersquo rsquotextplainrsquo)

switch (pathpathname)

case rdquoindexrdquo

resend(rsquoI am indexnrsquo)

break

case rdquotestrdquo

resend(rsquothis is test pagenrsquo)

break

default

resend(rsquodefault pagenrsquo)

break

)

serverlisten(port ip)

consolelog(rdquoServer running at httprdquo + ip + rdquordquo + port)

程式做了片段的修改首先載入 url模組另外增加一個 path變數url模組就跟如同他的命名一般專門處理 url字串處理裡面提供了許多方法來解決路徑上的問題因為從瀏覽器發出的要求路徑可能會帶有多種需求

或者 GET參數組合等因此我們需要將路徑單純化取用路徑部分的資料即可例如使用者可能會送出 http1270011337testsend=1如果直接

32 4 Nodejs基礎

Nodejs中文電子書

信任 requrl就會收到結果為testsend=1所以需要透過 url模組的方法將路徑資料過濾

在這邊使用 urlparse的方法裡面帶入網址格式資料會回傳路徑資料為了後需方便使用將回傳的資料設定到 path變數當中在回傳的路徑資料裡面包含資訊如下圖

這邊只需要使用單純的路徑要求直接取用 pathpathname就可以達到我們的目的

最後要做路徑的判別在不同的路徑可以指定不同的輸出在範例中

有三個可能結果第一個從瀏覽器輸入index就會顯示 index結果test 就會呈現出 test頁面最後如果都不符合預期的輸入會直接顯示 default的頁面最後的預防可以讓瀏覽器不會出現非預期結果讓程式的可靠性提昇

底下為測試結果

42 nodejs http路徑建立 33

Nodejs中文電子書

43 nodejs檔案讀取

前面已經介紹如何使用路由(rount)做出不同的回應實際應用只有在瀏覽器只有輸出幾個文字資料總是不夠的在本章節中將介紹如何使

用檔案讀取輸出檔案資料讓使用者在前端瀏覽器也可以讀取到完整的

html css javascript檔案輸出

檔案管理最重要的部分就是 lsquoFile system lthttpnodejsorgdocslatestapifshtmlgtlsquo這個模組此模組可以針對檔案做管理監控讀取等行為裡面有許多預設的方法底下是檔案輸出的基本範例底下會有兩個檔案

第一個是靜態 html檔案

ltDOCTYPE htmlgt

lthtml xmlns=rdquohttpwwww3org1999xhtmlrdquo xmllang=rdquozh-CNrdquo lang=rdquozh-CNrdquogt

ltheadgt

ltmeta http-equiv=rdquoContent-Typerdquo content=rdquotexthtml charset=UTF-7rdquo gt

lttitlegtnodejs index html filelttitlegt

ltheadgt

ltbodygt

34 4 Nodejs基礎

Nodejs中文電子書

lth1gtnodejs index html filelth1gt

ltbodygt

lthtmlgt

另一個為 nodejs程式

var fs = require(rdquofsrdquo)

filename = rdquostaticindexhtmlrdquo

encode = rdquoutf8rdquo

fsreadFile(filename encode function(err file)

consolelog(file)

)

一開始直接載入 le system模組 載入名稱為 fs讀取檔案主要使

用的方法為 readFile裡面以三個參數路徑 ( le path) 編碼方式 (encoding)

回應函式 (callback)路徑必須要設定為靜態 html所在位置才能指定到正確的檔案靜態檔案的編碼方式也必須正確這邊使用靜態檔案的編

碼為 utf8如果編碼設定錯誤nodejs讀取出來檔案結果會使用 byte raw格式輸出如果錯誤編碼格式會導致輸出資料為 byte raw

回應函式中裡面會使用兩個變數error為錯誤資訊如果讀取的檔案不存在或者發生錯誤error數值會是 true如果成功讀取資料 error將會是 falsecontent則是檔案內容資料讀取後將會把資料全數丟到 content這個變數當中

最後程式的輸出結果畫面如下

43 nodejs檔案讀取 35

Nodejs中文電子書

44 nodejs http靜態檔案輸出

前面已經了解如何讀取本地端檔案接下來將配合 http伺服器路由讓每個路由都能夠輸出相對應的靜態 html檔案首先新增加幾個靜態 html檔案

ltDOCTYPE htmlgt

lthtml xmlns=rdquohttpwwww3org1999xhtmlrdquo xmllang=rdquozh-CNrdquo lang=rdquozh-CNrdquogt

ltheadgt

ltmeta http-equiv=rdquoContent-Typerdquo content=rdquotexthtml charset=UTF-7rdquo gt

lttitlegtnodejs index html filelttitlegt

ltheadgt

ltbodygt

lth1gtnodejs index html filelth1gt

ltbodygt

lthtmlgt

ltDOCTYPE htmlgt

lthtml xmlns=rdquohttpwwww3org1999xhtmlrdquo xmllang=rdquozh-CNrdquo lang=rdquozh-CNrdquogt

ltheadgt

ltmeta http-equiv=rdquoContent-Typerdquo content=rdquotexthtml charset=UTF-7rdquo gt

lttitlegtnodejs test html filelttitlegt

ltheadgt

ltbodygt

lth1gtnodejs test html filelth1gt

ltbodygt

lthtmlgt

ltDOCTYPE htmlgt

lthtml xmlns=rdquohttpwwww3org1999xhtmlrdquo xmllang=rdquozh-CNrdquo lang=rdquozh-CNrdquogt

ltheadgt

ltmeta http-equiv=rdquoContent-Typerdquo content=rdquotexthtml charset=UTF-7rdquo gt

lttitlegtnodejs static html filelttitlegt

ltheadgt

ltbodygt

lth1gtnodejs static html filelth1gt

ltbodygt

lthtmlgt

36 4 Nodejs基礎

Nodejs中文電子書

準備一個包含基本路由功能的 http伺服器

var server

ip = rdquo127001rdquo

port = 1337

http = require(rsquohttprsquo)

url = require(rsquourlrsquo)

server = httpcreateServer(function (req res)

var path = urlparse(requrl)

)

serverlisten(port ip)

consolelog(rdquoServer running at httprdquo + ip + rdquordquo + port)

加入 le system 模組使用 readFile 的功能將這一段程式放置於

createServer的回應函式中

fsreadFile(filePath encode function(err file)

)

readFile的回應函式裡面加入頁面輸出讓瀏覽器可以正確讀到檔案在這邊我們設定讀取的檔案為 html 靜態檔案所以 Content-type 設定為texthtml讀取到檔案的內容將會正確輸出成 html靜態檔案

fsreadFile(filePath encode function(err file)

reswriteHead(200 rsquoContent-Typersquo rsquotexthtmlrsquo)

reswrite(file)

resend()

)

到這邊為止基本的程式內容都已經完成剩下一些細節的調整首先

路徑上必須做調整目前的靜態檔案全部都放置於 static資料夾底下設定一個變數來記住資料夾位置

接著將瀏覽器發出要求路徑與資料夾組合讀取正確 html靜態檔案使用者有可能會輸入錯誤路徑所以在讀取檔案的時候要加入錯誤處理

同時回應 404伺服器無法正確回應的 http header格式

加入這些細節的修改一個基本的 http 靜態 html輸出伺服器就完成了完整程式碼如下

44 nodejs http靜態檔案輸出 37

Nodejs中文電子書

var server

ip = rdquo127001rdquo

port = 1337

http = require(rsquohttprsquo)

fs = require(rdquofsrdquo)

folderPath = rdquostaticrdquo

url = require(rsquourlrsquo)

path

filePath

encode = rdquoutf8rdquo

server = httpcreateServer(function (req res)

path = urlparse(requrl)

filePath = folderPath + pathpathname

fsreadFile(filePath encode function(err file)

if (err)

reswriteHead(404 rsquoContent-Typersquo rsquotextplainrsquo)

resend()

return

reswriteHead(200 rsquoContent-Typersquo rsquotextapplicationrsquo)

reswrite(file)

resend()

)

)

serverlisten(port ip)

consolelog(rdquoServer running at httprdquo + ip + rdquordquo + port)

45 nodejs http GET資料擷取

http伺服器中除了路由之外另一個最常使用的方法就是擷取 GET資料本單元將會介紹如何透過基本 http伺服器擷取瀏覽器傳來的要求擷取 GET資料

在 http協定中GET參數都是藉由 URL從瀏覽器發出要求送至伺服器

38 4 Nodejs基礎

Nodejs中文電子書

端基本的傳送網址格式可能如下

http127001testsend=1amptest=2

上面這段網址裡面的 GET參數就是 send而這個變數的數值就為 1如果想要在 http伺服器取得 GET資料需要在瀏覽器給予的要求 (request)做處理

首先需要載入 query string這個模組這個模組主要是用來將字串資料

過濾後轉換成javascript物件

qs = require(rsquoquerystringrsquo)

接著在第一階段利用 url模組過濾瀏覽器發出的 URL資料後將回應的物件裡面的 query這個變數是一個字串值資料過濾後如下

send=1amptest=2

透過 query string使用 parse這個方法將資料轉換成 javascript物件就表示 GET的資料已經被伺服器端正式擷取下來

path = urlparse(requrl)

parameter = qsparse(pathquery)

整個 nodejs http GET參數完整擷取程式碼如下

var server

ip = rdquo127001rdquo

port = 1337

http = require(rsquohttprsquo)

qs = require(rsquoquerystringrsquo)

url = require(rsquourlrsquo)

server = httpcreateServer(function (req res)

var path = urlparse(requrl)

parameter = qsparse(pathquery)

consoledir(parameter)

reswriteHead(200 rsquoContent-Typersquo rsquotextplainrsquo)

reswrite(rsquoBrowser test GET parameternrsquo)

resend()

)

45 nodejs http GET資料擷取 39

Nodejs中文電子書

serverlisten(port ip)

consolelog(rdquoServer running at httprdquo + ip + rdquordquo + port)

程式運作之後由瀏覽器輸入要求網址之後nodejs伺服器端回應資料為

Server running at http1270011337

send rsquo1rsquo test rsquo1rsquo

46 本章結語

前面所解說的部份一大部分主要是處理 http伺服器基本問題雖然在某些部分有牽扯到 http 伺服器基本運作原理主要還是希望可以藉由這些基本範例練習 nodejs練習回應函式與語法串接的特點習慣編寫javascript風格程式當然直接這樣開發 nodejs是非常辛苦的接下來在模組實戰開發的部份將會介紹特定的模組一步一步帶領各位從無到有進行

nodejs應用程式開發

40 4 Nodejs基礎

5

NPM套件管理工具

npm全名為 Node Package Manager是 Nodejs的套件(package)管理工具類似 Perl的 ppm或 PHP的 PEAR等安裝 npm後使用npm install

module name指令即可安裝新套件維護管理套件的工作會更加輕鬆

npm可以讓Nodejs的開發者直接利用擴充線上的套件庫(packagesregistry)加速軟體專案的開發npm提供很友善的搜尋功能可以快速找到安裝需要的套件當這些套件發行新版本時npm也可以協助開發者自動更新這些套件

npm不僅可用於安裝新的套件它也支援搜尋列出已安裝模組及更新的功能

51 安裝 NPM

Nodejs在 063版本開始內建 npm讀者安裝的版本若是此版本或更新的版本就可以略過以下安裝說明

若要檢查 npm是否正確安裝可以使用以下的指令

npm -v

41

Nodejs中文電子書

執行結果說明

若 npm正確安裝執行 npm -v將會看到類似 110-2的版本訊息

若讀者安裝的 Nodejs版本比較舊或是有興趣嘗試自己動手安裝 npm工具則可以參考以下的說明

安裝於Windows系統

Nodejs for Windows 於 062 版開始內建 npm使用 nodejsorg 官方提供的安裝程式不需要進一步的設定就可以立即使用 npm指令對於Windows的開發者來說大幅降低環境設定的問題與門檻

除了使用 Nodejs內建的 npm讀者也可以從 npm官方提供的以下網址

httpnpmjsorgdist

這是由 npm提供的 Fancy Windows Install版本請下載壓縮檔(例如npm-110-3zip)並將壓縮檔內容解壓縮至 Nodejs的安裝路徑(例如CProgram Filesnodejs)

解壓縮後在 Nodejs的安裝路徑下應該有以下的檔案及資料夾

bull npmcmd(檔案)

bull node modules(資料夾)

安裝於 Linux系統

Ubuntu Linux的使用者可以加入 NPM Unoffcial PPA這個 repository即可使用 apt-get完成 npm安裝

42 5 NPM套件管理工具

Nodejs中文電子書

Ubuntu Linux使用 apt-get安裝 npm

sudo apt-get install python-software-properties

sudo add-apt-repository ppagias-kay-leenpm

sudo apt-get update

sudo apt-get install npm

npm官方提供的安裝程式 installsh可以適用於大多數的 Linux系統使用這個安裝程式請先確認

1 系統已安裝 curl工具(請使用 curl --version查看版本訊息)

2 已安裝 Nodejs並且 PATH正確設置

3 Nodejs的版本必須大於 04x

以下為 npm提供的安裝指令

curl httpnpmjsorginstallsh | sh

安裝成功會看到如下訊息

installsh安裝成功的訊息

npm10105 homeUSERNAMElocalnodelibnode_modulesnpm

It worked

安裝於Mac OS X

建議採用與 Nodejs相同的方式進行 npm的安裝例如使用MacPorts安裝 Nodejs就同樣使用MacPorts安裝 npm這樣對日後的維護才會更方便容易

使用 MacPorts安裝 npm是本書比較建議的方式它可以讓 npm的安裝移除及更新工作自動化將會幫助開發者節省寶貴時間

51 安裝 NPM 43

Nodejs中文電子書

安裝MacPorts的提示

在MacPorts網站可以取得 OS X系統版本對應的安裝程式(例如 106或 107)httpwwwmacportsorg安裝過程會詢問系統管理者密碼使用預設的選項完成安裝即可安

裝MacPorts之後在終端機執行 port -v將會看到MacPorts的版本訊息

安裝 npm之前先更新MacPorts的套件清單以確保安裝的 npm是最新版本

sudo port -d selfupdate

接著安裝 npm

sudo port install npm

若讀者的 Nodejs並非使用MacPorts安裝則不建議使用MacPorts安裝npm因為 MacPorts會自動檢查並安裝相依套件而 npm相依 nodejs所以MacPorts也會一併將 nodejs套件安裝造成先前讀者使用其它方式安裝的 nodejs被覆蓋

讀者可以先使用 MacPorts安裝 curl(sudo port install curl)

再參考 Linux的 installsh安裝方式即可使用 npm官方提供的安裝程式

NPM安裝後測試

npm是指令列工具(command-line tool)使用時請先打開系統的文字終端機工具

測試 npm安裝與設定是否正確請輸入指令如下

npm -v

或是

npm --version

如果 npm已經正確安裝設定就會顯示版本訊息

44 5 NPM套件管理工具

Nodejs中文電子書

執行結果(範例)

110-2

52 使用 NPM安裝套件

npm目前擁有超過 6000種套件(packages)可以在 npm registry使用關鍵字搜尋套件

httpsearchnpmjsorg

舉例來說在關鍵字欄位輸入「coffee-script」下方的清單就會自動列出包含 coffee-script關鍵字的套件

接著我們回到終端機模式的操作npm的指令工具本身就可以完成套

件搜尋的任務

例如以下的指令同樣可以找出 coffee-script相關套件

npm search coffee-script

以下是搜尋結果的參考畫面

52 使用 NPM安裝套件 45

Nodejs中文電子書

找到需要的套件後(例如 express)即可使用以下指令安裝

npm install coffee-script

值得注意的一點是使用 npm install會將指定的套件安裝在工

作目錄(Working Directory)的 node modules資料夾下

以Windows為例如果執行 npm install的目錄位於

Cproject1

那麼 npm將會自動建立一個 node modules的子目錄(如果不存在)

Cproject1node modules

並且將下載的套件放置於這個子目錄例如

Cproject1node modulescoffee-script

這個設計讓專案可以個別管理相依的套件並且可以在專案佈署或發

行時將這些套件(位於 node modules)一併打包方便其它專案的使用者不必再重新下載套件

這個 npm install的預設安裝模式為 local(本地)只會變更當前專案的資料夾不會影響系統

另一種安裝模式稱為 global(全域)這種模式會將套件安裝到系統資

料夾也就是 npm安裝路徑的 node modules資料夾例如

CProgram Filesnodejsnode modules

46 5 NPM套件管理工具

Nodejs中文電子書

是否要使用全域安裝可以依照套件是否提供新指令來判斷舉例來說express 套件提供 express 這個指令而 coffee-script 則提供 coffee

指令

在 local 安 裝 模 式 中這 些 指 令 的 程 式 檔 案會 被 安 裝 到node modules的 bin這個隱藏資料夾下除非將bin的路徑加入 PATH環境變數否則要執行這些指令將會相當不便

為了方便指令的執行我們可以在 npm install 加上 -g 或 --

global參數啟用 global安裝模式例如

npm install -g coffee-script

npm install -g express

使用 global安裝模式需要注意執行權限的問題若權限不足可能會出現類似以下的錯誤訊息

npm ERR Error EACCES permission denied rsquorsquo

npm ERR

npm ERR Please try running this command again as rootAdministrator

要獲得足夠得執行權限請參考以下說明

bull Windows 7 或 2008 以上在「命令提示字元」的捷徑按右鍵選擇「以系統管理員身分執行」執行 npm指令時就會具有 Administrator身分

bull Mac OS X或 Linux系統可以使用 sudo指令例如

sudo npm install -g express

bull Linux系統可以使用 root權限登入或是以「sudo su -」切換成 root身分(使用 root權限操作系統相當危險因此並不建議使用這種方式)

若加上 -g參數使用 npm install -g coffee-script完成安裝

後就可以在終端機執行 coffee指令例如

coffee -v

52 使用 NPM安裝套件 47

Nodejs中文電子書

執行結果(範例)

CoffeeScript version 120

53 套件的更新及維護

除了前一節說明的 search 及 install 用法npm 還提供其他許多指令(commands)

使用 npm help可以查詢可用的指令

npm help

執行結果(部分)

where ltcommandgt is one of

adduser apihelp author bin bugs c cache completion

config deprecate docs edit explore faq find get

help help-search home i info init install la link

list ll ln login ls outdated owner pack prefix

prune publish r rb rebuild remove restart rm root

run-script s se search set show star start stop

submodule tag test un uninstall unlink unpublish

unstar up update version view whoami

使用 npm help command可以查詢指令的詳細用法例如

npm help list

接下來本節要介紹開發過程常用的 npm指令

使用 list可以列出已安裝套件

npm list

48 5 NPM套件管理工具

Nodejs中文電子書

執行結果(範例)

-- coffee-script120

- express256

- connect185

| -- formidable108

-- mime124

-- mkdirp007

-- qs041

檢視某個套件的詳細資訊例如

npm show express

升級所有套件(如果該套件已發佈更新版本)

npm update

升級指定的套件

npm update express

移除指定的套件

npm uninstall express

54 使用 packagejson

對於正式的 Nodejs專案可以建立一個命名為 packagejson的設

定檔(純文字格式)檔案內容參考範例如下

54 使用 packagejson 49

Nodejs中文電子書

packagejson(範例)

rdquonamerdquo rdquoapplication-namerdquo

rdquoversionrdquo rdquo001rdquo

rdquoprivaterdquo true

rdquodependenciesrdquo

rdquoexpressrdquo rdquo255rdquo

rdquocoffee-scriptrdquo rdquolatestrdquo

rdquomongooserdquo rdquogt= 253rdquo

其中 name與 version依照專案的需求設置

需要注意的是 dependencies的設定它用於指定專案相依的套件名

稱及版本

bull rdquoexpressrdquo rdquo255rdquo

代表此專案相依版本 255的 express套件

bull rdquocoffee-scriptrdquo rdquolatestrdquo

使用最新版的 coffee-script套件(每次更新都會檢查新版)

bull rdquomongooserdquo rdquogt= 253rdquo

使用版本大於 253的 mongoose套件

假設某個套件的新版可能造成專案無法正常運作就必須指定套件的

版本避免專案的程式碼來不及更新以相容新版套件通常在開發初期的

專案需要盡可能維持新套件的相容性(以取得套件的更新或修正)可以

用「gt=」設定最低相容的版本或是使用「latest」設定永遠保持最新

套件

50 5 NPM套件管理工具

6

Express介紹

在前面的 nodejs基礎當中介紹許多許多開設 http的使用方法及介紹以及許多基本的 nodejs基本應用

接下來要介紹一個套件稱為 express [Express](httpexpressjscom)這個套件主要幫忙解決許多 nodejs http server所需要的基本服務讓開發 httpservice變得更為容易不需要像之前需要透過層層模組(module)才有辦法開始編寫自己的程式

這個套件是由 TJ Holowaychuk 製作而成的套件裡面包含基本的路由處理 (route)http資料處理(GETPOSTPUT)另外還與樣板套件(jshtml template engine)搭配同時也可以處理許多複雜化的問題

61 Express安裝

安裝方式十分簡單只要透過之前介紹的 NPM就可以使用簡單的指令安裝指令如下

這邊建議需要將此套件安裝成為全域模組方便日後使用

51

Nodejs中文電子書

62 Express基本操作

express的使用也十分簡單先來建立一個基本的 hello world

var app = require(rsquoexpressrsquo)createServer()

port = 1337

applisten(port)

appget(rsquorsquo function(req res)

ressend(rsquohello worldrsquo)

)

consolelog(rsquostart express servernrsquo)

可以從上面的程式碼發現基本操作與 nodejs http的建立方式沒有太大差異主要差在當我們設定路由時可以直接透過 appget方式設定回應與接受方式

63 Express路由處理

Express對於 http服務上有許多包裝讓開發者使用及設定上更為方便例如有幾個路由設定那我們就統一藉由 appget來處理

Create http server

appget(rsquorsquo function(req res)

ressend(rsquohello worldrsquo)

)

appget(rsquotestrsquo function(req res)

ressend(rsquotest renderrsquo)

)

appget(rsquouserrsquo function(req res)

ressend(rsquouser pagersquo)

)

52 6 Express介紹

Nodejs中文電子書

如上面的程式碼所表示appget可以帶入兩個參數第一個是路徑名稱設定第二個為回應函式 (call back function)回應函式裡面就如同之前的 createServer方法裡面包含 requestresponse兩個物件可供使用使用者就可以透過瀏覽器輸入不同的 url切換到不同的頁面顯示不同的結果

路由設定上也有基本的配對方式讓使用者從瀏覽器輸入的網址可以

是一個變數只要符合型態就可以有對應的頁面產出例如

Create http server

appget(rsquouseridrsquo function(req res)

ressend(rsquouser rsquo + reqparamsid)

)

appget(rsquonumberrsquo function(req res)

ressend(rsquonumber rsquo + reqparamsnumber)

)

裡 面 使 用 到number 從 網 址 輸 入 之 後 就 可 以 直 接 使 用reqparamsnumber 取得所輸入的資料變成 url 參數使用當然前面也是可以加上路徑的設定userid在瀏覽器上路徑必須符合userxxx透

過 reqparamsid就可以取到 xxx這個字串值

另外express參數處理也提供了路由參數配對處理也可以透過正規表示法作為參數設定

var app = require(rsquoexpressrsquo)createServer()

port = 1337

applisten(port)

appget(^ip((d23)((d23))((d23))((d23))) function(req res)

ressend(reqparams)

)

上面程式碼可以發現後面路由設定的型態是正規表示法裡面設定

格式為ip之後必須要加上 ip型態才會符合資料格式同時取得 ip資料已經由正規表示法將資料做分群因此可以取得 ip的四個數字

此程式執行之後可以透過瀏覽器測試輸入網址為 localhost3000ip25525510010可以從頁面獲得資料

63 Express路由處理 53

Nodejs中文電子書

此章節全部範例程式碼如下

overview

author Caesar Chi

blog clonnblogspotcom

version 20120226

create server

var app = require(rsquoexpressrsquo)createServer()

port = 1337

applisten(port)

normal style

appget(rsquorsquo function(req res)

ressend(rsquohello worldrsquo)

)

appget(rsquotestrsquo function(req res)

ressend(rsquotest renderrsquo)

)

parameter style

appget(rsquouseridrsquo function(req res)

ressend(rsquouser rsquo + reqparamsid)

)

appget(rsquonumberrsquo function(req res)

ressend(rsquonumber rsquo + reqparamsnumber)

)

REGX style

appget(^ip((d23)((d23))((d23))) function(req res)

ressend(reqparams)

)

appget(rsquorsquo function(req res)

ressend(rsquoPage not foundrsquo 404)

54 6 Express介紹

Nodejs中文電子書

)

consolelog(rsquostart express servernrsquo)

64 Express middleware

Express 裡面有一個十分好用的應用概念稱為 middleware可以透過middleware做出複雜的效果同時上面也有介紹 next方法參數傳遞就是靠 middleware的概念來傳遞參數讓開發者可以明確的控制程式邏輯

create http server

appuse(expressbodyParser())

appuse(expressmethodOverride())

appuse(expresssession())

上面都是一種 middleware的使用方式透過 appuse方式裡面載入函式執行方法回應函式會包含三個基本參數responserequestnext其中next表示下一個 middleware執行函式同時會自動將預設三個參數繼續帶往下個函式執行底下有個實驗

上面的片段程式執行後開啟瀏覽器連結上 localhost1337會發現伺服器回應結果順序如下

first middle ware

second middle ware

execute middle ware

end middleware function

從上面的結果可以得知剛才設定的 middleware都生效了在 appuse設定的 middleware是所有 url皆會執行方法如果有指定特定方法就可以使用 appget的 middleware設定在 appget函式的第二個參數就可以帶入函式或者是匿名函式只要函式裡面最後會接受 request response next這三個參數同時也有正確指定 next函式的執行時機最後都會執行到最後一個方法當然開發者也可以評估程式邏輯要執行到哪一個階段讓邏輯

可以更為分明

64 Express middleware 55

Nodejs中文電子書

65 Express路由應用

在實際開發上可能會遇到需要使用參數等方式混和變數一起使用

express裡面提供了一個很棒的處理方法 appall這個方式可以先採用基本路由配對再將設定為每個不同的處理方式開發者可以透過這個方式簡

化自己的程式邏輯

overview

author

version 20120226

create server

var app = require(rsquoexpressrsquo)createServer()

port = 1337

users = [

name rsquoClonnrsquo

name rsquoChirsquo

]

applisten(port)

appall(rsquouseridoprsquo function(req res next)

requser = users[reqparamsid]

if (requser)

next()

else

next(new Error(rsquocannot find user rsquo + reqparamsid))

)

appget(rsquouseridrsquo function(req res)

ressend(rsquoviewing rsquo + requsername)

)

appget(rsquouserideditrsquo function(req res)

ressend(rsquoediting rsquo + requsername)

)

56 6 Express介紹

Nodejs中文電子書

appget(rsquouseriddeletersquo function(req res)

ressend(rsquodeleting rsquo + requsername)

)

appget(rsquorsquo function(req res)

ressend(rsquoPage not foundrsquo 404)

)

consolelog(rsquostart express servernrsquo)

內部宣告一組預設的使用者分別給予名稱設定藉由 appall這個方法可以先將路由雛形建立再接下來設定 appget的路徑格式只要符合格式就會分配進入對應的方法中像上面的程式當中如果使用者輸入路徑為user0除了執行 appall程式之後執行 next方法就會對應到路徑設定為userid的這個方法當中如果使用者輸入路徑為user0edit就會執行到useridedit的對應方法

66 Express GET應用範例

我們準備一個使用 GET方法傳送資料的表單

ltDOCTYPE htmlgt

lthtmlgt

ltheadgt

ltmeta http-equiv=rdquoContent-Typerdquo content=rdquotexthtml charset=utf8rdquogt

lttitlegtNodejs 菜鳥筆記 (1)lttitlegt

ltlink rel=rdquostylesheetrdquo href=rdquocssstylecssrdquo type=rdquotextcssrdquo media=rdquoallrdquo gt

ltheadgt

ltbodygt

lth1gtNodejs 菜鳥筆記-註冊lth1gt

ltform id=rdquosignuprdquo method=rdquoGETrdquo action=rdquohttplocalhost3000Signuprdquogt

ltlabelgt 使用者名稱ltlabelgtltinput type=rdquotextrdquo id=rdquousernamerdquo name=rdquousernamerdquo gtltbrgt

ltlabelgt 電子郵件ltlabelgtltinput type=rdquotextrdquo id=rdquoemailrdquo name=rdquoemailrdquo gtltbrgt

ltinput type=rdquosubmitrdquo value=rdquo 註冊我的帳號rdquo gtltbrgt

ltformgt

ltbodygt

lthtmlgt

66 Express GET應用範例 57

Nodejs中文電子書

這個表單沒有什麼特別的地方我們只需要看第 9 行form 使用的method是 GET然後 action是rdquohttplocalhost3000Signupldquo等一下我們要來撰寫Signup這個 URL Path的處理程式

處理 Signup行為

我們知道所謂的 GET方法會透過 URL來把表單的值給帶過去以上面的表單來說到時候 URL會以這樣的形式傳遞

httplocalhost3000Signupusername=xxxampemail=xxx

所以要能處理這樣的資料必須有以下功能

bull 解析 URL

bull 辨別動作是 Signup

bull 解析出 username和 email

一旦能取得 username和 email的值程式就能加以應用了

處理 Signup的程式碼雛形

load module

var url = require(rsquourlrsquo)

urlData = urlparse(requrltrue)

action = urlDatapathname

reswriteHead(200 rdquoContent-Typerdquordquotexthtml charset=utf-8rdquo)

if (action === rdquoSignuprdquo)

user = urlDataquery

resend(rdquolth1gtrdquo + userusername + rdquo 歡迎您的加入 lth1gtltpgt 我們已經將會員啟用信寄至rdquo + useremail + rdquoltpgtrdquo)

首先需要加載 url module它是用來協助我們解析 URL的模組接著使用 urlparse方法第一個傳入 url字串變數也就是 requrl另外第二個參數的用意是設為 ture則引進 querystring模組來協助處理預設是 false它影響到的是 urlDataquery設為 true會傳回物件不然就只是一般的字串urlparse會將字串內容整理成一個物件我們把它指定給 urlData

action變數作為記錄 pathname這是我們稍後要來判斷目前網頁的動作是什麼接著先將 html表頭資訊 (Header)準備好再來判斷路徑邏輯如

58 6 Express介紹

Nodejs中文電子書

果是 Signup這個動作就把 urlDataquery裡的資料指定給 user然後輸出userusername和 useremail把使用者從表單註冊的資料顯示於頁面中

最後進行程式測試啟動 nodejs主程式之後開啟瀏覽器就會看到表單填寫完畢按下送出就可以看到結果了

完整 nodejs程式碼如下

var http = require(rsquohttprsquo)

url = require(rsquourlrsquo)

fs = require(rdquofsrdquo)

server

server = httpcreateServer(function (reqres)

var urlData

encode = rdquoutf8rdquo

filePath = rdquoviewexpress_get_example_formhtmlrdquo

action

urlData = urlparse(requrltrue)

action = urlDatapathname

reswriteHead(200 rdquoContent-Typerdquordquotexthtml charset=utf-8rdquo)

if (action === rdquoSignuprdquo)

user = urlDataquery

resend(rdquolth1gtrdquo + userusername + rdquo 歡迎您的加入 lth1gtltpgt 我們已經將會員啟用信寄至rdquo + useremail + rdquoltpgtrdquo)

else

fsreadFile(filePath encode function(err file)

reswrite(file)

resend()

)

)

serverlisten(3000)

consolelog(rsquoServer 跑起來了現在時間是rsquo + new Date())

66 Express GET應用範例 59

Nodejs中文電子書

67 Express POST應用範例

一開始準備基本的 html表單傳送內容以 POST方式form的 action屬性設定為 POST其餘 html內容與前一個範例應用相同

ltDOCTYPE htmlgt

lthtmlgt

ltheadgt

ltmeta http-equiv=rdquoContent-Typerdquo content=rdquotexthtml charset=utf8rdquogt

lttitlegtNodejs 菜鳥筆記 (1)lttitlegt

ltlink rel=rdquostylesheetrdquo href=rdquocssstylecssrdquo type=rdquotextcssrdquo media=rdquoallrdquo gt

ltheadgt

ltbodygt

lth1gtNodejs 菜鳥筆記-註冊lth1gt

ltform id=rdquosignuprdquo method=rdquoPOSTrdquo action=rdquohttplocalhost3000Signuprdquogt

ltlabelgt 使用者名稱ltlabelgtltinput type=rdquotextrdquo id=rdquousernamerdquo name=rdquousernamerdquo gtltbrgt

ltlabelgt 電子郵件ltlabelgtltinput type=rdquotextrdquo id=rdquoemailrdquo name=rdquoemailrdquo gtltbrgt

ltinput type=rdquosubmitrdquo value=rdquo 註冊我的帳號rdquo gtltbrgt

ltformgt

ltbodygt

lthtmlgt

nodejs的程式處理邏輯與前面 GET範例類似部分程式碼如下

qs = require(rsquoquerystringrsquo)

if (action === rdquoSignuprdquo)

formData = rsquorsquo

reqon(rdquodatardquo function (data)

formData += data

)

reqon(rdquoendrdquo function ()

user = qsparse(formData)

resend(rdquolth1gtrdquo + userusername + rdquo 歡迎您的加入 lth1gtltpgt 我們已經將會員啟用信寄至rdquo + useremail + rdquoltpgtrdquo)

)

60 6 Express介紹

Nodejs中文電子書

主要加入了rsquoquerystringrsquo這個moduel方便我們等一下解析由表單 POST回來的資料另外加入一個 formData的變數用來搜集待等一下表單回傳的資料前面的 GET範例我們只從 req拿出 url的資料這次要在利用req身上的事件處理

JavaScript 在訂閱事件時使用 addEventListener而 nodejs 使用的則是on這邊加上了監聽 data的事件會在瀏覽器傳送資料到Web Server時被執行參數是它所接收到的資料型態是字串

接著再增加 end的事件當瀏覽器的請求事件結束時它就會動作

由於瀏覽器使用 POST在上傳資料時會將資料一塊塊地上傳因為我們在監聽 data事件時透過 formData變數將它累加起來lt不過由於我們

上傳的資料很少一次就結束不過如果日後需要傳的是資料比較大的檔

案這個累加動作就很重要

當資料傳完就進到 end 事件中會用到 qsparse 來解析 formDataformData的內容是字串內容是

username=wordsmithampemail=wordsmith40somewhere

而 qsparse可以幫我們把這個 querystring轉成物件的格式也就是

username=wordsmithampemail=wordsmith40somewhere

一旦轉成物件並指定給 user之後其他的事情就和 GET方法時操作的一樣寫 response的表頭將內容回傳並將 userusername和 useremail代入到內容中

修改完成後接著執行 nodejs程式啟動 web server開啟瀏覽器進入表單測試看看POST的方式能否順利運作

完整程式碼如下

var http = require(rsquohttprsquo)

url = require(rsquourlrsquo)

fs = require(rdquofsrdquo)

qs = require(rsquoquerystringrsquo)

server

server = httpcreateServer(function (reqres)

var urlData

67 Express POST應用範例 61

Nodejs中文電子書

encode = rdquoutf8rdquo

filePath = rdquoviewexpress_post_example_formhtmlrdquo

formData

action

urlData = urlparse(requrltrue)

action = urlDatapathname

reswriteHead(200 rdquoContent-Typerdquordquotexthtml charset=utf-8rdquo)

if (action === rdquoSignuprdquo)

formData = rsquorsquo

reqon(rdquodatardquo function (data)

formData += data

)

reqon(rdquoendrdquo function ()

user = qsparse(formData)

resend(rdquolth1gtrdquo + userusername + rdquo 歡迎您的加入 lth1gtltpgt 我們已經將會員啟用信寄至rdquo + useremail + rdquoltpgtrdquo)

)

else

fsreadFile(filePath encode function(err file)

reswrite(file)

resend()

)

)

serverlisten(3000)

consolelog(rsquoServer 跑起來了現在時間是rsquo + new Date())

62 6 Express介紹

Nodejs中文電子書

68 Express AJAX應用範例

在 Nodejs要使用 Ajax傳送資料並且與之互動在接受資料的部份沒有太大的差別client端不是用 GET就是用 POST來傳資料重點在處理完後用 JSON格式回傳當然 Ajax不見得只傳 JSON格式有時是回傳一段 HTML碼不過後者對伺服器來說基本上就和前兩篇沒有差別了所以我們還是以回傳 JSON做為這一回的主題

這一回其實大多數的工作都會落在前端 Ajax上面前端要負責發送與接收資料並在接收資料後撤掉原先發送資料的表單並將取得的資料

改成 HTML格式之後放上頁面

首先先準備 HTML靜態頁面資料

ltDOCTYPE htmlgt

lthtmlgt

ltheadgt

lttitlegtNodejs 菜鳥筆記 (3)lttitlegt

ltlink rel=rdquostylesheetrdquo href=rdquocssstylecssrdquo type=rdquotextcssrdquo media=rdquoallrdquo gt

ltscript src=rdquohttpsajaxgoogleapiscomajaxlibsjquery171jqueryminjsrdquogtltscriptgt

ltheadgt

ltbodygt

lth1gtNodejs 菜鳥筆記-註冊 (用 Ajax)lth1gt

ltform id=rdquosignuprdquo action=rdquoSignuprdquo method=rdquoPOSTrdquogt

ltlabelgt 使用者名稱ltlabelgtltinput type=rdquotextrdquo id=rdquousernamerdquo name=rdquousernamerdquo gtltbrgt

ltlabelgt 電子郵件ltlabelgtltinput type=rdquotextrdquo id=rdquoemailrdquo name=rdquoemailrdquo gtltbrgt

ltinput type=rdquosubmitrdquo value=rdquo 註冊我的帳號rdquo gtltbrgt

ltformgt

ltdiv id=rdquoresultrdquogt

ltdivgt

ltscript type=rdquotextjavascriptrdquogt

$(function()$(rsquosignup input[type=rdquosubmitrdquo]rsquo)click(function(e)

var user =

userusername = $(rdquousernamerdquo)val()useremail = $(rdquoemailrdquo)val()

$post(rdquoSignuprdquouserfunction(data)greet(data)

)

68 Express AJAX應用範例 63

Nodejs中文電子書

epreventDefault()

)

var greet = function(msg)

var resultNode = $(rsquoresultrsquo)greeting = $(rsquolth2gtHirsquo+msgusername+rsquo 你的會員 id 是rsquo+ msgid +rsquo 我們會將會員啟動信寄至rsquo+msgemail+rsquolth2gtrsquo)

$(rsquosignuprsquo)hide()resultNodehtml(rdquordquo)

resultNodeappend(greeting)

)

ltscriptgt

ltbodygt

lthtmlgt

HTML頁面上準備了一個表單用來傳送註冊資料接著直接引用了Google CDN來載入 jQuery用來幫我們處理 Ajax的工作這次要傳送和接收的工作很大的變動都在 HTML頁面上的 JavaScript當中我們要做的事有 (相關 jQuery處理這邊不多做贅述指提起主要功能解說)

bull 用 jQUery取得 submit按鈕綁定它的 click動作

bull 取得表單 username和 email的值存放在 user這個物件中

bull 用 jQuery的 $post方法將 user的資料傳到 Server

bull 一旦成功取得資料後透過 greet這個 function組成回報給 user的訊息

bull 清空原本給使用者填資料的表單

bull 將 Server回傳的 usernameemail和 id這 3個資料組成回應的訊息

bull 將訊息放到原本表單的位置

經過以上的處理後一個 Ajax的表單的基本功能已經完成

接著進行 nodejs主要程式的編輯部分程式碼如下

var fs = require(rdquofsrdquo)

64 6 Express介紹

Nodejs中文電子書

qs = require(rsquoquerystringrsquo)

if (action === rdquoSignuprdquo)

formData = rsquorsquo

reqon(rdquodatardquo function (data)

formData += data

)

reqon(rdquoendrdquo function ()

var msg

user = qsparse(formData)

userid = rdquo123456rdquo

msg = JSONstringify(user)

reswriteHead(200 rdquoContent-Typerdquordquoapplicationjson charset=utf-8rdquordquoContent-Lengthrdquomsglength)

resend(msg)

)

這裡的程式和前面 POST範例基本上大同小異差別在

bull 幫 user的資料加上 id隨意存放一些文字進去讓 Server回傳的資料多於 Client端傳上來的不然會覺得 Server都沒做事

bull 增加了 msg 這個變數存放將 user 物件 JSON 文字化的結果JSONstringify這個轉換函式是 V8引擎所提供的如果你好奇的話

bull 大重點來了我們要告訴 Client端這次回傳的資料格式是 JSON所在 Content-type和 Content-Length要提供給 Client

Server很輕鬆就完成任務了最後進行程式測試啟動 nodejs主程式之後開啟瀏覽器就會看到表單填寫完畢按下送出就可以看到結果了

最後 nodejs本篇範例程式碼如下

var http = require(rsquohttprsquo)

url = require(rsquourlrsquo)

fs = require(rdquofsrdquo)

qs = require(rsquoquerystringrsquo)

server

68 Express AJAX應用範例 65

Nodejs中文電子書

server = httpcreateServer(function (reqres)

var urlData

encode = rdquoutf8rdquo

filePath = rdquoviewexpress_ajax_example_formhtmlrdquo

formData

action

urlData = urlparse(requrltrue)

action = urlDatapathname

if (action === rdquoSignuprdquo)

formData = rsquorsquo

reqon(rdquodatardquo function (data)

formData += data

)

reqon(rdquoendrdquo function ()

var msg

user = qsparse(formData)

userid = rdquo123456rdquo

msg = JSONstringify(user)

reswriteHead(200 rdquoContent-TyperdquordquoapplicationjsonrdquordquoContent-Lengthrdquomsglength)

resend(msg)

)

else

fsreadFile(filePath encode function(err file)

reswriteHead(200 rdquoContent-Typerdquordquotexthtml charset=utf-8rdquo)

reswrite(file)

resend()

)

)

serverlisten(3000)

66 6 Express介紹

Nodejs中文電子書

consolelog(rsquoServer 跑起來了現在時間是rsquo + new Date())

69 原始資料提供

bull [NodeJS初學者筆記 (1)-用 GET傳送資料] (httpithelpithomecomtwquestion10087402)

bull [NodeJS初學者筆記 (2)-用 POST傳送資料] (httpithelpithomecomtwquestion10087489)

bull [NodeJS 初學者筆記 (3)-用 Ajax 傳送資料] (httpithelpithomecomtwquestion10087627)

69 原始資料提供 67

Nodejs中文電子書

68 6 Express介紹

7

CoffeeScript

bull Spine

bull Hem

bull Spineapp

Letrsquos Have a Cup of CoffeeScript

bull es5-shim ECMAScript 5 compatibility shims for legacy JavaScript engines

ESnext

node-iform - A middleware for node-validator httpsgithubcomguileennode-iform

69

Nodejs中文電子書

70 7 CoffeeScript

8

製作一個 Hubot的 Plurk Adapter

81 應用事項提醒

此應用範例以CoffeeScript編寫主要程式架構採用Github釋放的Hubot專案以下有幾個主要注意事項請讀者注意

bull Hubot 一開始的架構除了內建的兩個 Adapter 之外其餘都要以Nodejs的Module方式才能運作

bull Hubot的 binhubot裡面寫著 npm install所以不管你怎麼改原始碼也不能改變第一點的狀況

其實上述都在討論同一件事情NodeJS的模組而且模組不能設定相對路徑之類的來安裝一定要包裝成 npm相符和格式才有辦法正確執行

82 建立 Adapter

首先需要準備兩個檔案

bull packagejson

bull plurkcoffee

71

Nodejs中文電子書

有這兩個就足以變成 NodeJS的模組了在開始 Coding之前先來設定一下 packagejson相依性

rdquonamerdquo rdquohubot-plurkrdquo

rdquoversionrdquo rdquo011rdquo

rdquomainrdquo rdquoplurkrdquo

rdquodependenciesrdquo

rdquohubotrdquo rdquogt=205rdquo

rdquooauthrdquo rdquordquo

rdquocronrdquordquordquo

這邊會使用到NodeJS的 cron模組(Module)而登入 Plurk需要OAuth標準授權才行所以也需要加載 OAuth模組

注意Hubot在讀取非內建模組時會自動在前面加上 hubot-的前置

83 建立 Robot跟 API

本篇應用基本上程式碼大部分參考 Hubot - Twitter Adapter來製作主要差異只有在使用 OAuth這個模組Twitter有 Streaming可用而 Plurk則得用 Comet方式來達到即時讀取

plurkcoffee程式碼

Robot = require(rdquohubotrdquo)robot()

Adapter = require(rdquohubtordquo)adapter()

EventEmitter = require(rdquoeventsrdquo)EventEmitter

oauth = require(rdquooauthrdquo)

cronJob = require(rdquocronrdquo)CronJob

class Plurk exntends Adapter

class PlurkStreaming exnteds EventEmitter

72 8 製作一個 Hubot的 Plurk Adapter

Nodejs中文電子書

先弄個基本架構主要 Class皆參考 Twitter Adapter命名方式接著再將 Plurk這個 Class增加幾個Method基本上只要有 run send reply就夠了而 run用來做初始化的部分

class Plurk entends Adapter

send (plurk id strings⋯) -gt

reply (plurk id strings⋯) -gt

run -gt

看起來有點東西接著來處理主要的 Plurk API結合部分

class PlurkStreaming extends EventEmitter

consuctor (options) -gt

plurk (callback) -gt

觀察河道

getChannel -gt

取得 Comet 網址

reply (plurk id message) -gt

回噗

acceptFriends -gt

接受好友

get (path callback) -gt

GET 請求

post (path body callback)-gt

POST 請求(其實是裝飾)

request (method path body callback)-gt

主要的 OAuth 請求

comet (server callback)-gt

噗浪的 Comet 傳回是 JavaScript Callback 要另外處理後才會變成 JSON

然後把注意力集中到 constructor上先把建構子弄好

constructor (options) -gt

super()

if optionskey and optionssecret and optionstoken and optionstoken secret

key = optionskey

secret = optionssecret

token = optionstoken

token secret = optionstoken secret

83 建立 Robot跟 API 73

Nodejs中文電子書

建立 OAuth 連接

consumer = new oauthOAuth(

rdquohttpwwwplurkcomOAuthrequest tokenrdquo

rdquohttpwwwplurkcomOAuthaccess tokenrdquo

key

secret

rdquo10rdquo

rdquohttpwwwplurkcomOAuthauthorizerdquo

rdquoHMAC-SHA1rdquo

)

domain = rdquowwwplurkcomrdquo

初始化取得 Comet 網址

do getChannel

else

throw new Error(rdquo 參數不足需要 Key Secret Token Token Secretrdquo)

接著來處理 request這個 method

request (method path body callback) -gt

記錄一下這次的 Request

consolelog(rdquohttpdomainpathrdquo)

Callback 這邊先不丟進去要用另一種方式處理

request = consumerget(rdquohttpdomainpathrdquo token token secret null)

requeston rdquoresponserdquo (res) -gt

reson rdquodatardquo (chunk) -gt

parseResponse(chunk+rsquorsquo callback)

reson rdquoendrdquo (data) -gt

consolelog rdquoEnd Request pathrdquo

reson rdquoerrorrdquo (data) -gt

consolelog rdquoError rdquo + data

requestend()

處理資料

parseResponse = (data callback) -gt

if datalength gt 0

用 TryCatch 避免處理 JSON 出錯導致整個中斷

try

callback null JSONparse(data)

catch err

74 8 製作一個 Hubot的 Plurk Adapter

Nodejs中文電子書

consolelog(rdquoError Parse JSONrdquo + data err)

繼續執行

callback null data ||

大致上就是這樣上面程式的架構已經將整個 Hubot Plurk Adapter完成因為在測試時竟然因為噗浪 Lag而沒讀到完整的 Comet資料然後造成程式異常為了避免這個問題發生因此需要加上為了完美呈現需要再

加上 Comet的處理所以要使用到 EventEmitter的功能

comet (server callback) -gt

在 Callback 裡面會找不到自身所以設定區域變數

self =

記錄一下這次的 Request

consolelog(rdquo[Comet] serverrdquo)

Callback 這邊先不丟進去要用另一種方式處理

request = consumerget(rdquohttpdomainpathrdquo token token secret null)

requeston rdquoresponserdquo (res) -gt

reson rdquodatardquo (chunk) -gt

parseResponse(chunk+rsquorsquo callback)

reson rdquoendrdquo (data) -gt

consolelog rdquoEnd Request pathrdquo

請求結束發出事件通知可以進行下一次請求

selfemit rdquonextPlurkrdquo

reson rdquoerrorrdquo (data) -gt

consolelog rdquoError rdquo + data

requestend()

處理資料

parseResponse = (data callback) -gt

if datalength gt 0

用 trycatch 避免失敗中斷

try

去掉 JavaScript 的 Callback

data = datamatch(CometChannelscriptCallback((+))s)

jsonData = rdquordquo

if data

83 建立 Robot跟 API 75

Nodejs中文電子書

jsonData = JSONparse(data[1])

else

如果沒有任何 Match 嘗試直接 parse

jsonData = JSONparse(data)

catch err

consolelog(rdquo[Comet] Errorrdquo data err)

用 TryCatch 避免處理 JSON 出錯導致整個中斷

try

只傳入 json 的 data 部分

callback null jsonDatadata

catch err

consolelog(rdquo[Comet]Error Parse JSONrdquo + data err)

繼續執行

callback null data ||

後面的 get跟 post就簡單多了

get (path callback) -gt

request(rdquoGETrdquo path null callback)

post (path body callback) -gt

request(rdquoPOSTrdquo path body callback)

接著處理取的 Comet網址的 getChannel

getChannel -gt

self =

get rdquoAPPRealtimegetUserChannelrdquo (error data) -gt

if error

檢查是否有 comet server

if datacomet server

selfchannel = datacomet server

如果沒有 Channel Ready 就嘗試連接會失敗

selfemit(rsquochannel readyrsquo)

那麼先來處理 Plurk Adaper好處理的部份

send (plruk id strings⋯)-gt

跟 Reply 一樣直接交給 reply 做

reply plurk id strings⋯

76 8 製作一個 Hubot的 Plurk Adapter

Nodejs中文電子書

reply (plurk id strings⋯) -gt

stringsforEach (message) =gt

botreply(plruk id message)

接著把 run處理好就可以上線運作摟

run -gt

self =

options =

key processenvHUBOT PLURK KEY

secret processenvHUBOT PLURK SECRET

token processenvHUBOT PLURK TOKEN

token secret processenvHUBOT PLURK TOKEN SECRET

創建剛剛的 API

bot = new PlurkStreaming(options)

依照 Twitter 的 new RobotTextMessage 會沒有反應所以參考 hubot-minecraft 的方式

r = robotconstructor

處理噗浪河道訊息

doPlurk = (data)-gt

檢查是否為回噗

if dataresponse

datacontent raw = dataresponsecontent raw

datauser id = dataresponseuser id

確定有噗浪 ID 跟訊息

if dataplurk id and datacontent raw

selfreceive new rTextMessage(dataplurk id datacontent raw)

取得 Comet Server 完成開始第一次 Comet 連接

boton rdquochannel readyrdquo () -gt

botplurk selfdoPlurk

上一次 Comet 完成繼續 Polling

boton rdquonextPlurkrdquo ()-gt

botplurk selfdoPlurk

定時接受好友邀請

do botacceptFriends

bot = bot

83 建立 Robot跟 API 77

Nodejs中文電子書

終於完成 AdapterHubot專案裡面的 scripts資料夾內是互動部分不需要像 Adapter如此大費周章處理只新增檔案並且設計好對白之後就會回噗了我開發用的機器人在此大家可以去跟他玩玩[httpplurkcomelct9620 bot](httpplurkcomelct9620 bot)

84 原始資料提供

bull [製 作 一 個 Hubot 的 噗 浪 Adapter](http revo-skillfrosttw blog20120318create-a-hubot-plurk-adapter)

78 8 製作一個 Hubot的 Plurk Adapter

9

Nodejs好用工具介紹

91 logio

Real-time log monitoring in your browser

httplogioorg

nodejs (gt=0412 lt=0611)

npm config set unsafe-perm true

npm install -g --prefix=usrlocal logio

logio server start

httplocalhost8998

79

Nodejs中文電子書

80 9 Nodejs好用工具介紹

10

精選文章

101 Express

bull 使用 NodeJS + Express從 GETPOST Request取值 [邀稿中]

bull link2 [邀稿中]

bull link3 [邀稿中]

81

Nodejs中文電子書

82 10 精選文章

11

參考資源

111 Nodejs書籍

bull Programming Nodejs April 2012 (est) Travis Swicegood Pragmatic Bookshelf

bull e Little Book on CoffeeScript January 2012 Alex MacCaw OrsquoReilly Media

bull Node for Front-End Developers January 2012 Garann Means OrsquoReilly Media

bull Building Hypermedia APIs with HTML5 and Node November 2011 MikeAmundsen OrsquoReilly Media

bull Node Web Development August 2011 David Herron PacktPub

bull CoffeeScript Accelerated JavaScript Development July 2011 Trevor BurnhamPragmatic Bookshelf

bull Getting Started with GEO CouchDB and Nodejs July 2011 Mick omp-son OrsquoReilly Media

bull What Is Node July 2011 Brett McLaughlin OrsquoReilly Media

bull Node Up and Running May 2011 Tom Hughes-Croucher Mike WilsonOrsquoReilly Media

83

Nodejs中文電子書

bull Hands-on Nodejs e Nodejs introduction and API reference May 2011 Pe-dro Teixeira LeanPub

bull e Node Beginner Book Manuel Kiessling

bull Mastering Nodejs visionmedia

112 Nodejs影音教學

bull e Node Sessions e Best of OSCON 2011 August 2011 OrsquoReilly Media(Video)

bull Tom Hughes-Croucher on Node March 2011 OrsquoReilly Media (Video)

113 Nodejs教學網站

bull Node Tuts

114 Nodejs課程

bull Introduction To Nodejs Van Nguyen CodeLesson

84 11 參考資源

Page 26: Nodejs Wiki

Nodejs中文電子書

章裡面有一種解法不過還需要額外包裝(在他的例子中)NodeJS核心的 fs物件把一些函數(例如 readFile)用 Currying處理類似像這樣

var fs = require(lsquofsrsquo) var readFile = function(path)

return function(callback errback)

fsreadFile(path function(err data)

if(err) errback()

else callback(data)

)

其他部份可以參考 Tim Caswell的文章他的 Doparallel跟上面的 wait差不多意思這裡只提示一下他沒說到的地方

另外一種做法是去修飾一下 callback當他作為事件處理函數執行後再用 cps的方式取得結果

ltscriptgt function Wait(fns done)

var count = 0 var results = [] thisgetCallback = function(index)

count++ return (function(waitback)

return function() var i=0args=[]for(iltargumentslengthi++)

argspush(arguments[i])

argspush(waitback) fns[index]apply(thisargs)

)(function(result) resultspush(result) if(ndashcount == 0)

20 2 JavaScript與 NodeJS

Nodejs中文電子書

done(results)

)

var a = new Wait(

[ function(waitback) consolelog(lsquodone arsquo) var result = 500 wait-back(result) function(waitback) consolelog(lsquodone brsquo) var result =1000 waitback(result) function(waitback) consolelog(lsquodone crsquo) varresult = 1500 waitback(result) ] function(results) var ret = 0 i=0for( iltresultslength i++) ret += results[i] consolelog(lsquodone allresult lsquo+ret)

) var callbacks = [agetCallback(0)agetCallback(1)agetCallback(0)agetCallback(2)]一次取出要使用的 callbacks避免結果提早送出 setTimeout(callbacks[0]500) setTimeout(callbacks[1] 1000) setTimeout(callbacks[2] 1500) setTime-out(callbacks[3] 2000) 當所有取出的 callbacks執行完畢就呼叫 done()來處理結果 ltscriptgt

執行結果

done a done b done a done c done all result 3500

上面只是一些小實驗更成熟的作品是 Tim Caswell 的 stephttpsgithubcomcreationixstep

如果希望真正使用同步的方式寫非同步則需要使用 Promisejs這一類的 library來轉換非同步函數不過他結構比較複雜 XD(見仁見智不過有些人認為 Promise有點過頭了)httpblogsmsdncombrbucktonarchive20110815promise-js-2-0-promise-framework-for-javascriptaspx

如果想不透過其他 Library做轉換又能直接用同步方式執行非同步函數大概就要使用一些需要額外 compile原始程式碼的方法了例如 BrunoJouhier的 streamlinejshttpsgithubcomSagestreamlinejs

26 流程控制 21

Nodejs中文電子書

循序執行

循序執行可以協助把非常深的巢狀 callback結構攤平例如用這樣的簡單模組來做(serialjs)

moduleexports = function(funs) var c = 0 if(isArrayOfFunctions(funs))

throw(lsquoArgument type was not matched Should be array of func-tionsrsquo)

return function()

var args = Arrayprototypeslicecall(arguments 0) if((cgt=funslength))

c++ return funs[c-1]apply(this args)

function isArrayOfFunctions(f ) if(typeof f == lsquoobjectrsquo) return false if(flength)return false if(fconcat) return false if(fsplice) return false var i = 0 for(iltflength i++)

if(typeof f[i] == lsquofunctionrsquo) return false

return true

簡單的測試範例(testSerialjs)使用 fs 模組確定某個 path 是檔案然後讀取印出檔案內容這樣會用到兩層的 callback所以測試中有使用serial的版本與 nested callbacks的版本做對照

var serial = require(lsquoserialrsquo) fs = require(lsquofsrsquo) path = lsquodclientjsrsquo cb = serial([ func-tion(err data)

if(err)

if(dataisFile) fsreadFile(path cb)

22 2 JavaScript與 NodeJS

Nodejs中文電子書

else consolelog(err)

function(err data)

if(err) consolelog(lsquo[ attened by searial]rsquo) con-solelog(datatoString(lsquoutf8rsquo))

else consolelog(err)

]) fsstat(path cb)

fsstat(path function(err data) 第一層 callback if(err)

if(dataisFile)

fsreadFile(path function(err data) 第二層 callback if(err)

consolelog(lsquo[nested callbacks]rsquo) con-solelog(datatoString(lsquoutf8rsquo))

else consolelog(err)

)

else consolelog(err)

)

關鍵在於這些 callback的執行是有順序性的所以利用 serial返回的一個函數 cb來取代這些 callback然後在 cb中控制每次會循序呼叫的函數

26 流程控制 23

Nodejs中文電子書

就可以把巢狀的 callback攤平成循序的 function陣列(就是傳給 serial函數的參數)

測試中的dclientjs 是一個簡單的 dnode 測試程式放在跟 testSerialjs同一個目錄

var dnode = require(lsquodnodersquo)

dnodeconnect(8000 lsquolocalhostrsquo function(remote)

remoterestart(function(str) consolelog(str) processexit()

)

)

執行測試程式後出現結果

[ attened by searial] var dnode = require(lsquodnodersquo)

dnodeconnect(8000 lsquolocalhostrsquo function(remote)

remoterestart(function(str) consolelog(str) processexit()

)

)

[nested callbacks] var dnode = require(lsquodnodersquo)

dnodeconnect(8000 lsquolocalhostrsquo function(remote)

remoterestart(function(str) consolelog(str) processexit()

)

)

對照起來看兩種寫法的結果其實是一樣的但是利用 serialjs巢狀的 callback結構就會消失

不過這樣也只限於順序單純的狀況如果函數執行的順序比較複雜

(不只是一直線)還是需要用功能更完整的流程控制模組比較好例如

httpsgithubcomcaolanasync

24 2 JavaScript與 NodeJS

3

Nodejs安裝與設定

本篇將講解如何在各個不同 OS建立 NodeJS環境目前 NodeJS 048版本環境架設方式需依賴 Linux指令才可編譯完成當然在不同作業系統中也已經有 NodeJS package可以直接使用指令快速架設以下各不同作業系統解說如何安裝 NodeJS

31 Ubuntu Linux

更新推薦使用 nvm

git clone gitgithubcomcreationixnvmgit ~nvm

echo rdquo ~nvmnvmshrdquo gtgt ~bashrc

nvm install v0614

nvm alias default v0614

以 上 可 參 考 http dreamerslabcom blog tw how-to-setup-a-node-js-development-environment-on-ubuntu-11-04

使用 APT套件管理工具是常見的方法以下是使用社群提供的 PPA安裝方式

sudo apt-get install python-software-properties

sudo add-apt-repository ppachris-leanodejs-devel

25

Nodejs中文電子書

sudo apt-get update

sudo apt-get install nodejs

32 Other Linux

Linux很適合作為 NodeJS的伺服器作業系統及開發環境安裝前請先確認以下套件已正確安裝

bull curl (wget)用來下載檔案的工具

bull git先進的版本控制工具

bull g++ GNU C++軟體編譯工具

bull make GNU軟體專案建置工具

安裝指令如下如設有權限問題請在指令前面加上 sudo

git clone httpsgithubcomjoyentnodegit

cd node

git checkout v067

configure

make

sudo make install

接著測試 nodeJS是否正常執行

node --version

出現版本訊息即表示安裝成功

33 Windows

nodeJS 在 v060 版本之後開始正式支援 windows native直接使用nodeexe 就可以執行程式支援性完全與 linux 相同更棒的部份就是不需經過編譯經過下載之後簡單設定完成立即開發 node程式

26 3 Nodejs安裝與設定

Nodejs中文電子書

下載 nodejs安裝檔案 lthttpnodejsorgdownloadgt

如此完成 windows native nodeexe安裝接著可以進入 command line執行測試在 command line輸入rdquonoderdquo會出現 nodejs版本訊息畫面表示安裝完成

33 Windows 27

Nodejs中文電子書

28 3 Nodejs安裝與設定

4

Nodejs基礎

前篇文章已經由介紹安裝至設定都有完整介紹nodeJS 內部除了javascript常用的函式 (function)物件 (object)之外也有許多不同的自訂物件nodeJS預設建立這些物件為核心物件是為了要讓開發流程更為這些資料在官方文件已經具有許多具體說明接下來將會介紹在開發 nodeJS程式時常見的物件特性與使用方法

41 nodejs http伺服器建立

在 lsquonodejs官方網站 lthttpnodejsorggtlsquo裡面有舉一個最簡單的 HTTP伺服器建立一開始初步就是建立一個伺服器平台讓 nodejs可以與瀏覽器互相行為每種語言一開始的程式建立都是以 Hello world開始最初也從 Hello world帶各位進入 nodejs的世界

輸入以下程式碼儲存檔案為 node basic http hello worldjs

var server

ip = rdquo127001rdquo

port = 1337

http = require(rsquohttprsquo)

server = httpcreateServer(function (req res)

29

Nodejs中文電子書

reswriteHead(200 rsquoContent-Typersquo rsquotextplainrsquo)

resend(rsquoHello Worldnrsquo)

)

serverlisten(port ip)

consolelog(rdquoServer running at httprdquo + ip + rdquordquo + port)

程式碼解講一開始需要有幾個基本的變數

bull ip 機器本身的 ip位置因為使用本地端因此設定為 127001

bull port 需要開通的阜號通常設定為 http port 80因範例不希望與基本 port相衝隨意設定為 1337

在 nodejs 的程式中有許多預設的模組可以使用因此需要使用require方法將模組引入在這邊我們需要使用 http這個模組因此將 http載入Http模組裡面內建有許多方法可以使用這邊採用 createServer創建一個基本的 http伺服器再將 http伺服器給予一個 server變數裡面的回呼函式 (call back function)可以載入 http伺服器的資料與回應方法 (requestresponse)在程式裡面就可以看到我們直接回應給瀏覽器端所需的 Header回應內容

reswriteHead(200 rsquoContent-Typersquo rsquotextplainrsquo)

resend(rsquoHello Worldnrsquo)

Http伺服器需要設定 port ip在最後需要設定 Http監聽需要使用到listen事件監聽所有 Http伺服器行為

httplisten(port ip)

所有事情都完成之後需要確認伺服器正確執行因此使用 console在javascript裡就有這個原生物件console所印出的資料都會顯示於 nodejs伺服器頁面這邊印出的資料並不會傳送到使用者頁面上之後許多除壞

(debug)都會用到 console物件

consolelog(rdquoServer running at httprdquo + ip + rdquordquo + port)

30 4 Nodejs基礎

Nodejs中文電子書

42 nodejs http路徑建立

前面已經介紹如何建立一個簡單的 http伺服器接下來本章節將會介紹如何處理伺服器路徑 (rout)問題在 http的協定下所有從瀏覽器發出的要求 (request)都需要經過處理路徑上的建立也是如此

路徑就是指伺服器 ip位置或者是網域名稱之後對於伺服器給予的要求修改剛才的 hello world檔案修改如下

server = httpcreateServer(function (req res)

consolelog(requrl)

reswriteHead(200 rsquoContent-Typersquo rsquotextplainrsquo)

resend(rsquohello worldnrsquo)

)

重新啟動 nodejs程式後在瀏覽器端測試一下路徑行為結果如下圖

當在瀏覽器輸入 http1270011337test 在伺服器端會收到兩個要求一個是我們輸入的test要求另外一個則是faviconicotest的路徑要求http伺服器本身需要經過程式設定才有辦法回應給瀏覽器端所需要的回應在伺服器中所有的路徑要求都是需要被解析才有辦法取得資料從

42 nodejs http路徑建立 31

Nodejs中文電子書

上面解說可以了解到在 nodejs當中所有的路徑都需要經過設定未經過設定的路由會讓瀏覽器無法取得任何資料導致錯誤頁面的發生底下將會解

說如何設定路由同時避免發生錯誤情形先前 nodejs程式需要增加一些修改才能讓使用者透過瀏覽器在不同路徑時有不同的結果根據剛才

的程式做如下的修改

var server

ip = rdquo127001rdquo

port = 1337

http = require(rsquohttprsquo)

url = require(rsquourlrsquo)

path

server = httpcreateServer(function (req res)

path = urlparse(requrl)

reswriteHead(200 rsquoContent-Typersquo rsquotextplainrsquo)

switch (pathpathname)

case rdquoindexrdquo

resend(rsquoI am indexnrsquo)

break

case rdquotestrdquo

resend(rsquothis is test pagenrsquo)

break

default

resend(rsquodefault pagenrsquo)

break

)

serverlisten(port ip)

consolelog(rdquoServer running at httprdquo + ip + rdquordquo + port)

程式做了片段的修改首先載入 url模組另外增加一個 path變數url模組就跟如同他的命名一般專門處理 url字串處理裡面提供了許多方法來解決路徑上的問題因為從瀏覽器發出的要求路徑可能會帶有多種需求

或者 GET參數組合等因此我們需要將路徑單純化取用路徑部分的資料即可例如使用者可能會送出 http1270011337testsend=1如果直接

32 4 Nodejs基礎

Nodejs中文電子書

信任 requrl就會收到結果為testsend=1所以需要透過 url模組的方法將路徑資料過濾

在這邊使用 urlparse的方法裡面帶入網址格式資料會回傳路徑資料為了後需方便使用將回傳的資料設定到 path變數當中在回傳的路徑資料裡面包含資訊如下圖

這邊只需要使用單純的路徑要求直接取用 pathpathname就可以達到我們的目的

最後要做路徑的判別在不同的路徑可以指定不同的輸出在範例中

有三個可能結果第一個從瀏覽器輸入index就會顯示 index結果test 就會呈現出 test頁面最後如果都不符合預期的輸入會直接顯示 default的頁面最後的預防可以讓瀏覽器不會出現非預期結果讓程式的可靠性提昇

底下為測試結果

42 nodejs http路徑建立 33

Nodejs中文電子書

43 nodejs檔案讀取

前面已經介紹如何使用路由(rount)做出不同的回應實際應用只有在瀏覽器只有輸出幾個文字資料總是不夠的在本章節中將介紹如何使

用檔案讀取輸出檔案資料讓使用者在前端瀏覽器也可以讀取到完整的

html css javascript檔案輸出

檔案管理最重要的部分就是 lsquoFile system lthttpnodejsorgdocslatestapifshtmlgtlsquo這個模組此模組可以針對檔案做管理監控讀取等行為裡面有許多預設的方法底下是檔案輸出的基本範例底下會有兩個檔案

第一個是靜態 html檔案

ltDOCTYPE htmlgt

lthtml xmlns=rdquohttpwwww3org1999xhtmlrdquo xmllang=rdquozh-CNrdquo lang=rdquozh-CNrdquogt

ltheadgt

ltmeta http-equiv=rdquoContent-Typerdquo content=rdquotexthtml charset=UTF-7rdquo gt

lttitlegtnodejs index html filelttitlegt

ltheadgt

ltbodygt

34 4 Nodejs基礎

Nodejs中文電子書

lth1gtnodejs index html filelth1gt

ltbodygt

lthtmlgt

另一個為 nodejs程式

var fs = require(rdquofsrdquo)

filename = rdquostaticindexhtmlrdquo

encode = rdquoutf8rdquo

fsreadFile(filename encode function(err file)

consolelog(file)

)

一開始直接載入 le system模組 載入名稱為 fs讀取檔案主要使

用的方法為 readFile裡面以三個參數路徑 ( le path) 編碼方式 (encoding)

回應函式 (callback)路徑必須要設定為靜態 html所在位置才能指定到正確的檔案靜態檔案的編碼方式也必須正確這邊使用靜態檔案的編

碼為 utf8如果編碼設定錯誤nodejs讀取出來檔案結果會使用 byte raw格式輸出如果錯誤編碼格式會導致輸出資料為 byte raw

回應函式中裡面會使用兩個變數error為錯誤資訊如果讀取的檔案不存在或者發生錯誤error數值會是 true如果成功讀取資料 error將會是 falsecontent則是檔案內容資料讀取後將會把資料全數丟到 content這個變數當中

最後程式的輸出結果畫面如下

43 nodejs檔案讀取 35

Nodejs中文電子書

44 nodejs http靜態檔案輸出

前面已經了解如何讀取本地端檔案接下來將配合 http伺服器路由讓每個路由都能夠輸出相對應的靜態 html檔案首先新增加幾個靜態 html檔案

ltDOCTYPE htmlgt

lthtml xmlns=rdquohttpwwww3org1999xhtmlrdquo xmllang=rdquozh-CNrdquo lang=rdquozh-CNrdquogt

ltheadgt

ltmeta http-equiv=rdquoContent-Typerdquo content=rdquotexthtml charset=UTF-7rdquo gt

lttitlegtnodejs index html filelttitlegt

ltheadgt

ltbodygt

lth1gtnodejs index html filelth1gt

ltbodygt

lthtmlgt

ltDOCTYPE htmlgt

lthtml xmlns=rdquohttpwwww3org1999xhtmlrdquo xmllang=rdquozh-CNrdquo lang=rdquozh-CNrdquogt

ltheadgt

ltmeta http-equiv=rdquoContent-Typerdquo content=rdquotexthtml charset=UTF-7rdquo gt

lttitlegtnodejs test html filelttitlegt

ltheadgt

ltbodygt

lth1gtnodejs test html filelth1gt

ltbodygt

lthtmlgt

ltDOCTYPE htmlgt

lthtml xmlns=rdquohttpwwww3org1999xhtmlrdquo xmllang=rdquozh-CNrdquo lang=rdquozh-CNrdquogt

ltheadgt

ltmeta http-equiv=rdquoContent-Typerdquo content=rdquotexthtml charset=UTF-7rdquo gt

lttitlegtnodejs static html filelttitlegt

ltheadgt

ltbodygt

lth1gtnodejs static html filelth1gt

ltbodygt

lthtmlgt

36 4 Nodejs基礎

Nodejs中文電子書

準備一個包含基本路由功能的 http伺服器

var server

ip = rdquo127001rdquo

port = 1337

http = require(rsquohttprsquo)

url = require(rsquourlrsquo)

server = httpcreateServer(function (req res)

var path = urlparse(requrl)

)

serverlisten(port ip)

consolelog(rdquoServer running at httprdquo + ip + rdquordquo + port)

加入 le system 模組使用 readFile 的功能將這一段程式放置於

createServer的回應函式中

fsreadFile(filePath encode function(err file)

)

readFile的回應函式裡面加入頁面輸出讓瀏覽器可以正確讀到檔案在這邊我們設定讀取的檔案為 html 靜態檔案所以 Content-type 設定為texthtml讀取到檔案的內容將會正確輸出成 html靜態檔案

fsreadFile(filePath encode function(err file)

reswriteHead(200 rsquoContent-Typersquo rsquotexthtmlrsquo)

reswrite(file)

resend()

)

到這邊為止基本的程式內容都已經完成剩下一些細節的調整首先

路徑上必須做調整目前的靜態檔案全部都放置於 static資料夾底下設定一個變數來記住資料夾位置

接著將瀏覽器發出要求路徑與資料夾組合讀取正確 html靜態檔案使用者有可能會輸入錯誤路徑所以在讀取檔案的時候要加入錯誤處理

同時回應 404伺服器無法正確回應的 http header格式

加入這些細節的修改一個基本的 http 靜態 html輸出伺服器就完成了完整程式碼如下

44 nodejs http靜態檔案輸出 37

Nodejs中文電子書

var server

ip = rdquo127001rdquo

port = 1337

http = require(rsquohttprsquo)

fs = require(rdquofsrdquo)

folderPath = rdquostaticrdquo

url = require(rsquourlrsquo)

path

filePath

encode = rdquoutf8rdquo

server = httpcreateServer(function (req res)

path = urlparse(requrl)

filePath = folderPath + pathpathname

fsreadFile(filePath encode function(err file)

if (err)

reswriteHead(404 rsquoContent-Typersquo rsquotextplainrsquo)

resend()

return

reswriteHead(200 rsquoContent-Typersquo rsquotextapplicationrsquo)

reswrite(file)

resend()

)

)

serverlisten(port ip)

consolelog(rdquoServer running at httprdquo + ip + rdquordquo + port)

45 nodejs http GET資料擷取

http伺服器中除了路由之外另一個最常使用的方法就是擷取 GET資料本單元將會介紹如何透過基本 http伺服器擷取瀏覽器傳來的要求擷取 GET資料

在 http協定中GET參數都是藉由 URL從瀏覽器發出要求送至伺服器

38 4 Nodejs基礎

Nodejs中文電子書

端基本的傳送網址格式可能如下

http127001testsend=1amptest=2

上面這段網址裡面的 GET參數就是 send而這個變數的數值就為 1如果想要在 http伺服器取得 GET資料需要在瀏覽器給予的要求 (request)做處理

首先需要載入 query string這個模組這個模組主要是用來將字串資料

過濾後轉換成javascript物件

qs = require(rsquoquerystringrsquo)

接著在第一階段利用 url模組過濾瀏覽器發出的 URL資料後將回應的物件裡面的 query這個變數是一個字串值資料過濾後如下

send=1amptest=2

透過 query string使用 parse這個方法將資料轉換成 javascript物件就表示 GET的資料已經被伺服器端正式擷取下來

path = urlparse(requrl)

parameter = qsparse(pathquery)

整個 nodejs http GET參數完整擷取程式碼如下

var server

ip = rdquo127001rdquo

port = 1337

http = require(rsquohttprsquo)

qs = require(rsquoquerystringrsquo)

url = require(rsquourlrsquo)

server = httpcreateServer(function (req res)

var path = urlparse(requrl)

parameter = qsparse(pathquery)

consoledir(parameter)

reswriteHead(200 rsquoContent-Typersquo rsquotextplainrsquo)

reswrite(rsquoBrowser test GET parameternrsquo)

resend()

)

45 nodejs http GET資料擷取 39

Nodejs中文電子書

serverlisten(port ip)

consolelog(rdquoServer running at httprdquo + ip + rdquordquo + port)

程式運作之後由瀏覽器輸入要求網址之後nodejs伺服器端回應資料為

Server running at http1270011337

send rsquo1rsquo test rsquo1rsquo

46 本章結語

前面所解說的部份一大部分主要是處理 http伺服器基本問題雖然在某些部分有牽扯到 http 伺服器基本運作原理主要還是希望可以藉由這些基本範例練習 nodejs練習回應函式與語法串接的特點習慣編寫javascript風格程式當然直接這樣開發 nodejs是非常辛苦的接下來在模組實戰開發的部份將會介紹特定的模組一步一步帶領各位從無到有進行

nodejs應用程式開發

40 4 Nodejs基礎

5

NPM套件管理工具

npm全名為 Node Package Manager是 Nodejs的套件(package)管理工具類似 Perl的 ppm或 PHP的 PEAR等安裝 npm後使用npm install

module name指令即可安裝新套件維護管理套件的工作會更加輕鬆

npm可以讓Nodejs的開發者直接利用擴充線上的套件庫(packagesregistry)加速軟體專案的開發npm提供很友善的搜尋功能可以快速找到安裝需要的套件當這些套件發行新版本時npm也可以協助開發者自動更新這些套件

npm不僅可用於安裝新的套件它也支援搜尋列出已安裝模組及更新的功能

51 安裝 NPM

Nodejs在 063版本開始內建 npm讀者安裝的版本若是此版本或更新的版本就可以略過以下安裝說明

若要檢查 npm是否正確安裝可以使用以下的指令

npm -v

41

Nodejs中文電子書

執行結果說明

若 npm正確安裝執行 npm -v將會看到類似 110-2的版本訊息

若讀者安裝的 Nodejs版本比較舊或是有興趣嘗試自己動手安裝 npm工具則可以參考以下的說明

安裝於Windows系統

Nodejs for Windows 於 062 版開始內建 npm使用 nodejsorg 官方提供的安裝程式不需要進一步的設定就可以立即使用 npm指令對於Windows的開發者來說大幅降低環境設定的問題與門檻

除了使用 Nodejs內建的 npm讀者也可以從 npm官方提供的以下網址

httpnpmjsorgdist

這是由 npm提供的 Fancy Windows Install版本請下載壓縮檔(例如npm-110-3zip)並將壓縮檔內容解壓縮至 Nodejs的安裝路徑(例如CProgram Filesnodejs)

解壓縮後在 Nodejs的安裝路徑下應該有以下的檔案及資料夾

bull npmcmd(檔案)

bull node modules(資料夾)

安裝於 Linux系統

Ubuntu Linux的使用者可以加入 NPM Unoffcial PPA這個 repository即可使用 apt-get完成 npm安裝

42 5 NPM套件管理工具

Nodejs中文電子書

Ubuntu Linux使用 apt-get安裝 npm

sudo apt-get install python-software-properties

sudo add-apt-repository ppagias-kay-leenpm

sudo apt-get update

sudo apt-get install npm

npm官方提供的安裝程式 installsh可以適用於大多數的 Linux系統使用這個安裝程式請先確認

1 系統已安裝 curl工具(請使用 curl --version查看版本訊息)

2 已安裝 Nodejs並且 PATH正確設置

3 Nodejs的版本必須大於 04x

以下為 npm提供的安裝指令

curl httpnpmjsorginstallsh | sh

安裝成功會看到如下訊息

installsh安裝成功的訊息

npm10105 homeUSERNAMElocalnodelibnode_modulesnpm

It worked

安裝於Mac OS X

建議採用與 Nodejs相同的方式進行 npm的安裝例如使用MacPorts安裝 Nodejs就同樣使用MacPorts安裝 npm這樣對日後的維護才會更方便容易

使用 MacPorts安裝 npm是本書比較建議的方式它可以讓 npm的安裝移除及更新工作自動化將會幫助開發者節省寶貴時間

51 安裝 NPM 43

Nodejs中文電子書

安裝MacPorts的提示

在MacPorts網站可以取得 OS X系統版本對應的安裝程式(例如 106或 107)httpwwwmacportsorg安裝過程會詢問系統管理者密碼使用預設的選項完成安裝即可安

裝MacPorts之後在終端機執行 port -v將會看到MacPorts的版本訊息

安裝 npm之前先更新MacPorts的套件清單以確保安裝的 npm是最新版本

sudo port -d selfupdate

接著安裝 npm

sudo port install npm

若讀者的 Nodejs並非使用MacPorts安裝則不建議使用MacPorts安裝npm因為 MacPorts會自動檢查並安裝相依套件而 npm相依 nodejs所以MacPorts也會一併將 nodejs套件安裝造成先前讀者使用其它方式安裝的 nodejs被覆蓋

讀者可以先使用 MacPorts安裝 curl(sudo port install curl)

再參考 Linux的 installsh安裝方式即可使用 npm官方提供的安裝程式

NPM安裝後測試

npm是指令列工具(command-line tool)使用時請先打開系統的文字終端機工具

測試 npm安裝與設定是否正確請輸入指令如下

npm -v

或是

npm --version

如果 npm已經正確安裝設定就會顯示版本訊息

44 5 NPM套件管理工具

Nodejs中文電子書

執行結果(範例)

110-2

52 使用 NPM安裝套件

npm目前擁有超過 6000種套件(packages)可以在 npm registry使用關鍵字搜尋套件

httpsearchnpmjsorg

舉例來說在關鍵字欄位輸入「coffee-script」下方的清單就會自動列出包含 coffee-script關鍵字的套件

接著我們回到終端機模式的操作npm的指令工具本身就可以完成套

件搜尋的任務

例如以下的指令同樣可以找出 coffee-script相關套件

npm search coffee-script

以下是搜尋結果的參考畫面

52 使用 NPM安裝套件 45

Nodejs中文電子書

找到需要的套件後(例如 express)即可使用以下指令安裝

npm install coffee-script

值得注意的一點是使用 npm install會將指定的套件安裝在工

作目錄(Working Directory)的 node modules資料夾下

以Windows為例如果執行 npm install的目錄位於

Cproject1

那麼 npm將會自動建立一個 node modules的子目錄(如果不存在)

Cproject1node modules

並且將下載的套件放置於這個子目錄例如

Cproject1node modulescoffee-script

這個設計讓專案可以個別管理相依的套件並且可以在專案佈署或發

行時將這些套件(位於 node modules)一併打包方便其它專案的使用者不必再重新下載套件

這個 npm install的預設安裝模式為 local(本地)只會變更當前專案的資料夾不會影響系統

另一種安裝模式稱為 global(全域)這種模式會將套件安裝到系統資

料夾也就是 npm安裝路徑的 node modules資料夾例如

CProgram Filesnodejsnode modules

46 5 NPM套件管理工具

Nodejs中文電子書

是否要使用全域安裝可以依照套件是否提供新指令來判斷舉例來說express 套件提供 express 這個指令而 coffee-script 則提供 coffee

指令

在 local 安 裝 模 式 中這 些 指 令 的 程 式 檔 案會 被 安 裝 到node modules的 bin這個隱藏資料夾下除非將bin的路徑加入 PATH環境變數否則要執行這些指令將會相當不便

為了方便指令的執行我們可以在 npm install 加上 -g 或 --

global參數啟用 global安裝模式例如

npm install -g coffee-script

npm install -g express

使用 global安裝模式需要注意執行權限的問題若權限不足可能會出現類似以下的錯誤訊息

npm ERR Error EACCES permission denied rsquorsquo

npm ERR

npm ERR Please try running this command again as rootAdministrator

要獲得足夠得執行權限請參考以下說明

bull Windows 7 或 2008 以上在「命令提示字元」的捷徑按右鍵選擇「以系統管理員身分執行」執行 npm指令時就會具有 Administrator身分

bull Mac OS X或 Linux系統可以使用 sudo指令例如

sudo npm install -g express

bull Linux系統可以使用 root權限登入或是以「sudo su -」切換成 root身分(使用 root權限操作系統相當危險因此並不建議使用這種方式)

若加上 -g參數使用 npm install -g coffee-script完成安裝

後就可以在終端機執行 coffee指令例如

coffee -v

52 使用 NPM安裝套件 47

Nodejs中文電子書

執行結果(範例)

CoffeeScript version 120

53 套件的更新及維護

除了前一節說明的 search 及 install 用法npm 還提供其他許多指令(commands)

使用 npm help可以查詢可用的指令

npm help

執行結果(部分)

where ltcommandgt is one of

adduser apihelp author bin bugs c cache completion

config deprecate docs edit explore faq find get

help help-search home i info init install la link

list ll ln login ls outdated owner pack prefix

prune publish r rb rebuild remove restart rm root

run-script s se search set show star start stop

submodule tag test un uninstall unlink unpublish

unstar up update version view whoami

使用 npm help command可以查詢指令的詳細用法例如

npm help list

接下來本節要介紹開發過程常用的 npm指令

使用 list可以列出已安裝套件

npm list

48 5 NPM套件管理工具

Nodejs中文電子書

執行結果(範例)

-- coffee-script120

- express256

- connect185

| -- formidable108

-- mime124

-- mkdirp007

-- qs041

檢視某個套件的詳細資訊例如

npm show express

升級所有套件(如果該套件已發佈更新版本)

npm update

升級指定的套件

npm update express

移除指定的套件

npm uninstall express

54 使用 packagejson

對於正式的 Nodejs專案可以建立一個命名為 packagejson的設

定檔(純文字格式)檔案內容參考範例如下

54 使用 packagejson 49

Nodejs中文電子書

packagejson(範例)

rdquonamerdquo rdquoapplication-namerdquo

rdquoversionrdquo rdquo001rdquo

rdquoprivaterdquo true

rdquodependenciesrdquo

rdquoexpressrdquo rdquo255rdquo

rdquocoffee-scriptrdquo rdquolatestrdquo

rdquomongooserdquo rdquogt= 253rdquo

其中 name與 version依照專案的需求設置

需要注意的是 dependencies的設定它用於指定專案相依的套件名

稱及版本

bull rdquoexpressrdquo rdquo255rdquo

代表此專案相依版本 255的 express套件

bull rdquocoffee-scriptrdquo rdquolatestrdquo

使用最新版的 coffee-script套件(每次更新都會檢查新版)

bull rdquomongooserdquo rdquogt= 253rdquo

使用版本大於 253的 mongoose套件

假設某個套件的新版可能造成專案無法正常運作就必須指定套件的

版本避免專案的程式碼來不及更新以相容新版套件通常在開發初期的

專案需要盡可能維持新套件的相容性(以取得套件的更新或修正)可以

用「gt=」設定最低相容的版本或是使用「latest」設定永遠保持最新

套件

50 5 NPM套件管理工具

6

Express介紹

在前面的 nodejs基礎當中介紹許多許多開設 http的使用方法及介紹以及許多基本的 nodejs基本應用

接下來要介紹一個套件稱為 express [Express](httpexpressjscom)這個套件主要幫忙解決許多 nodejs http server所需要的基本服務讓開發 httpservice變得更為容易不需要像之前需要透過層層模組(module)才有辦法開始編寫自己的程式

這個套件是由 TJ Holowaychuk 製作而成的套件裡面包含基本的路由處理 (route)http資料處理(GETPOSTPUT)另外還與樣板套件(jshtml template engine)搭配同時也可以處理許多複雜化的問題

61 Express安裝

安裝方式十分簡單只要透過之前介紹的 NPM就可以使用簡單的指令安裝指令如下

這邊建議需要將此套件安裝成為全域模組方便日後使用

51

Nodejs中文電子書

62 Express基本操作

express的使用也十分簡單先來建立一個基本的 hello world

var app = require(rsquoexpressrsquo)createServer()

port = 1337

applisten(port)

appget(rsquorsquo function(req res)

ressend(rsquohello worldrsquo)

)

consolelog(rsquostart express servernrsquo)

可以從上面的程式碼發現基本操作與 nodejs http的建立方式沒有太大差異主要差在當我們設定路由時可以直接透過 appget方式設定回應與接受方式

63 Express路由處理

Express對於 http服務上有許多包裝讓開發者使用及設定上更為方便例如有幾個路由設定那我們就統一藉由 appget來處理

Create http server

appget(rsquorsquo function(req res)

ressend(rsquohello worldrsquo)

)

appget(rsquotestrsquo function(req res)

ressend(rsquotest renderrsquo)

)

appget(rsquouserrsquo function(req res)

ressend(rsquouser pagersquo)

)

52 6 Express介紹

Nodejs中文電子書

如上面的程式碼所表示appget可以帶入兩個參數第一個是路徑名稱設定第二個為回應函式 (call back function)回應函式裡面就如同之前的 createServer方法裡面包含 requestresponse兩個物件可供使用使用者就可以透過瀏覽器輸入不同的 url切換到不同的頁面顯示不同的結果

路由設定上也有基本的配對方式讓使用者從瀏覽器輸入的網址可以

是一個變數只要符合型態就可以有對應的頁面產出例如

Create http server

appget(rsquouseridrsquo function(req res)

ressend(rsquouser rsquo + reqparamsid)

)

appget(rsquonumberrsquo function(req res)

ressend(rsquonumber rsquo + reqparamsnumber)

)

裡 面 使 用 到number 從 網 址 輸 入 之 後 就 可 以 直 接 使 用reqparamsnumber 取得所輸入的資料變成 url 參數使用當然前面也是可以加上路徑的設定userid在瀏覽器上路徑必須符合userxxx透

過 reqparamsid就可以取到 xxx這個字串值

另外express參數處理也提供了路由參數配對處理也可以透過正規表示法作為參數設定

var app = require(rsquoexpressrsquo)createServer()

port = 1337

applisten(port)

appget(^ip((d23)((d23))((d23))((d23))) function(req res)

ressend(reqparams)

)

上面程式碼可以發現後面路由設定的型態是正規表示法裡面設定

格式為ip之後必須要加上 ip型態才會符合資料格式同時取得 ip資料已經由正規表示法將資料做分群因此可以取得 ip的四個數字

此程式執行之後可以透過瀏覽器測試輸入網址為 localhost3000ip25525510010可以從頁面獲得資料

63 Express路由處理 53

Nodejs中文電子書

此章節全部範例程式碼如下

overview

author Caesar Chi

blog clonnblogspotcom

version 20120226

create server

var app = require(rsquoexpressrsquo)createServer()

port = 1337

applisten(port)

normal style

appget(rsquorsquo function(req res)

ressend(rsquohello worldrsquo)

)

appget(rsquotestrsquo function(req res)

ressend(rsquotest renderrsquo)

)

parameter style

appget(rsquouseridrsquo function(req res)

ressend(rsquouser rsquo + reqparamsid)

)

appget(rsquonumberrsquo function(req res)

ressend(rsquonumber rsquo + reqparamsnumber)

)

REGX style

appget(^ip((d23)((d23))((d23))) function(req res)

ressend(reqparams)

)

appget(rsquorsquo function(req res)

ressend(rsquoPage not foundrsquo 404)

54 6 Express介紹

Nodejs中文電子書

)

consolelog(rsquostart express servernrsquo)

64 Express middleware

Express 裡面有一個十分好用的應用概念稱為 middleware可以透過middleware做出複雜的效果同時上面也有介紹 next方法參數傳遞就是靠 middleware的概念來傳遞參數讓開發者可以明確的控制程式邏輯

create http server

appuse(expressbodyParser())

appuse(expressmethodOverride())

appuse(expresssession())

上面都是一種 middleware的使用方式透過 appuse方式裡面載入函式執行方法回應函式會包含三個基本參數responserequestnext其中next表示下一個 middleware執行函式同時會自動將預設三個參數繼續帶往下個函式執行底下有個實驗

上面的片段程式執行後開啟瀏覽器連結上 localhost1337會發現伺服器回應結果順序如下

first middle ware

second middle ware

execute middle ware

end middleware function

從上面的結果可以得知剛才設定的 middleware都生效了在 appuse設定的 middleware是所有 url皆會執行方法如果有指定特定方法就可以使用 appget的 middleware設定在 appget函式的第二個參數就可以帶入函式或者是匿名函式只要函式裡面最後會接受 request response next這三個參數同時也有正確指定 next函式的執行時機最後都會執行到最後一個方法當然開發者也可以評估程式邏輯要執行到哪一個階段讓邏輯

可以更為分明

64 Express middleware 55

Nodejs中文電子書

65 Express路由應用

在實際開發上可能會遇到需要使用參數等方式混和變數一起使用

express裡面提供了一個很棒的處理方法 appall這個方式可以先採用基本路由配對再將設定為每個不同的處理方式開發者可以透過這個方式簡

化自己的程式邏輯

overview

author

version 20120226

create server

var app = require(rsquoexpressrsquo)createServer()

port = 1337

users = [

name rsquoClonnrsquo

name rsquoChirsquo

]

applisten(port)

appall(rsquouseridoprsquo function(req res next)

requser = users[reqparamsid]

if (requser)

next()

else

next(new Error(rsquocannot find user rsquo + reqparamsid))

)

appget(rsquouseridrsquo function(req res)

ressend(rsquoviewing rsquo + requsername)

)

appget(rsquouserideditrsquo function(req res)

ressend(rsquoediting rsquo + requsername)

)

56 6 Express介紹

Nodejs中文電子書

appget(rsquouseriddeletersquo function(req res)

ressend(rsquodeleting rsquo + requsername)

)

appget(rsquorsquo function(req res)

ressend(rsquoPage not foundrsquo 404)

)

consolelog(rsquostart express servernrsquo)

內部宣告一組預設的使用者分別給予名稱設定藉由 appall這個方法可以先將路由雛形建立再接下來設定 appget的路徑格式只要符合格式就會分配進入對應的方法中像上面的程式當中如果使用者輸入路徑為user0除了執行 appall程式之後執行 next方法就會對應到路徑設定為userid的這個方法當中如果使用者輸入路徑為user0edit就會執行到useridedit的對應方法

66 Express GET應用範例

我們準備一個使用 GET方法傳送資料的表單

ltDOCTYPE htmlgt

lthtmlgt

ltheadgt

ltmeta http-equiv=rdquoContent-Typerdquo content=rdquotexthtml charset=utf8rdquogt

lttitlegtNodejs 菜鳥筆記 (1)lttitlegt

ltlink rel=rdquostylesheetrdquo href=rdquocssstylecssrdquo type=rdquotextcssrdquo media=rdquoallrdquo gt

ltheadgt

ltbodygt

lth1gtNodejs 菜鳥筆記-註冊lth1gt

ltform id=rdquosignuprdquo method=rdquoGETrdquo action=rdquohttplocalhost3000Signuprdquogt

ltlabelgt 使用者名稱ltlabelgtltinput type=rdquotextrdquo id=rdquousernamerdquo name=rdquousernamerdquo gtltbrgt

ltlabelgt 電子郵件ltlabelgtltinput type=rdquotextrdquo id=rdquoemailrdquo name=rdquoemailrdquo gtltbrgt

ltinput type=rdquosubmitrdquo value=rdquo 註冊我的帳號rdquo gtltbrgt

ltformgt

ltbodygt

lthtmlgt

66 Express GET應用範例 57

Nodejs中文電子書

這個表單沒有什麼特別的地方我們只需要看第 9 行form 使用的method是 GET然後 action是rdquohttplocalhost3000Signupldquo等一下我們要來撰寫Signup這個 URL Path的處理程式

處理 Signup行為

我們知道所謂的 GET方法會透過 URL來把表單的值給帶過去以上面的表單來說到時候 URL會以這樣的形式傳遞

httplocalhost3000Signupusername=xxxampemail=xxx

所以要能處理這樣的資料必須有以下功能

bull 解析 URL

bull 辨別動作是 Signup

bull 解析出 username和 email

一旦能取得 username和 email的值程式就能加以應用了

處理 Signup的程式碼雛形

load module

var url = require(rsquourlrsquo)

urlData = urlparse(requrltrue)

action = urlDatapathname

reswriteHead(200 rdquoContent-Typerdquordquotexthtml charset=utf-8rdquo)

if (action === rdquoSignuprdquo)

user = urlDataquery

resend(rdquolth1gtrdquo + userusername + rdquo 歡迎您的加入 lth1gtltpgt 我們已經將會員啟用信寄至rdquo + useremail + rdquoltpgtrdquo)

首先需要加載 url module它是用來協助我們解析 URL的模組接著使用 urlparse方法第一個傳入 url字串變數也就是 requrl另外第二個參數的用意是設為 ture則引進 querystring模組來協助處理預設是 false它影響到的是 urlDataquery設為 true會傳回物件不然就只是一般的字串urlparse會將字串內容整理成一個物件我們把它指定給 urlData

action變數作為記錄 pathname這是我們稍後要來判斷目前網頁的動作是什麼接著先將 html表頭資訊 (Header)準備好再來判斷路徑邏輯如

58 6 Express介紹

Nodejs中文電子書

果是 Signup這個動作就把 urlDataquery裡的資料指定給 user然後輸出userusername和 useremail把使用者從表單註冊的資料顯示於頁面中

最後進行程式測試啟動 nodejs主程式之後開啟瀏覽器就會看到表單填寫完畢按下送出就可以看到結果了

完整 nodejs程式碼如下

var http = require(rsquohttprsquo)

url = require(rsquourlrsquo)

fs = require(rdquofsrdquo)

server

server = httpcreateServer(function (reqres)

var urlData

encode = rdquoutf8rdquo

filePath = rdquoviewexpress_get_example_formhtmlrdquo

action

urlData = urlparse(requrltrue)

action = urlDatapathname

reswriteHead(200 rdquoContent-Typerdquordquotexthtml charset=utf-8rdquo)

if (action === rdquoSignuprdquo)

user = urlDataquery

resend(rdquolth1gtrdquo + userusername + rdquo 歡迎您的加入 lth1gtltpgt 我們已經將會員啟用信寄至rdquo + useremail + rdquoltpgtrdquo)

else

fsreadFile(filePath encode function(err file)

reswrite(file)

resend()

)

)

serverlisten(3000)

consolelog(rsquoServer 跑起來了現在時間是rsquo + new Date())

66 Express GET應用範例 59

Nodejs中文電子書

67 Express POST應用範例

一開始準備基本的 html表單傳送內容以 POST方式form的 action屬性設定為 POST其餘 html內容與前一個範例應用相同

ltDOCTYPE htmlgt

lthtmlgt

ltheadgt

ltmeta http-equiv=rdquoContent-Typerdquo content=rdquotexthtml charset=utf8rdquogt

lttitlegtNodejs 菜鳥筆記 (1)lttitlegt

ltlink rel=rdquostylesheetrdquo href=rdquocssstylecssrdquo type=rdquotextcssrdquo media=rdquoallrdquo gt

ltheadgt

ltbodygt

lth1gtNodejs 菜鳥筆記-註冊lth1gt

ltform id=rdquosignuprdquo method=rdquoPOSTrdquo action=rdquohttplocalhost3000Signuprdquogt

ltlabelgt 使用者名稱ltlabelgtltinput type=rdquotextrdquo id=rdquousernamerdquo name=rdquousernamerdquo gtltbrgt

ltlabelgt 電子郵件ltlabelgtltinput type=rdquotextrdquo id=rdquoemailrdquo name=rdquoemailrdquo gtltbrgt

ltinput type=rdquosubmitrdquo value=rdquo 註冊我的帳號rdquo gtltbrgt

ltformgt

ltbodygt

lthtmlgt

nodejs的程式處理邏輯與前面 GET範例類似部分程式碼如下

qs = require(rsquoquerystringrsquo)

if (action === rdquoSignuprdquo)

formData = rsquorsquo

reqon(rdquodatardquo function (data)

formData += data

)

reqon(rdquoendrdquo function ()

user = qsparse(formData)

resend(rdquolth1gtrdquo + userusername + rdquo 歡迎您的加入 lth1gtltpgt 我們已經將會員啟用信寄至rdquo + useremail + rdquoltpgtrdquo)

)

60 6 Express介紹

Nodejs中文電子書

主要加入了rsquoquerystringrsquo這個moduel方便我們等一下解析由表單 POST回來的資料另外加入一個 formData的變數用來搜集待等一下表單回傳的資料前面的 GET範例我們只從 req拿出 url的資料這次要在利用req身上的事件處理

JavaScript 在訂閱事件時使用 addEventListener而 nodejs 使用的則是on這邊加上了監聽 data的事件會在瀏覽器傳送資料到Web Server時被執行參數是它所接收到的資料型態是字串

接著再增加 end的事件當瀏覽器的請求事件結束時它就會動作

由於瀏覽器使用 POST在上傳資料時會將資料一塊塊地上傳因為我們在監聽 data事件時透過 formData變數將它累加起來lt不過由於我們

上傳的資料很少一次就結束不過如果日後需要傳的是資料比較大的檔

案這個累加動作就很重要

當資料傳完就進到 end 事件中會用到 qsparse 來解析 formDataformData的內容是字串內容是

username=wordsmithampemail=wordsmith40somewhere

而 qsparse可以幫我們把這個 querystring轉成物件的格式也就是

username=wordsmithampemail=wordsmith40somewhere

一旦轉成物件並指定給 user之後其他的事情就和 GET方法時操作的一樣寫 response的表頭將內容回傳並將 userusername和 useremail代入到內容中

修改完成後接著執行 nodejs程式啟動 web server開啟瀏覽器進入表單測試看看POST的方式能否順利運作

完整程式碼如下

var http = require(rsquohttprsquo)

url = require(rsquourlrsquo)

fs = require(rdquofsrdquo)

qs = require(rsquoquerystringrsquo)

server

server = httpcreateServer(function (reqres)

var urlData

67 Express POST應用範例 61

Nodejs中文電子書

encode = rdquoutf8rdquo

filePath = rdquoviewexpress_post_example_formhtmlrdquo

formData

action

urlData = urlparse(requrltrue)

action = urlDatapathname

reswriteHead(200 rdquoContent-Typerdquordquotexthtml charset=utf-8rdquo)

if (action === rdquoSignuprdquo)

formData = rsquorsquo

reqon(rdquodatardquo function (data)

formData += data

)

reqon(rdquoendrdquo function ()

user = qsparse(formData)

resend(rdquolth1gtrdquo + userusername + rdquo 歡迎您的加入 lth1gtltpgt 我們已經將會員啟用信寄至rdquo + useremail + rdquoltpgtrdquo)

)

else

fsreadFile(filePath encode function(err file)

reswrite(file)

resend()

)

)

serverlisten(3000)

consolelog(rsquoServer 跑起來了現在時間是rsquo + new Date())

62 6 Express介紹

Nodejs中文電子書

68 Express AJAX應用範例

在 Nodejs要使用 Ajax傳送資料並且與之互動在接受資料的部份沒有太大的差別client端不是用 GET就是用 POST來傳資料重點在處理完後用 JSON格式回傳當然 Ajax不見得只傳 JSON格式有時是回傳一段 HTML碼不過後者對伺服器來說基本上就和前兩篇沒有差別了所以我們還是以回傳 JSON做為這一回的主題

這一回其實大多數的工作都會落在前端 Ajax上面前端要負責發送與接收資料並在接收資料後撤掉原先發送資料的表單並將取得的資料

改成 HTML格式之後放上頁面

首先先準備 HTML靜態頁面資料

ltDOCTYPE htmlgt

lthtmlgt

ltheadgt

lttitlegtNodejs 菜鳥筆記 (3)lttitlegt

ltlink rel=rdquostylesheetrdquo href=rdquocssstylecssrdquo type=rdquotextcssrdquo media=rdquoallrdquo gt

ltscript src=rdquohttpsajaxgoogleapiscomajaxlibsjquery171jqueryminjsrdquogtltscriptgt

ltheadgt

ltbodygt

lth1gtNodejs 菜鳥筆記-註冊 (用 Ajax)lth1gt

ltform id=rdquosignuprdquo action=rdquoSignuprdquo method=rdquoPOSTrdquogt

ltlabelgt 使用者名稱ltlabelgtltinput type=rdquotextrdquo id=rdquousernamerdquo name=rdquousernamerdquo gtltbrgt

ltlabelgt 電子郵件ltlabelgtltinput type=rdquotextrdquo id=rdquoemailrdquo name=rdquoemailrdquo gtltbrgt

ltinput type=rdquosubmitrdquo value=rdquo 註冊我的帳號rdquo gtltbrgt

ltformgt

ltdiv id=rdquoresultrdquogt

ltdivgt

ltscript type=rdquotextjavascriptrdquogt

$(function()$(rsquosignup input[type=rdquosubmitrdquo]rsquo)click(function(e)

var user =

userusername = $(rdquousernamerdquo)val()useremail = $(rdquoemailrdquo)val()

$post(rdquoSignuprdquouserfunction(data)greet(data)

)

68 Express AJAX應用範例 63

Nodejs中文電子書

epreventDefault()

)

var greet = function(msg)

var resultNode = $(rsquoresultrsquo)greeting = $(rsquolth2gtHirsquo+msgusername+rsquo 你的會員 id 是rsquo+ msgid +rsquo 我們會將會員啟動信寄至rsquo+msgemail+rsquolth2gtrsquo)

$(rsquosignuprsquo)hide()resultNodehtml(rdquordquo)

resultNodeappend(greeting)

)

ltscriptgt

ltbodygt

lthtmlgt

HTML頁面上準備了一個表單用來傳送註冊資料接著直接引用了Google CDN來載入 jQuery用來幫我們處理 Ajax的工作這次要傳送和接收的工作很大的變動都在 HTML頁面上的 JavaScript當中我們要做的事有 (相關 jQuery處理這邊不多做贅述指提起主要功能解說)

bull 用 jQUery取得 submit按鈕綁定它的 click動作

bull 取得表單 username和 email的值存放在 user這個物件中

bull 用 jQuery的 $post方法將 user的資料傳到 Server

bull 一旦成功取得資料後透過 greet這個 function組成回報給 user的訊息

bull 清空原本給使用者填資料的表單

bull 將 Server回傳的 usernameemail和 id這 3個資料組成回應的訊息

bull 將訊息放到原本表單的位置

經過以上的處理後一個 Ajax的表單的基本功能已經完成

接著進行 nodejs主要程式的編輯部分程式碼如下

var fs = require(rdquofsrdquo)

64 6 Express介紹

Nodejs中文電子書

qs = require(rsquoquerystringrsquo)

if (action === rdquoSignuprdquo)

formData = rsquorsquo

reqon(rdquodatardquo function (data)

formData += data

)

reqon(rdquoendrdquo function ()

var msg

user = qsparse(formData)

userid = rdquo123456rdquo

msg = JSONstringify(user)

reswriteHead(200 rdquoContent-Typerdquordquoapplicationjson charset=utf-8rdquordquoContent-Lengthrdquomsglength)

resend(msg)

)

這裡的程式和前面 POST範例基本上大同小異差別在

bull 幫 user的資料加上 id隨意存放一些文字進去讓 Server回傳的資料多於 Client端傳上來的不然會覺得 Server都沒做事

bull 增加了 msg 這個變數存放將 user 物件 JSON 文字化的結果JSONstringify這個轉換函式是 V8引擎所提供的如果你好奇的話

bull 大重點來了我們要告訴 Client端這次回傳的資料格式是 JSON所在 Content-type和 Content-Length要提供給 Client

Server很輕鬆就完成任務了最後進行程式測試啟動 nodejs主程式之後開啟瀏覽器就會看到表單填寫完畢按下送出就可以看到結果了

最後 nodejs本篇範例程式碼如下

var http = require(rsquohttprsquo)

url = require(rsquourlrsquo)

fs = require(rdquofsrdquo)

qs = require(rsquoquerystringrsquo)

server

68 Express AJAX應用範例 65

Nodejs中文電子書

server = httpcreateServer(function (reqres)

var urlData

encode = rdquoutf8rdquo

filePath = rdquoviewexpress_ajax_example_formhtmlrdquo

formData

action

urlData = urlparse(requrltrue)

action = urlDatapathname

if (action === rdquoSignuprdquo)

formData = rsquorsquo

reqon(rdquodatardquo function (data)

formData += data

)

reqon(rdquoendrdquo function ()

var msg

user = qsparse(formData)

userid = rdquo123456rdquo

msg = JSONstringify(user)

reswriteHead(200 rdquoContent-TyperdquordquoapplicationjsonrdquordquoContent-Lengthrdquomsglength)

resend(msg)

)

else

fsreadFile(filePath encode function(err file)

reswriteHead(200 rdquoContent-Typerdquordquotexthtml charset=utf-8rdquo)

reswrite(file)

resend()

)

)

serverlisten(3000)

66 6 Express介紹

Nodejs中文電子書

consolelog(rsquoServer 跑起來了現在時間是rsquo + new Date())

69 原始資料提供

bull [NodeJS初學者筆記 (1)-用 GET傳送資料] (httpithelpithomecomtwquestion10087402)

bull [NodeJS初學者筆記 (2)-用 POST傳送資料] (httpithelpithomecomtwquestion10087489)

bull [NodeJS 初學者筆記 (3)-用 Ajax 傳送資料] (httpithelpithomecomtwquestion10087627)

69 原始資料提供 67

Nodejs中文電子書

68 6 Express介紹

7

CoffeeScript

bull Spine

bull Hem

bull Spineapp

Letrsquos Have a Cup of CoffeeScript

bull es5-shim ECMAScript 5 compatibility shims for legacy JavaScript engines

ESnext

node-iform - A middleware for node-validator httpsgithubcomguileennode-iform

69

Nodejs中文電子書

70 7 CoffeeScript

8

製作一個 Hubot的 Plurk Adapter

81 應用事項提醒

此應用範例以CoffeeScript編寫主要程式架構採用Github釋放的Hubot專案以下有幾個主要注意事項請讀者注意

bull Hubot 一開始的架構除了內建的兩個 Adapter 之外其餘都要以Nodejs的Module方式才能運作

bull Hubot的 binhubot裡面寫著 npm install所以不管你怎麼改原始碼也不能改變第一點的狀況

其實上述都在討論同一件事情NodeJS的模組而且模組不能設定相對路徑之類的來安裝一定要包裝成 npm相符和格式才有辦法正確執行

82 建立 Adapter

首先需要準備兩個檔案

bull packagejson

bull plurkcoffee

71

Nodejs中文電子書

有這兩個就足以變成 NodeJS的模組了在開始 Coding之前先來設定一下 packagejson相依性

rdquonamerdquo rdquohubot-plurkrdquo

rdquoversionrdquo rdquo011rdquo

rdquomainrdquo rdquoplurkrdquo

rdquodependenciesrdquo

rdquohubotrdquo rdquogt=205rdquo

rdquooauthrdquo rdquordquo

rdquocronrdquordquordquo

這邊會使用到NodeJS的 cron模組(Module)而登入 Plurk需要OAuth標準授權才行所以也需要加載 OAuth模組

注意Hubot在讀取非內建模組時會自動在前面加上 hubot-的前置

83 建立 Robot跟 API

本篇應用基本上程式碼大部分參考 Hubot - Twitter Adapter來製作主要差異只有在使用 OAuth這個模組Twitter有 Streaming可用而 Plurk則得用 Comet方式來達到即時讀取

plurkcoffee程式碼

Robot = require(rdquohubotrdquo)robot()

Adapter = require(rdquohubtordquo)adapter()

EventEmitter = require(rdquoeventsrdquo)EventEmitter

oauth = require(rdquooauthrdquo)

cronJob = require(rdquocronrdquo)CronJob

class Plurk exntends Adapter

class PlurkStreaming exnteds EventEmitter

72 8 製作一個 Hubot的 Plurk Adapter

Nodejs中文電子書

先弄個基本架構主要 Class皆參考 Twitter Adapter命名方式接著再將 Plurk這個 Class增加幾個Method基本上只要有 run send reply就夠了而 run用來做初始化的部分

class Plurk entends Adapter

send (plurk id strings⋯) -gt

reply (plurk id strings⋯) -gt

run -gt

看起來有點東西接著來處理主要的 Plurk API結合部分

class PlurkStreaming extends EventEmitter

consuctor (options) -gt

plurk (callback) -gt

觀察河道

getChannel -gt

取得 Comet 網址

reply (plurk id message) -gt

回噗

acceptFriends -gt

接受好友

get (path callback) -gt

GET 請求

post (path body callback)-gt

POST 請求(其實是裝飾)

request (method path body callback)-gt

主要的 OAuth 請求

comet (server callback)-gt

噗浪的 Comet 傳回是 JavaScript Callback 要另外處理後才會變成 JSON

然後把注意力集中到 constructor上先把建構子弄好

constructor (options) -gt

super()

if optionskey and optionssecret and optionstoken and optionstoken secret

key = optionskey

secret = optionssecret

token = optionstoken

token secret = optionstoken secret

83 建立 Robot跟 API 73

Nodejs中文電子書

建立 OAuth 連接

consumer = new oauthOAuth(

rdquohttpwwwplurkcomOAuthrequest tokenrdquo

rdquohttpwwwplurkcomOAuthaccess tokenrdquo

key

secret

rdquo10rdquo

rdquohttpwwwplurkcomOAuthauthorizerdquo

rdquoHMAC-SHA1rdquo

)

domain = rdquowwwplurkcomrdquo

初始化取得 Comet 網址

do getChannel

else

throw new Error(rdquo 參數不足需要 Key Secret Token Token Secretrdquo)

接著來處理 request這個 method

request (method path body callback) -gt

記錄一下這次的 Request

consolelog(rdquohttpdomainpathrdquo)

Callback 這邊先不丟進去要用另一種方式處理

request = consumerget(rdquohttpdomainpathrdquo token token secret null)

requeston rdquoresponserdquo (res) -gt

reson rdquodatardquo (chunk) -gt

parseResponse(chunk+rsquorsquo callback)

reson rdquoendrdquo (data) -gt

consolelog rdquoEnd Request pathrdquo

reson rdquoerrorrdquo (data) -gt

consolelog rdquoError rdquo + data

requestend()

處理資料

parseResponse = (data callback) -gt

if datalength gt 0

用 TryCatch 避免處理 JSON 出錯導致整個中斷

try

callback null JSONparse(data)

catch err

74 8 製作一個 Hubot的 Plurk Adapter

Nodejs中文電子書

consolelog(rdquoError Parse JSONrdquo + data err)

繼續執行

callback null data ||

大致上就是這樣上面程式的架構已經將整個 Hubot Plurk Adapter完成因為在測試時竟然因為噗浪 Lag而沒讀到完整的 Comet資料然後造成程式異常為了避免這個問題發生因此需要加上為了完美呈現需要再

加上 Comet的處理所以要使用到 EventEmitter的功能

comet (server callback) -gt

在 Callback 裡面會找不到自身所以設定區域變數

self =

記錄一下這次的 Request

consolelog(rdquo[Comet] serverrdquo)

Callback 這邊先不丟進去要用另一種方式處理

request = consumerget(rdquohttpdomainpathrdquo token token secret null)

requeston rdquoresponserdquo (res) -gt

reson rdquodatardquo (chunk) -gt

parseResponse(chunk+rsquorsquo callback)

reson rdquoendrdquo (data) -gt

consolelog rdquoEnd Request pathrdquo

請求結束發出事件通知可以進行下一次請求

selfemit rdquonextPlurkrdquo

reson rdquoerrorrdquo (data) -gt

consolelog rdquoError rdquo + data

requestend()

處理資料

parseResponse = (data callback) -gt

if datalength gt 0

用 trycatch 避免失敗中斷

try

去掉 JavaScript 的 Callback

data = datamatch(CometChannelscriptCallback((+))s)

jsonData = rdquordquo

if data

83 建立 Robot跟 API 75

Nodejs中文電子書

jsonData = JSONparse(data[1])

else

如果沒有任何 Match 嘗試直接 parse

jsonData = JSONparse(data)

catch err

consolelog(rdquo[Comet] Errorrdquo data err)

用 TryCatch 避免處理 JSON 出錯導致整個中斷

try

只傳入 json 的 data 部分

callback null jsonDatadata

catch err

consolelog(rdquo[Comet]Error Parse JSONrdquo + data err)

繼續執行

callback null data ||

後面的 get跟 post就簡單多了

get (path callback) -gt

request(rdquoGETrdquo path null callback)

post (path body callback) -gt

request(rdquoPOSTrdquo path body callback)

接著處理取的 Comet網址的 getChannel

getChannel -gt

self =

get rdquoAPPRealtimegetUserChannelrdquo (error data) -gt

if error

檢查是否有 comet server

if datacomet server

selfchannel = datacomet server

如果沒有 Channel Ready 就嘗試連接會失敗

selfemit(rsquochannel readyrsquo)

那麼先來處理 Plurk Adaper好處理的部份

send (plruk id strings⋯)-gt

跟 Reply 一樣直接交給 reply 做

reply plurk id strings⋯

76 8 製作一個 Hubot的 Plurk Adapter

Nodejs中文電子書

reply (plurk id strings⋯) -gt

stringsforEach (message) =gt

botreply(plruk id message)

接著把 run處理好就可以上線運作摟

run -gt

self =

options =

key processenvHUBOT PLURK KEY

secret processenvHUBOT PLURK SECRET

token processenvHUBOT PLURK TOKEN

token secret processenvHUBOT PLURK TOKEN SECRET

創建剛剛的 API

bot = new PlurkStreaming(options)

依照 Twitter 的 new RobotTextMessage 會沒有反應所以參考 hubot-minecraft 的方式

r = robotconstructor

處理噗浪河道訊息

doPlurk = (data)-gt

檢查是否為回噗

if dataresponse

datacontent raw = dataresponsecontent raw

datauser id = dataresponseuser id

確定有噗浪 ID 跟訊息

if dataplurk id and datacontent raw

selfreceive new rTextMessage(dataplurk id datacontent raw)

取得 Comet Server 完成開始第一次 Comet 連接

boton rdquochannel readyrdquo () -gt

botplurk selfdoPlurk

上一次 Comet 完成繼續 Polling

boton rdquonextPlurkrdquo ()-gt

botplurk selfdoPlurk

定時接受好友邀請

do botacceptFriends

bot = bot

83 建立 Robot跟 API 77

Nodejs中文電子書

終於完成 AdapterHubot專案裡面的 scripts資料夾內是互動部分不需要像 Adapter如此大費周章處理只新增檔案並且設計好對白之後就會回噗了我開發用的機器人在此大家可以去跟他玩玩[httpplurkcomelct9620 bot](httpplurkcomelct9620 bot)

84 原始資料提供

bull [製 作 一 個 Hubot 的 噗 浪 Adapter](http revo-skillfrosttw blog20120318create-a-hubot-plurk-adapter)

78 8 製作一個 Hubot的 Plurk Adapter

9

Nodejs好用工具介紹

91 logio

Real-time log monitoring in your browser

httplogioorg

nodejs (gt=0412 lt=0611)

npm config set unsafe-perm true

npm install -g --prefix=usrlocal logio

logio server start

httplocalhost8998

79

Nodejs中文電子書

80 9 Nodejs好用工具介紹

10

精選文章

101 Express

bull 使用 NodeJS + Express從 GETPOST Request取值 [邀稿中]

bull link2 [邀稿中]

bull link3 [邀稿中]

81

Nodejs中文電子書

82 10 精選文章

11

參考資源

111 Nodejs書籍

bull Programming Nodejs April 2012 (est) Travis Swicegood Pragmatic Bookshelf

bull e Little Book on CoffeeScript January 2012 Alex MacCaw OrsquoReilly Media

bull Node for Front-End Developers January 2012 Garann Means OrsquoReilly Media

bull Building Hypermedia APIs with HTML5 and Node November 2011 MikeAmundsen OrsquoReilly Media

bull Node Web Development August 2011 David Herron PacktPub

bull CoffeeScript Accelerated JavaScript Development July 2011 Trevor BurnhamPragmatic Bookshelf

bull Getting Started with GEO CouchDB and Nodejs July 2011 Mick omp-son OrsquoReilly Media

bull What Is Node July 2011 Brett McLaughlin OrsquoReilly Media

bull Node Up and Running May 2011 Tom Hughes-Croucher Mike WilsonOrsquoReilly Media

83

Nodejs中文電子書

bull Hands-on Nodejs e Nodejs introduction and API reference May 2011 Pe-dro Teixeira LeanPub

bull e Node Beginner Book Manuel Kiessling

bull Mastering Nodejs visionmedia

112 Nodejs影音教學

bull e Node Sessions e Best of OSCON 2011 August 2011 OrsquoReilly Media(Video)

bull Tom Hughes-Croucher on Node March 2011 OrsquoReilly Media (Video)

113 Nodejs教學網站

bull Node Tuts

114 Nodejs課程

bull Introduction To Nodejs Van Nguyen CodeLesson

84 11 參考資源

Page 27: Nodejs Wiki

Nodejs中文電子書

done(results)

)

var a = new Wait(

[ function(waitback) consolelog(lsquodone arsquo) var result = 500 wait-back(result) function(waitback) consolelog(lsquodone brsquo) var result =1000 waitback(result) function(waitback) consolelog(lsquodone crsquo) varresult = 1500 waitback(result) ] function(results) var ret = 0 i=0for( iltresultslength i++) ret += results[i] consolelog(lsquodone allresult lsquo+ret)

) var callbacks = [agetCallback(0)agetCallback(1)agetCallback(0)agetCallback(2)]一次取出要使用的 callbacks避免結果提早送出 setTimeout(callbacks[0]500) setTimeout(callbacks[1] 1000) setTimeout(callbacks[2] 1500) setTime-out(callbacks[3] 2000) 當所有取出的 callbacks執行完畢就呼叫 done()來處理結果 ltscriptgt

執行結果

done a done b done a done c done all result 3500

上面只是一些小實驗更成熟的作品是 Tim Caswell 的 stephttpsgithubcomcreationixstep

如果希望真正使用同步的方式寫非同步則需要使用 Promisejs這一類的 library來轉換非同步函數不過他結構比較複雜 XD(見仁見智不過有些人認為 Promise有點過頭了)httpblogsmsdncombrbucktonarchive20110815promise-js-2-0-promise-framework-for-javascriptaspx

如果想不透過其他 Library做轉換又能直接用同步方式執行非同步函數大概就要使用一些需要額外 compile原始程式碼的方法了例如 BrunoJouhier的 streamlinejshttpsgithubcomSagestreamlinejs

26 流程控制 21

Nodejs中文電子書

循序執行

循序執行可以協助把非常深的巢狀 callback結構攤平例如用這樣的簡單模組來做(serialjs)

moduleexports = function(funs) var c = 0 if(isArrayOfFunctions(funs))

throw(lsquoArgument type was not matched Should be array of func-tionsrsquo)

return function()

var args = Arrayprototypeslicecall(arguments 0) if((cgt=funslength))

c++ return funs[c-1]apply(this args)

function isArrayOfFunctions(f ) if(typeof f == lsquoobjectrsquo) return false if(flength)return false if(fconcat) return false if(fsplice) return false var i = 0 for(iltflength i++)

if(typeof f[i] == lsquofunctionrsquo) return false

return true

簡單的測試範例(testSerialjs)使用 fs 模組確定某個 path 是檔案然後讀取印出檔案內容這樣會用到兩層的 callback所以測試中有使用serial的版本與 nested callbacks的版本做對照

var serial = require(lsquoserialrsquo) fs = require(lsquofsrsquo) path = lsquodclientjsrsquo cb = serial([ func-tion(err data)

if(err)

if(dataisFile) fsreadFile(path cb)

22 2 JavaScript與 NodeJS

Nodejs中文電子書

else consolelog(err)

function(err data)

if(err) consolelog(lsquo[ attened by searial]rsquo) con-solelog(datatoString(lsquoutf8rsquo))

else consolelog(err)

]) fsstat(path cb)

fsstat(path function(err data) 第一層 callback if(err)

if(dataisFile)

fsreadFile(path function(err data) 第二層 callback if(err)

consolelog(lsquo[nested callbacks]rsquo) con-solelog(datatoString(lsquoutf8rsquo))

else consolelog(err)

)

else consolelog(err)

)

關鍵在於這些 callback的執行是有順序性的所以利用 serial返回的一個函數 cb來取代這些 callback然後在 cb中控制每次會循序呼叫的函數

26 流程控制 23

Nodejs中文電子書

就可以把巢狀的 callback攤平成循序的 function陣列(就是傳給 serial函數的參數)

測試中的dclientjs 是一個簡單的 dnode 測試程式放在跟 testSerialjs同一個目錄

var dnode = require(lsquodnodersquo)

dnodeconnect(8000 lsquolocalhostrsquo function(remote)

remoterestart(function(str) consolelog(str) processexit()

)

)

執行測試程式後出現結果

[ attened by searial] var dnode = require(lsquodnodersquo)

dnodeconnect(8000 lsquolocalhostrsquo function(remote)

remoterestart(function(str) consolelog(str) processexit()

)

)

[nested callbacks] var dnode = require(lsquodnodersquo)

dnodeconnect(8000 lsquolocalhostrsquo function(remote)

remoterestart(function(str) consolelog(str) processexit()

)

)

對照起來看兩種寫法的結果其實是一樣的但是利用 serialjs巢狀的 callback結構就會消失

不過這樣也只限於順序單純的狀況如果函數執行的順序比較複雜

(不只是一直線)還是需要用功能更完整的流程控制模組比較好例如

httpsgithubcomcaolanasync

24 2 JavaScript與 NodeJS

3

Nodejs安裝與設定

本篇將講解如何在各個不同 OS建立 NodeJS環境目前 NodeJS 048版本環境架設方式需依賴 Linux指令才可編譯完成當然在不同作業系統中也已經有 NodeJS package可以直接使用指令快速架設以下各不同作業系統解說如何安裝 NodeJS

31 Ubuntu Linux

更新推薦使用 nvm

git clone gitgithubcomcreationixnvmgit ~nvm

echo rdquo ~nvmnvmshrdquo gtgt ~bashrc

nvm install v0614

nvm alias default v0614

以 上 可 參 考 http dreamerslabcom blog tw how-to-setup-a-node-js-development-environment-on-ubuntu-11-04

使用 APT套件管理工具是常見的方法以下是使用社群提供的 PPA安裝方式

sudo apt-get install python-software-properties

sudo add-apt-repository ppachris-leanodejs-devel

25

Nodejs中文電子書

sudo apt-get update

sudo apt-get install nodejs

32 Other Linux

Linux很適合作為 NodeJS的伺服器作業系統及開發環境安裝前請先確認以下套件已正確安裝

bull curl (wget)用來下載檔案的工具

bull git先進的版本控制工具

bull g++ GNU C++軟體編譯工具

bull make GNU軟體專案建置工具

安裝指令如下如設有權限問題請在指令前面加上 sudo

git clone httpsgithubcomjoyentnodegit

cd node

git checkout v067

configure

make

sudo make install

接著測試 nodeJS是否正常執行

node --version

出現版本訊息即表示安裝成功

33 Windows

nodeJS 在 v060 版本之後開始正式支援 windows native直接使用nodeexe 就可以執行程式支援性完全與 linux 相同更棒的部份就是不需經過編譯經過下載之後簡單設定完成立即開發 node程式

26 3 Nodejs安裝與設定

Nodejs中文電子書

下載 nodejs安裝檔案 lthttpnodejsorgdownloadgt

如此完成 windows native nodeexe安裝接著可以進入 command line執行測試在 command line輸入rdquonoderdquo會出現 nodejs版本訊息畫面表示安裝完成

33 Windows 27

Nodejs中文電子書

28 3 Nodejs安裝與設定

4

Nodejs基礎

前篇文章已經由介紹安裝至設定都有完整介紹nodeJS 內部除了javascript常用的函式 (function)物件 (object)之外也有許多不同的自訂物件nodeJS預設建立這些物件為核心物件是為了要讓開發流程更為這些資料在官方文件已經具有許多具體說明接下來將會介紹在開發 nodeJS程式時常見的物件特性與使用方法

41 nodejs http伺服器建立

在 lsquonodejs官方網站 lthttpnodejsorggtlsquo裡面有舉一個最簡單的 HTTP伺服器建立一開始初步就是建立一個伺服器平台讓 nodejs可以與瀏覽器互相行為每種語言一開始的程式建立都是以 Hello world開始最初也從 Hello world帶各位進入 nodejs的世界

輸入以下程式碼儲存檔案為 node basic http hello worldjs

var server

ip = rdquo127001rdquo

port = 1337

http = require(rsquohttprsquo)

server = httpcreateServer(function (req res)

29

Nodejs中文電子書

reswriteHead(200 rsquoContent-Typersquo rsquotextplainrsquo)

resend(rsquoHello Worldnrsquo)

)

serverlisten(port ip)

consolelog(rdquoServer running at httprdquo + ip + rdquordquo + port)

程式碼解講一開始需要有幾個基本的變數

bull ip 機器本身的 ip位置因為使用本地端因此設定為 127001

bull port 需要開通的阜號通常設定為 http port 80因範例不希望與基本 port相衝隨意設定為 1337

在 nodejs 的程式中有許多預設的模組可以使用因此需要使用require方法將模組引入在這邊我們需要使用 http這個模組因此將 http載入Http模組裡面內建有許多方法可以使用這邊採用 createServer創建一個基本的 http伺服器再將 http伺服器給予一個 server變數裡面的回呼函式 (call back function)可以載入 http伺服器的資料與回應方法 (requestresponse)在程式裡面就可以看到我們直接回應給瀏覽器端所需的 Header回應內容

reswriteHead(200 rsquoContent-Typersquo rsquotextplainrsquo)

resend(rsquoHello Worldnrsquo)

Http伺服器需要設定 port ip在最後需要設定 Http監聽需要使用到listen事件監聽所有 Http伺服器行為

httplisten(port ip)

所有事情都完成之後需要確認伺服器正確執行因此使用 console在javascript裡就有這個原生物件console所印出的資料都會顯示於 nodejs伺服器頁面這邊印出的資料並不會傳送到使用者頁面上之後許多除壞

(debug)都會用到 console物件

consolelog(rdquoServer running at httprdquo + ip + rdquordquo + port)

30 4 Nodejs基礎

Nodejs中文電子書

42 nodejs http路徑建立

前面已經介紹如何建立一個簡單的 http伺服器接下來本章節將會介紹如何處理伺服器路徑 (rout)問題在 http的協定下所有從瀏覽器發出的要求 (request)都需要經過處理路徑上的建立也是如此

路徑就是指伺服器 ip位置或者是網域名稱之後對於伺服器給予的要求修改剛才的 hello world檔案修改如下

server = httpcreateServer(function (req res)

consolelog(requrl)

reswriteHead(200 rsquoContent-Typersquo rsquotextplainrsquo)

resend(rsquohello worldnrsquo)

)

重新啟動 nodejs程式後在瀏覽器端測試一下路徑行為結果如下圖

當在瀏覽器輸入 http1270011337test 在伺服器端會收到兩個要求一個是我們輸入的test要求另外一個則是faviconicotest的路徑要求http伺服器本身需要經過程式設定才有辦法回應給瀏覽器端所需要的回應在伺服器中所有的路徑要求都是需要被解析才有辦法取得資料從

42 nodejs http路徑建立 31

Nodejs中文電子書

上面解說可以了解到在 nodejs當中所有的路徑都需要經過設定未經過設定的路由會讓瀏覽器無法取得任何資料導致錯誤頁面的發生底下將會解

說如何設定路由同時避免發生錯誤情形先前 nodejs程式需要增加一些修改才能讓使用者透過瀏覽器在不同路徑時有不同的結果根據剛才

的程式做如下的修改

var server

ip = rdquo127001rdquo

port = 1337

http = require(rsquohttprsquo)

url = require(rsquourlrsquo)

path

server = httpcreateServer(function (req res)

path = urlparse(requrl)

reswriteHead(200 rsquoContent-Typersquo rsquotextplainrsquo)

switch (pathpathname)

case rdquoindexrdquo

resend(rsquoI am indexnrsquo)

break

case rdquotestrdquo

resend(rsquothis is test pagenrsquo)

break

default

resend(rsquodefault pagenrsquo)

break

)

serverlisten(port ip)

consolelog(rdquoServer running at httprdquo + ip + rdquordquo + port)

程式做了片段的修改首先載入 url模組另外增加一個 path變數url模組就跟如同他的命名一般專門處理 url字串處理裡面提供了許多方法來解決路徑上的問題因為從瀏覽器發出的要求路徑可能會帶有多種需求

或者 GET參數組合等因此我們需要將路徑單純化取用路徑部分的資料即可例如使用者可能會送出 http1270011337testsend=1如果直接

32 4 Nodejs基礎

Nodejs中文電子書

信任 requrl就會收到結果為testsend=1所以需要透過 url模組的方法將路徑資料過濾

在這邊使用 urlparse的方法裡面帶入網址格式資料會回傳路徑資料為了後需方便使用將回傳的資料設定到 path變數當中在回傳的路徑資料裡面包含資訊如下圖

這邊只需要使用單純的路徑要求直接取用 pathpathname就可以達到我們的目的

最後要做路徑的判別在不同的路徑可以指定不同的輸出在範例中

有三個可能結果第一個從瀏覽器輸入index就會顯示 index結果test 就會呈現出 test頁面最後如果都不符合預期的輸入會直接顯示 default的頁面最後的預防可以讓瀏覽器不會出現非預期結果讓程式的可靠性提昇

底下為測試結果

42 nodejs http路徑建立 33

Nodejs中文電子書

43 nodejs檔案讀取

前面已經介紹如何使用路由(rount)做出不同的回應實際應用只有在瀏覽器只有輸出幾個文字資料總是不夠的在本章節中將介紹如何使

用檔案讀取輸出檔案資料讓使用者在前端瀏覽器也可以讀取到完整的

html css javascript檔案輸出

檔案管理最重要的部分就是 lsquoFile system lthttpnodejsorgdocslatestapifshtmlgtlsquo這個模組此模組可以針對檔案做管理監控讀取等行為裡面有許多預設的方法底下是檔案輸出的基本範例底下會有兩個檔案

第一個是靜態 html檔案

ltDOCTYPE htmlgt

lthtml xmlns=rdquohttpwwww3org1999xhtmlrdquo xmllang=rdquozh-CNrdquo lang=rdquozh-CNrdquogt

ltheadgt

ltmeta http-equiv=rdquoContent-Typerdquo content=rdquotexthtml charset=UTF-7rdquo gt

lttitlegtnodejs index html filelttitlegt

ltheadgt

ltbodygt

34 4 Nodejs基礎

Nodejs中文電子書

lth1gtnodejs index html filelth1gt

ltbodygt

lthtmlgt

另一個為 nodejs程式

var fs = require(rdquofsrdquo)

filename = rdquostaticindexhtmlrdquo

encode = rdquoutf8rdquo

fsreadFile(filename encode function(err file)

consolelog(file)

)

一開始直接載入 le system模組 載入名稱為 fs讀取檔案主要使

用的方法為 readFile裡面以三個參數路徑 ( le path) 編碼方式 (encoding)

回應函式 (callback)路徑必須要設定為靜態 html所在位置才能指定到正確的檔案靜態檔案的編碼方式也必須正確這邊使用靜態檔案的編

碼為 utf8如果編碼設定錯誤nodejs讀取出來檔案結果會使用 byte raw格式輸出如果錯誤編碼格式會導致輸出資料為 byte raw

回應函式中裡面會使用兩個變數error為錯誤資訊如果讀取的檔案不存在或者發生錯誤error數值會是 true如果成功讀取資料 error將會是 falsecontent則是檔案內容資料讀取後將會把資料全數丟到 content這個變數當中

最後程式的輸出結果畫面如下

43 nodejs檔案讀取 35

Nodejs中文電子書

44 nodejs http靜態檔案輸出

前面已經了解如何讀取本地端檔案接下來將配合 http伺服器路由讓每個路由都能夠輸出相對應的靜態 html檔案首先新增加幾個靜態 html檔案

ltDOCTYPE htmlgt

lthtml xmlns=rdquohttpwwww3org1999xhtmlrdquo xmllang=rdquozh-CNrdquo lang=rdquozh-CNrdquogt

ltheadgt

ltmeta http-equiv=rdquoContent-Typerdquo content=rdquotexthtml charset=UTF-7rdquo gt

lttitlegtnodejs index html filelttitlegt

ltheadgt

ltbodygt

lth1gtnodejs index html filelth1gt

ltbodygt

lthtmlgt

ltDOCTYPE htmlgt

lthtml xmlns=rdquohttpwwww3org1999xhtmlrdquo xmllang=rdquozh-CNrdquo lang=rdquozh-CNrdquogt

ltheadgt

ltmeta http-equiv=rdquoContent-Typerdquo content=rdquotexthtml charset=UTF-7rdquo gt

lttitlegtnodejs test html filelttitlegt

ltheadgt

ltbodygt

lth1gtnodejs test html filelth1gt

ltbodygt

lthtmlgt

ltDOCTYPE htmlgt

lthtml xmlns=rdquohttpwwww3org1999xhtmlrdquo xmllang=rdquozh-CNrdquo lang=rdquozh-CNrdquogt

ltheadgt

ltmeta http-equiv=rdquoContent-Typerdquo content=rdquotexthtml charset=UTF-7rdquo gt

lttitlegtnodejs static html filelttitlegt

ltheadgt

ltbodygt

lth1gtnodejs static html filelth1gt

ltbodygt

lthtmlgt

36 4 Nodejs基礎

Nodejs中文電子書

準備一個包含基本路由功能的 http伺服器

var server

ip = rdquo127001rdquo

port = 1337

http = require(rsquohttprsquo)

url = require(rsquourlrsquo)

server = httpcreateServer(function (req res)

var path = urlparse(requrl)

)

serverlisten(port ip)

consolelog(rdquoServer running at httprdquo + ip + rdquordquo + port)

加入 le system 模組使用 readFile 的功能將這一段程式放置於

createServer的回應函式中

fsreadFile(filePath encode function(err file)

)

readFile的回應函式裡面加入頁面輸出讓瀏覽器可以正確讀到檔案在這邊我們設定讀取的檔案為 html 靜態檔案所以 Content-type 設定為texthtml讀取到檔案的內容將會正確輸出成 html靜態檔案

fsreadFile(filePath encode function(err file)

reswriteHead(200 rsquoContent-Typersquo rsquotexthtmlrsquo)

reswrite(file)

resend()

)

到這邊為止基本的程式內容都已經完成剩下一些細節的調整首先

路徑上必須做調整目前的靜態檔案全部都放置於 static資料夾底下設定一個變數來記住資料夾位置

接著將瀏覽器發出要求路徑與資料夾組合讀取正確 html靜態檔案使用者有可能會輸入錯誤路徑所以在讀取檔案的時候要加入錯誤處理

同時回應 404伺服器無法正確回應的 http header格式

加入這些細節的修改一個基本的 http 靜態 html輸出伺服器就完成了完整程式碼如下

44 nodejs http靜態檔案輸出 37

Nodejs中文電子書

var server

ip = rdquo127001rdquo

port = 1337

http = require(rsquohttprsquo)

fs = require(rdquofsrdquo)

folderPath = rdquostaticrdquo

url = require(rsquourlrsquo)

path

filePath

encode = rdquoutf8rdquo

server = httpcreateServer(function (req res)

path = urlparse(requrl)

filePath = folderPath + pathpathname

fsreadFile(filePath encode function(err file)

if (err)

reswriteHead(404 rsquoContent-Typersquo rsquotextplainrsquo)

resend()

return

reswriteHead(200 rsquoContent-Typersquo rsquotextapplicationrsquo)

reswrite(file)

resend()

)

)

serverlisten(port ip)

consolelog(rdquoServer running at httprdquo + ip + rdquordquo + port)

45 nodejs http GET資料擷取

http伺服器中除了路由之外另一個最常使用的方法就是擷取 GET資料本單元將會介紹如何透過基本 http伺服器擷取瀏覽器傳來的要求擷取 GET資料

在 http協定中GET參數都是藉由 URL從瀏覽器發出要求送至伺服器

38 4 Nodejs基礎

Nodejs中文電子書

端基本的傳送網址格式可能如下

http127001testsend=1amptest=2

上面這段網址裡面的 GET參數就是 send而這個變數的數值就為 1如果想要在 http伺服器取得 GET資料需要在瀏覽器給予的要求 (request)做處理

首先需要載入 query string這個模組這個模組主要是用來將字串資料

過濾後轉換成javascript物件

qs = require(rsquoquerystringrsquo)

接著在第一階段利用 url模組過濾瀏覽器發出的 URL資料後將回應的物件裡面的 query這個變數是一個字串值資料過濾後如下

send=1amptest=2

透過 query string使用 parse這個方法將資料轉換成 javascript物件就表示 GET的資料已經被伺服器端正式擷取下來

path = urlparse(requrl)

parameter = qsparse(pathquery)

整個 nodejs http GET參數完整擷取程式碼如下

var server

ip = rdquo127001rdquo

port = 1337

http = require(rsquohttprsquo)

qs = require(rsquoquerystringrsquo)

url = require(rsquourlrsquo)

server = httpcreateServer(function (req res)

var path = urlparse(requrl)

parameter = qsparse(pathquery)

consoledir(parameter)

reswriteHead(200 rsquoContent-Typersquo rsquotextplainrsquo)

reswrite(rsquoBrowser test GET parameternrsquo)

resend()

)

45 nodejs http GET資料擷取 39

Nodejs中文電子書

serverlisten(port ip)

consolelog(rdquoServer running at httprdquo + ip + rdquordquo + port)

程式運作之後由瀏覽器輸入要求網址之後nodejs伺服器端回應資料為

Server running at http1270011337

send rsquo1rsquo test rsquo1rsquo

46 本章結語

前面所解說的部份一大部分主要是處理 http伺服器基本問題雖然在某些部分有牽扯到 http 伺服器基本運作原理主要還是希望可以藉由這些基本範例練習 nodejs練習回應函式與語法串接的特點習慣編寫javascript風格程式當然直接這樣開發 nodejs是非常辛苦的接下來在模組實戰開發的部份將會介紹特定的模組一步一步帶領各位從無到有進行

nodejs應用程式開發

40 4 Nodejs基礎

5

NPM套件管理工具

npm全名為 Node Package Manager是 Nodejs的套件(package)管理工具類似 Perl的 ppm或 PHP的 PEAR等安裝 npm後使用npm install

module name指令即可安裝新套件維護管理套件的工作會更加輕鬆

npm可以讓Nodejs的開發者直接利用擴充線上的套件庫(packagesregistry)加速軟體專案的開發npm提供很友善的搜尋功能可以快速找到安裝需要的套件當這些套件發行新版本時npm也可以協助開發者自動更新這些套件

npm不僅可用於安裝新的套件它也支援搜尋列出已安裝模組及更新的功能

51 安裝 NPM

Nodejs在 063版本開始內建 npm讀者安裝的版本若是此版本或更新的版本就可以略過以下安裝說明

若要檢查 npm是否正確安裝可以使用以下的指令

npm -v

41

Nodejs中文電子書

執行結果說明

若 npm正確安裝執行 npm -v將會看到類似 110-2的版本訊息

若讀者安裝的 Nodejs版本比較舊或是有興趣嘗試自己動手安裝 npm工具則可以參考以下的說明

安裝於Windows系統

Nodejs for Windows 於 062 版開始內建 npm使用 nodejsorg 官方提供的安裝程式不需要進一步的設定就可以立即使用 npm指令對於Windows的開發者來說大幅降低環境設定的問題與門檻

除了使用 Nodejs內建的 npm讀者也可以從 npm官方提供的以下網址

httpnpmjsorgdist

這是由 npm提供的 Fancy Windows Install版本請下載壓縮檔(例如npm-110-3zip)並將壓縮檔內容解壓縮至 Nodejs的安裝路徑(例如CProgram Filesnodejs)

解壓縮後在 Nodejs的安裝路徑下應該有以下的檔案及資料夾

bull npmcmd(檔案)

bull node modules(資料夾)

安裝於 Linux系統

Ubuntu Linux的使用者可以加入 NPM Unoffcial PPA這個 repository即可使用 apt-get完成 npm安裝

42 5 NPM套件管理工具

Nodejs中文電子書

Ubuntu Linux使用 apt-get安裝 npm

sudo apt-get install python-software-properties

sudo add-apt-repository ppagias-kay-leenpm

sudo apt-get update

sudo apt-get install npm

npm官方提供的安裝程式 installsh可以適用於大多數的 Linux系統使用這個安裝程式請先確認

1 系統已安裝 curl工具(請使用 curl --version查看版本訊息)

2 已安裝 Nodejs並且 PATH正確設置

3 Nodejs的版本必須大於 04x

以下為 npm提供的安裝指令

curl httpnpmjsorginstallsh | sh

安裝成功會看到如下訊息

installsh安裝成功的訊息

npm10105 homeUSERNAMElocalnodelibnode_modulesnpm

It worked

安裝於Mac OS X

建議採用與 Nodejs相同的方式進行 npm的安裝例如使用MacPorts安裝 Nodejs就同樣使用MacPorts安裝 npm這樣對日後的維護才會更方便容易

使用 MacPorts安裝 npm是本書比較建議的方式它可以讓 npm的安裝移除及更新工作自動化將會幫助開發者節省寶貴時間

51 安裝 NPM 43

Nodejs中文電子書

安裝MacPorts的提示

在MacPorts網站可以取得 OS X系統版本對應的安裝程式(例如 106或 107)httpwwwmacportsorg安裝過程會詢問系統管理者密碼使用預設的選項完成安裝即可安

裝MacPorts之後在終端機執行 port -v將會看到MacPorts的版本訊息

安裝 npm之前先更新MacPorts的套件清單以確保安裝的 npm是最新版本

sudo port -d selfupdate

接著安裝 npm

sudo port install npm

若讀者的 Nodejs並非使用MacPorts安裝則不建議使用MacPorts安裝npm因為 MacPorts會自動檢查並安裝相依套件而 npm相依 nodejs所以MacPorts也會一併將 nodejs套件安裝造成先前讀者使用其它方式安裝的 nodejs被覆蓋

讀者可以先使用 MacPorts安裝 curl(sudo port install curl)

再參考 Linux的 installsh安裝方式即可使用 npm官方提供的安裝程式

NPM安裝後測試

npm是指令列工具(command-line tool)使用時請先打開系統的文字終端機工具

測試 npm安裝與設定是否正確請輸入指令如下

npm -v

或是

npm --version

如果 npm已經正確安裝設定就會顯示版本訊息

44 5 NPM套件管理工具

Nodejs中文電子書

執行結果(範例)

110-2

52 使用 NPM安裝套件

npm目前擁有超過 6000種套件(packages)可以在 npm registry使用關鍵字搜尋套件

httpsearchnpmjsorg

舉例來說在關鍵字欄位輸入「coffee-script」下方的清單就會自動列出包含 coffee-script關鍵字的套件

接著我們回到終端機模式的操作npm的指令工具本身就可以完成套

件搜尋的任務

例如以下的指令同樣可以找出 coffee-script相關套件

npm search coffee-script

以下是搜尋結果的參考畫面

52 使用 NPM安裝套件 45

Nodejs中文電子書

找到需要的套件後(例如 express)即可使用以下指令安裝

npm install coffee-script

值得注意的一點是使用 npm install會將指定的套件安裝在工

作目錄(Working Directory)的 node modules資料夾下

以Windows為例如果執行 npm install的目錄位於

Cproject1

那麼 npm將會自動建立一個 node modules的子目錄(如果不存在)

Cproject1node modules

並且將下載的套件放置於這個子目錄例如

Cproject1node modulescoffee-script

這個設計讓專案可以個別管理相依的套件並且可以在專案佈署或發

行時將這些套件(位於 node modules)一併打包方便其它專案的使用者不必再重新下載套件

這個 npm install的預設安裝模式為 local(本地)只會變更當前專案的資料夾不會影響系統

另一種安裝模式稱為 global(全域)這種模式會將套件安裝到系統資

料夾也就是 npm安裝路徑的 node modules資料夾例如

CProgram Filesnodejsnode modules

46 5 NPM套件管理工具

Nodejs中文電子書

是否要使用全域安裝可以依照套件是否提供新指令來判斷舉例來說express 套件提供 express 這個指令而 coffee-script 則提供 coffee

指令

在 local 安 裝 模 式 中這 些 指 令 的 程 式 檔 案會 被 安 裝 到node modules的 bin這個隱藏資料夾下除非將bin的路徑加入 PATH環境變數否則要執行這些指令將會相當不便

為了方便指令的執行我們可以在 npm install 加上 -g 或 --

global參數啟用 global安裝模式例如

npm install -g coffee-script

npm install -g express

使用 global安裝模式需要注意執行權限的問題若權限不足可能會出現類似以下的錯誤訊息

npm ERR Error EACCES permission denied rsquorsquo

npm ERR

npm ERR Please try running this command again as rootAdministrator

要獲得足夠得執行權限請參考以下說明

bull Windows 7 或 2008 以上在「命令提示字元」的捷徑按右鍵選擇「以系統管理員身分執行」執行 npm指令時就會具有 Administrator身分

bull Mac OS X或 Linux系統可以使用 sudo指令例如

sudo npm install -g express

bull Linux系統可以使用 root權限登入或是以「sudo su -」切換成 root身分(使用 root權限操作系統相當危險因此並不建議使用這種方式)

若加上 -g參數使用 npm install -g coffee-script完成安裝

後就可以在終端機執行 coffee指令例如

coffee -v

52 使用 NPM安裝套件 47

Nodejs中文電子書

執行結果(範例)

CoffeeScript version 120

53 套件的更新及維護

除了前一節說明的 search 及 install 用法npm 還提供其他許多指令(commands)

使用 npm help可以查詢可用的指令

npm help

執行結果(部分)

where ltcommandgt is one of

adduser apihelp author bin bugs c cache completion

config deprecate docs edit explore faq find get

help help-search home i info init install la link

list ll ln login ls outdated owner pack prefix

prune publish r rb rebuild remove restart rm root

run-script s se search set show star start stop

submodule tag test un uninstall unlink unpublish

unstar up update version view whoami

使用 npm help command可以查詢指令的詳細用法例如

npm help list

接下來本節要介紹開發過程常用的 npm指令

使用 list可以列出已安裝套件

npm list

48 5 NPM套件管理工具

Nodejs中文電子書

執行結果(範例)

-- coffee-script120

- express256

- connect185

| -- formidable108

-- mime124

-- mkdirp007

-- qs041

檢視某個套件的詳細資訊例如

npm show express

升級所有套件(如果該套件已發佈更新版本)

npm update

升級指定的套件

npm update express

移除指定的套件

npm uninstall express

54 使用 packagejson

對於正式的 Nodejs專案可以建立一個命名為 packagejson的設

定檔(純文字格式)檔案內容參考範例如下

54 使用 packagejson 49

Nodejs中文電子書

packagejson(範例)

rdquonamerdquo rdquoapplication-namerdquo

rdquoversionrdquo rdquo001rdquo

rdquoprivaterdquo true

rdquodependenciesrdquo

rdquoexpressrdquo rdquo255rdquo

rdquocoffee-scriptrdquo rdquolatestrdquo

rdquomongooserdquo rdquogt= 253rdquo

其中 name與 version依照專案的需求設置

需要注意的是 dependencies的設定它用於指定專案相依的套件名

稱及版本

bull rdquoexpressrdquo rdquo255rdquo

代表此專案相依版本 255的 express套件

bull rdquocoffee-scriptrdquo rdquolatestrdquo

使用最新版的 coffee-script套件(每次更新都會檢查新版)

bull rdquomongooserdquo rdquogt= 253rdquo

使用版本大於 253的 mongoose套件

假設某個套件的新版可能造成專案無法正常運作就必須指定套件的

版本避免專案的程式碼來不及更新以相容新版套件通常在開發初期的

專案需要盡可能維持新套件的相容性(以取得套件的更新或修正)可以

用「gt=」設定最低相容的版本或是使用「latest」設定永遠保持最新

套件

50 5 NPM套件管理工具

6

Express介紹

在前面的 nodejs基礎當中介紹許多許多開設 http的使用方法及介紹以及許多基本的 nodejs基本應用

接下來要介紹一個套件稱為 express [Express](httpexpressjscom)這個套件主要幫忙解決許多 nodejs http server所需要的基本服務讓開發 httpservice變得更為容易不需要像之前需要透過層層模組(module)才有辦法開始編寫自己的程式

這個套件是由 TJ Holowaychuk 製作而成的套件裡面包含基本的路由處理 (route)http資料處理(GETPOSTPUT)另外還與樣板套件(jshtml template engine)搭配同時也可以處理許多複雜化的問題

61 Express安裝

安裝方式十分簡單只要透過之前介紹的 NPM就可以使用簡單的指令安裝指令如下

這邊建議需要將此套件安裝成為全域模組方便日後使用

51

Nodejs中文電子書

62 Express基本操作

express的使用也十分簡單先來建立一個基本的 hello world

var app = require(rsquoexpressrsquo)createServer()

port = 1337

applisten(port)

appget(rsquorsquo function(req res)

ressend(rsquohello worldrsquo)

)

consolelog(rsquostart express servernrsquo)

可以從上面的程式碼發現基本操作與 nodejs http的建立方式沒有太大差異主要差在當我們設定路由時可以直接透過 appget方式設定回應與接受方式

63 Express路由處理

Express對於 http服務上有許多包裝讓開發者使用及設定上更為方便例如有幾個路由設定那我們就統一藉由 appget來處理

Create http server

appget(rsquorsquo function(req res)

ressend(rsquohello worldrsquo)

)

appget(rsquotestrsquo function(req res)

ressend(rsquotest renderrsquo)

)

appget(rsquouserrsquo function(req res)

ressend(rsquouser pagersquo)

)

52 6 Express介紹

Nodejs中文電子書

如上面的程式碼所表示appget可以帶入兩個參數第一個是路徑名稱設定第二個為回應函式 (call back function)回應函式裡面就如同之前的 createServer方法裡面包含 requestresponse兩個物件可供使用使用者就可以透過瀏覽器輸入不同的 url切換到不同的頁面顯示不同的結果

路由設定上也有基本的配對方式讓使用者從瀏覽器輸入的網址可以

是一個變數只要符合型態就可以有對應的頁面產出例如

Create http server

appget(rsquouseridrsquo function(req res)

ressend(rsquouser rsquo + reqparamsid)

)

appget(rsquonumberrsquo function(req res)

ressend(rsquonumber rsquo + reqparamsnumber)

)

裡 面 使 用 到number 從 網 址 輸 入 之 後 就 可 以 直 接 使 用reqparamsnumber 取得所輸入的資料變成 url 參數使用當然前面也是可以加上路徑的設定userid在瀏覽器上路徑必須符合userxxx透

過 reqparamsid就可以取到 xxx這個字串值

另外express參數處理也提供了路由參數配對處理也可以透過正規表示法作為參數設定

var app = require(rsquoexpressrsquo)createServer()

port = 1337

applisten(port)

appget(^ip((d23)((d23))((d23))((d23))) function(req res)

ressend(reqparams)

)

上面程式碼可以發現後面路由設定的型態是正規表示法裡面設定

格式為ip之後必須要加上 ip型態才會符合資料格式同時取得 ip資料已經由正規表示法將資料做分群因此可以取得 ip的四個數字

此程式執行之後可以透過瀏覽器測試輸入網址為 localhost3000ip25525510010可以從頁面獲得資料

63 Express路由處理 53

Nodejs中文電子書

此章節全部範例程式碼如下

overview

author Caesar Chi

blog clonnblogspotcom

version 20120226

create server

var app = require(rsquoexpressrsquo)createServer()

port = 1337

applisten(port)

normal style

appget(rsquorsquo function(req res)

ressend(rsquohello worldrsquo)

)

appget(rsquotestrsquo function(req res)

ressend(rsquotest renderrsquo)

)

parameter style

appget(rsquouseridrsquo function(req res)

ressend(rsquouser rsquo + reqparamsid)

)

appget(rsquonumberrsquo function(req res)

ressend(rsquonumber rsquo + reqparamsnumber)

)

REGX style

appget(^ip((d23)((d23))((d23))) function(req res)

ressend(reqparams)

)

appget(rsquorsquo function(req res)

ressend(rsquoPage not foundrsquo 404)

54 6 Express介紹

Nodejs中文電子書

)

consolelog(rsquostart express servernrsquo)

64 Express middleware

Express 裡面有一個十分好用的應用概念稱為 middleware可以透過middleware做出複雜的效果同時上面也有介紹 next方法參數傳遞就是靠 middleware的概念來傳遞參數讓開發者可以明確的控制程式邏輯

create http server

appuse(expressbodyParser())

appuse(expressmethodOverride())

appuse(expresssession())

上面都是一種 middleware的使用方式透過 appuse方式裡面載入函式執行方法回應函式會包含三個基本參數responserequestnext其中next表示下一個 middleware執行函式同時會自動將預設三個參數繼續帶往下個函式執行底下有個實驗

上面的片段程式執行後開啟瀏覽器連結上 localhost1337會發現伺服器回應結果順序如下

first middle ware

second middle ware

execute middle ware

end middleware function

從上面的結果可以得知剛才設定的 middleware都生效了在 appuse設定的 middleware是所有 url皆會執行方法如果有指定特定方法就可以使用 appget的 middleware設定在 appget函式的第二個參數就可以帶入函式或者是匿名函式只要函式裡面最後會接受 request response next這三個參數同時也有正確指定 next函式的執行時機最後都會執行到最後一個方法當然開發者也可以評估程式邏輯要執行到哪一個階段讓邏輯

可以更為分明

64 Express middleware 55

Nodejs中文電子書

65 Express路由應用

在實際開發上可能會遇到需要使用參數等方式混和變數一起使用

express裡面提供了一個很棒的處理方法 appall這個方式可以先採用基本路由配對再將設定為每個不同的處理方式開發者可以透過這個方式簡

化自己的程式邏輯

overview

author

version 20120226

create server

var app = require(rsquoexpressrsquo)createServer()

port = 1337

users = [

name rsquoClonnrsquo

name rsquoChirsquo

]

applisten(port)

appall(rsquouseridoprsquo function(req res next)

requser = users[reqparamsid]

if (requser)

next()

else

next(new Error(rsquocannot find user rsquo + reqparamsid))

)

appget(rsquouseridrsquo function(req res)

ressend(rsquoviewing rsquo + requsername)

)

appget(rsquouserideditrsquo function(req res)

ressend(rsquoediting rsquo + requsername)

)

56 6 Express介紹

Nodejs中文電子書

appget(rsquouseriddeletersquo function(req res)

ressend(rsquodeleting rsquo + requsername)

)

appget(rsquorsquo function(req res)

ressend(rsquoPage not foundrsquo 404)

)

consolelog(rsquostart express servernrsquo)

內部宣告一組預設的使用者分別給予名稱設定藉由 appall這個方法可以先將路由雛形建立再接下來設定 appget的路徑格式只要符合格式就會分配進入對應的方法中像上面的程式當中如果使用者輸入路徑為user0除了執行 appall程式之後執行 next方法就會對應到路徑設定為userid的這個方法當中如果使用者輸入路徑為user0edit就會執行到useridedit的對應方法

66 Express GET應用範例

我們準備一個使用 GET方法傳送資料的表單

ltDOCTYPE htmlgt

lthtmlgt

ltheadgt

ltmeta http-equiv=rdquoContent-Typerdquo content=rdquotexthtml charset=utf8rdquogt

lttitlegtNodejs 菜鳥筆記 (1)lttitlegt

ltlink rel=rdquostylesheetrdquo href=rdquocssstylecssrdquo type=rdquotextcssrdquo media=rdquoallrdquo gt

ltheadgt

ltbodygt

lth1gtNodejs 菜鳥筆記-註冊lth1gt

ltform id=rdquosignuprdquo method=rdquoGETrdquo action=rdquohttplocalhost3000Signuprdquogt

ltlabelgt 使用者名稱ltlabelgtltinput type=rdquotextrdquo id=rdquousernamerdquo name=rdquousernamerdquo gtltbrgt

ltlabelgt 電子郵件ltlabelgtltinput type=rdquotextrdquo id=rdquoemailrdquo name=rdquoemailrdquo gtltbrgt

ltinput type=rdquosubmitrdquo value=rdquo 註冊我的帳號rdquo gtltbrgt

ltformgt

ltbodygt

lthtmlgt

66 Express GET應用範例 57

Nodejs中文電子書

這個表單沒有什麼特別的地方我們只需要看第 9 行form 使用的method是 GET然後 action是rdquohttplocalhost3000Signupldquo等一下我們要來撰寫Signup這個 URL Path的處理程式

處理 Signup行為

我們知道所謂的 GET方法會透過 URL來把表單的值給帶過去以上面的表單來說到時候 URL會以這樣的形式傳遞

httplocalhost3000Signupusername=xxxampemail=xxx

所以要能處理這樣的資料必須有以下功能

bull 解析 URL

bull 辨別動作是 Signup

bull 解析出 username和 email

一旦能取得 username和 email的值程式就能加以應用了

處理 Signup的程式碼雛形

load module

var url = require(rsquourlrsquo)

urlData = urlparse(requrltrue)

action = urlDatapathname

reswriteHead(200 rdquoContent-Typerdquordquotexthtml charset=utf-8rdquo)

if (action === rdquoSignuprdquo)

user = urlDataquery

resend(rdquolth1gtrdquo + userusername + rdquo 歡迎您的加入 lth1gtltpgt 我們已經將會員啟用信寄至rdquo + useremail + rdquoltpgtrdquo)

首先需要加載 url module它是用來協助我們解析 URL的模組接著使用 urlparse方法第一個傳入 url字串變數也就是 requrl另外第二個參數的用意是設為 ture則引進 querystring模組來協助處理預設是 false它影響到的是 urlDataquery設為 true會傳回物件不然就只是一般的字串urlparse會將字串內容整理成一個物件我們把它指定給 urlData

action變數作為記錄 pathname這是我們稍後要來判斷目前網頁的動作是什麼接著先將 html表頭資訊 (Header)準備好再來判斷路徑邏輯如

58 6 Express介紹

Nodejs中文電子書

果是 Signup這個動作就把 urlDataquery裡的資料指定給 user然後輸出userusername和 useremail把使用者從表單註冊的資料顯示於頁面中

最後進行程式測試啟動 nodejs主程式之後開啟瀏覽器就會看到表單填寫完畢按下送出就可以看到結果了

完整 nodejs程式碼如下

var http = require(rsquohttprsquo)

url = require(rsquourlrsquo)

fs = require(rdquofsrdquo)

server

server = httpcreateServer(function (reqres)

var urlData

encode = rdquoutf8rdquo

filePath = rdquoviewexpress_get_example_formhtmlrdquo

action

urlData = urlparse(requrltrue)

action = urlDatapathname

reswriteHead(200 rdquoContent-Typerdquordquotexthtml charset=utf-8rdquo)

if (action === rdquoSignuprdquo)

user = urlDataquery

resend(rdquolth1gtrdquo + userusername + rdquo 歡迎您的加入 lth1gtltpgt 我們已經將會員啟用信寄至rdquo + useremail + rdquoltpgtrdquo)

else

fsreadFile(filePath encode function(err file)

reswrite(file)

resend()

)

)

serverlisten(3000)

consolelog(rsquoServer 跑起來了現在時間是rsquo + new Date())

66 Express GET應用範例 59

Nodejs中文電子書

67 Express POST應用範例

一開始準備基本的 html表單傳送內容以 POST方式form的 action屬性設定為 POST其餘 html內容與前一個範例應用相同

ltDOCTYPE htmlgt

lthtmlgt

ltheadgt

ltmeta http-equiv=rdquoContent-Typerdquo content=rdquotexthtml charset=utf8rdquogt

lttitlegtNodejs 菜鳥筆記 (1)lttitlegt

ltlink rel=rdquostylesheetrdquo href=rdquocssstylecssrdquo type=rdquotextcssrdquo media=rdquoallrdquo gt

ltheadgt

ltbodygt

lth1gtNodejs 菜鳥筆記-註冊lth1gt

ltform id=rdquosignuprdquo method=rdquoPOSTrdquo action=rdquohttplocalhost3000Signuprdquogt

ltlabelgt 使用者名稱ltlabelgtltinput type=rdquotextrdquo id=rdquousernamerdquo name=rdquousernamerdquo gtltbrgt

ltlabelgt 電子郵件ltlabelgtltinput type=rdquotextrdquo id=rdquoemailrdquo name=rdquoemailrdquo gtltbrgt

ltinput type=rdquosubmitrdquo value=rdquo 註冊我的帳號rdquo gtltbrgt

ltformgt

ltbodygt

lthtmlgt

nodejs的程式處理邏輯與前面 GET範例類似部分程式碼如下

qs = require(rsquoquerystringrsquo)

if (action === rdquoSignuprdquo)

formData = rsquorsquo

reqon(rdquodatardquo function (data)

formData += data

)

reqon(rdquoendrdquo function ()

user = qsparse(formData)

resend(rdquolth1gtrdquo + userusername + rdquo 歡迎您的加入 lth1gtltpgt 我們已經將會員啟用信寄至rdquo + useremail + rdquoltpgtrdquo)

)

60 6 Express介紹

Nodejs中文電子書

主要加入了rsquoquerystringrsquo這個moduel方便我們等一下解析由表單 POST回來的資料另外加入一個 formData的變數用來搜集待等一下表單回傳的資料前面的 GET範例我們只從 req拿出 url的資料這次要在利用req身上的事件處理

JavaScript 在訂閱事件時使用 addEventListener而 nodejs 使用的則是on這邊加上了監聽 data的事件會在瀏覽器傳送資料到Web Server時被執行參數是它所接收到的資料型態是字串

接著再增加 end的事件當瀏覽器的請求事件結束時它就會動作

由於瀏覽器使用 POST在上傳資料時會將資料一塊塊地上傳因為我們在監聽 data事件時透過 formData變數將它累加起來lt不過由於我們

上傳的資料很少一次就結束不過如果日後需要傳的是資料比較大的檔

案這個累加動作就很重要

當資料傳完就進到 end 事件中會用到 qsparse 來解析 formDataformData的內容是字串內容是

username=wordsmithampemail=wordsmith40somewhere

而 qsparse可以幫我們把這個 querystring轉成物件的格式也就是

username=wordsmithampemail=wordsmith40somewhere

一旦轉成物件並指定給 user之後其他的事情就和 GET方法時操作的一樣寫 response的表頭將內容回傳並將 userusername和 useremail代入到內容中

修改完成後接著執行 nodejs程式啟動 web server開啟瀏覽器進入表單測試看看POST的方式能否順利運作

完整程式碼如下

var http = require(rsquohttprsquo)

url = require(rsquourlrsquo)

fs = require(rdquofsrdquo)

qs = require(rsquoquerystringrsquo)

server

server = httpcreateServer(function (reqres)

var urlData

67 Express POST應用範例 61

Nodejs中文電子書

encode = rdquoutf8rdquo

filePath = rdquoviewexpress_post_example_formhtmlrdquo

formData

action

urlData = urlparse(requrltrue)

action = urlDatapathname

reswriteHead(200 rdquoContent-Typerdquordquotexthtml charset=utf-8rdquo)

if (action === rdquoSignuprdquo)

formData = rsquorsquo

reqon(rdquodatardquo function (data)

formData += data

)

reqon(rdquoendrdquo function ()

user = qsparse(formData)

resend(rdquolth1gtrdquo + userusername + rdquo 歡迎您的加入 lth1gtltpgt 我們已經將會員啟用信寄至rdquo + useremail + rdquoltpgtrdquo)

)

else

fsreadFile(filePath encode function(err file)

reswrite(file)

resend()

)

)

serverlisten(3000)

consolelog(rsquoServer 跑起來了現在時間是rsquo + new Date())

62 6 Express介紹

Nodejs中文電子書

68 Express AJAX應用範例

在 Nodejs要使用 Ajax傳送資料並且與之互動在接受資料的部份沒有太大的差別client端不是用 GET就是用 POST來傳資料重點在處理完後用 JSON格式回傳當然 Ajax不見得只傳 JSON格式有時是回傳一段 HTML碼不過後者對伺服器來說基本上就和前兩篇沒有差別了所以我們還是以回傳 JSON做為這一回的主題

這一回其實大多數的工作都會落在前端 Ajax上面前端要負責發送與接收資料並在接收資料後撤掉原先發送資料的表單並將取得的資料

改成 HTML格式之後放上頁面

首先先準備 HTML靜態頁面資料

ltDOCTYPE htmlgt

lthtmlgt

ltheadgt

lttitlegtNodejs 菜鳥筆記 (3)lttitlegt

ltlink rel=rdquostylesheetrdquo href=rdquocssstylecssrdquo type=rdquotextcssrdquo media=rdquoallrdquo gt

ltscript src=rdquohttpsajaxgoogleapiscomajaxlibsjquery171jqueryminjsrdquogtltscriptgt

ltheadgt

ltbodygt

lth1gtNodejs 菜鳥筆記-註冊 (用 Ajax)lth1gt

ltform id=rdquosignuprdquo action=rdquoSignuprdquo method=rdquoPOSTrdquogt

ltlabelgt 使用者名稱ltlabelgtltinput type=rdquotextrdquo id=rdquousernamerdquo name=rdquousernamerdquo gtltbrgt

ltlabelgt 電子郵件ltlabelgtltinput type=rdquotextrdquo id=rdquoemailrdquo name=rdquoemailrdquo gtltbrgt

ltinput type=rdquosubmitrdquo value=rdquo 註冊我的帳號rdquo gtltbrgt

ltformgt

ltdiv id=rdquoresultrdquogt

ltdivgt

ltscript type=rdquotextjavascriptrdquogt

$(function()$(rsquosignup input[type=rdquosubmitrdquo]rsquo)click(function(e)

var user =

userusername = $(rdquousernamerdquo)val()useremail = $(rdquoemailrdquo)val()

$post(rdquoSignuprdquouserfunction(data)greet(data)

)

68 Express AJAX應用範例 63

Nodejs中文電子書

epreventDefault()

)

var greet = function(msg)

var resultNode = $(rsquoresultrsquo)greeting = $(rsquolth2gtHirsquo+msgusername+rsquo 你的會員 id 是rsquo+ msgid +rsquo 我們會將會員啟動信寄至rsquo+msgemail+rsquolth2gtrsquo)

$(rsquosignuprsquo)hide()resultNodehtml(rdquordquo)

resultNodeappend(greeting)

)

ltscriptgt

ltbodygt

lthtmlgt

HTML頁面上準備了一個表單用來傳送註冊資料接著直接引用了Google CDN來載入 jQuery用來幫我們處理 Ajax的工作這次要傳送和接收的工作很大的變動都在 HTML頁面上的 JavaScript當中我們要做的事有 (相關 jQuery處理這邊不多做贅述指提起主要功能解說)

bull 用 jQUery取得 submit按鈕綁定它的 click動作

bull 取得表單 username和 email的值存放在 user這個物件中

bull 用 jQuery的 $post方法將 user的資料傳到 Server

bull 一旦成功取得資料後透過 greet這個 function組成回報給 user的訊息

bull 清空原本給使用者填資料的表單

bull 將 Server回傳的 usernameemail和 id這 3個資料組成回應的訊息

bull 將訊息放到原本表單的位置

經過以上的處理後一個 Ajax的表單的基本功能已經完成

接著進行 nodejs主要程式的編輯部分程式碼如下

var fs = require(rdquofsrdquo)

64 6 Express介紹

Nodejs中文電子書

qs = require(rsquoquerystringrsquo)

if (action === rdquoSignuprdquo)

formData = rsquorsquo

reqon(rdquodatardquo function (data)

formData += data

)

reqon(rdquoendrdquo function ()

var msg

user = qsparse(formData)

userid = rdquo123456rdquo

msg = JSONstringify(user)

reswriteHead(200 rdquoContent-Typerdquordquoapplicationjson charset=utf-8rdquordquoContent-Lengthrdquomsglength)

resend(msg)

)

這裡的程式和前面 POST範例基本上大同小異差別在

bull 幫 user的資料加上 id隨意存放一些文字進去讓 Server回傳的資料多於 Client端傳上來的不然會覺得 Server都沒做事

bull 增加了 msg 這個變數存放將 user 物件 JSON 文字化的結果JSONstringify這個轉換函式是 V8引擎所提供的如果你好奇的話

bull 大重點來了我們要告訴 Client端這次回傳的資料格式是 JSON所在 Content-type和 Content-Length要提供給 Client

Server很輕鬆就完成任務了最後進行程式測試啟動 nodejs主程式之後開啟瀏覽器就會看到表單填寫完畢按下送出就可以看到結果了

最後 nodejs本篇範例程式碼如下

var http = require(rsquohttprsquo)

url = require(rsquourlrsquo)

fs = require(rdquofsrdquo)

qs = require(rsquoquerystringrsquo)

server

68 Express AJAX應用範例 65

Nodejs中文電子書

server = httpcreateServer(function (reqres)

var urlData

encode = rdquoutf8rdquo

filePath = rdquoviewexpress_ajax_example_formhtmlrdquo

formData

action

urlData = urlparse(requrltrue)

action = urlDatapathname

if (action === rdquoSignuprdquo)

formData = rsquorsquo

reqon(rdquodatardquo function (data)

formData += data

)

reqon(rdquoendrdquo function ()

var msg

user = qsparse(formData)

userid = rdquo123456rdquo

msg = JSONstringify(user)

reswriteHead(200 rdquoContent-TyperdquordquoapplicationjsonrdquordquoContent-Lengthrdquomsglength)

resend(msg)

)

else

fsreadFile(filePath encode function(err file)

reswriteHead(200 rdquoContent-Typerdquordquotexthtml charset=utf-8rdquo)

reswrite(file)

resend()

)

)

serverlisten(3000)

66 6 Express介紹

Nodejs中文電子書

consolelog(rsquoServer 跑起來了現在時間是rsquo + new Date())

69 原始資料提供

bull [NodeJS初學者筆記 (1)-用 GET傳送資料] (httpithelpithomecomtwquestion10087402)

bull [NodeJS初學者筆記 (2)-用 POST傳送資料] (httpithelpithomecomtwquestion10087489)

bull [NodeJS 初學者筆記 (3)-用 Ajax 傳送資料] (httpithelpithomecomtwquestion10087627)

69 原始資料提供 67

Nodejs中文電子書

68 6 Express介紹

7

CoffeeScript

bull Spine

bull Hem

bull Spineapp

Letrsquos Have a Cup of CoffeeScript

bull es5-shim ECMAScript 5 compatibility shims for legacy JavaScript engines

ESnext

node-iform - A middleware for node-validator httpsgithubcomguileennode-iform

69

Nodejs中文電子書

70 7 CoffeeScript

8

製作一個 Hubot的 Plurk Adapter

81 應用事項提醒

此應用範例以CoffeeScript編寫主要程式架構採用Github釋放的Hubot專案以下有幾個主要注意事項請讀者注意

bull Hubot 一開始的架構除了內建的兩個 Adapter 之外其餘都要以Nodejs的Module方式才能運作

bull Hubot的 binhubot裡面寫著 npm install所以不管你怎麼改原始碼也不能改變第一點的狀況

其實上述都在討論同一件事情NodeJS的模組而且模組不能設定相對路徑之類的來安裝一定要包裝成 npm相符和格式才有辦法正確執行

82 建立 Adapter

首先需要準備兩個檔案

bull packagejson

bull plurkcoffee

71

Nodejs中文電子書

有這兩個就足以變成 NodeJS的模組了在開始 Coding之前先來設定一下 packagejson相依性

rdquonamerdquo rdquohubot-plurkrdquo

rdquoversionrdquo rdquo011rdquo

rdquomainrdquo rdquoplurkrdquo

rdquodependenciesrdquo

rdquohubotrdquo rdquogt=205rdquo

rdquooauthrdquo rdquordquo

rdquocronrdquordquordquo

這邊會使用到NodeJS的 cron模組(Module)而登入 Plurk需要OAuth標準授權才行所以也需要加載 OAuth模組

注意Hubot在讀取非內建模組時會自動在前面加上 hubot-的前置

83 建立 Robot跟 API

本篇應用基本上程式碼大部分參考 Hubot - Twitter Adapter來製作主要差異只有在使用 OAuth這個模組Twitter有 Streaming可用而 Plurk則得用 Comet方式來達到即時讀取

plurkcoffee程式碼

Robot = require(rdquohubotrdquo)robot()

Adapter = require(rdquohubtordquo)adapter()

EventEmitter = require(rdquoeventsrdquo)EventEmitter

oauth = require(rdquooauthrdquo)

cronJob = require(rdquocronrdquo)CronJob

class Plurk exntends Adapter

class PlurkStreaming exnteds EventEmitter

72 8 製作一個 Hubot的 Plurk Adapter

Nodejs中文電子書

先弄個基本架構主要 Class皆參考 Twitter Adapter命名方式接著再將 Plurk這個 Class增加幾個Method基本上只要有 run send reply就夠了而 run用來做初始化的部分

class Plurk entends Adapter

send (plurk id strings⋯) -gt

reply (plurk id strings⋯) -gt

run -gt

看起來有點東西接著來處理主要的 Plurk API結合部分

class PlurkStreaming extends EventEmitter

consuctor (options) -gt

plurk (callback) -gt

觀察河道

getChannel -gt

取得 Comet 網址

reply (plurk id message) -gt

回噗

acceptFriends -gt

接受好友

get (path callback) -gt

GET 請求

post (path body callback)-gt

POST 請求(其實是裝飾)

request (method path body callback)-gt

主要的 OAuth 請求

comet (server callback)-gt

噗浪的 Comet 傳回是 JavaScript Callback 要另外處理後才會變成 JSON

然後把注意力集中到 constructor上先把建構子弄好

constructor (options) -gt

super()

if optionskey and optionssecret and optionstoken and optionstoken secret

key = optionskey

secret = optionssecret

token = optionstoken

token secret = optionstoken secret

83 建立 Robot跟 API 73

Nodejs中文電子書

建立 OAuth 連接

consumer = new oauthOAuth(

rdquohttpwwwplurkcomOAuthrequest tokenrdquo

rdquohttpwwwplurkcomOAuthaccess tokenrdquo

key

secret

rdquo10rdquo

rdquohttpwwwplurkcomOAuthauthorizerdquo

rdquoHMAC-SHA1rdquo

)

domain = rdquowwwplurkcomrdquo

初始化取得 Comet 網址

do getChannel

else

throw new Error(rdquo 參數不足需要 Key Secret Token Token Secretrdquo)

接著來處理 request這個 method

request (method path body callback) -gt

記錄一下這次的 Request

consolelog(rdquohttpdomainpathrdquo)

Callback 這邊先不丟進去要用另一種方式處理

request = consumerget(rdquohttpdomainpathrdquo token token secret null)

requeston rdquoresponserdquo (res) -gt

reson rdquodatardquo (chunk) -gt

parseResponse(chunk+rsquorsquo callback)

reson rdquoendrdquo (data) -gt

consolelog rdquoEnd Request pathrdquo

reson rdquoerrorrdquo (data) -gt

consolelog rdquoError rdquo + data

requestend()

處理資料

parseResponse = (data callback) -gt

if datalength gt 0

用 TryCatch 避免處理 JSON 出錯導致整個中斷

try

callback null JSONparse(data)

catch err

74 8 製作一個 Hubot的 Plurk Adapter

Nodejs中文電子書

consolelog(rdquoError Parse JSONrdquo + data err)

繼續執行

callback null data ||

大致上就是這樣上面程式的架構已經將整個 Hubot Plurk Adapter完成因為在測試時竟然因為噗浪 Lag而沒讀到完整的 Comet資料然後造成程式異常為了避免這個問題發生因此需要加上為了完美呈現需要再

加上 Comet的處理所以要使用到 EventEmitter的功能

comet (server callback) -gt

在 Callback 裡面會找不到自身所以設定區域變數

self =

記錄一下這次的 Request

consolelog(rdquo[Comet] serverrdquo)

Callback 這邊先不丟進去要用另一種方式處理

request = consumerget(rdquohttpdomainpathrdquo token token secret null)

requeston rdquoresponserdquo (res) -gt

reson rdquodatardquo (chunk) -gt

parseResponse(chunk+rsquorsquo callback)

reson rdquoendrdquo (data) -gt

consolelog rdquoEnd Request pathrdquo

請求結束發出事件通知可以進行下一次請求

selfemit rdquonextPlurkrdquo

reson rdquoerrorrdquo (data) -gt

consolelog rdquoError rdquo + data

requestend()

處理資料

parseResponse = (data callback) -gt

if datalength gt 0

用 trycatch 避免失敗中斷

try

去掉 JavaScript 的 Callback

data = datamatch(CometChannelscriptCallback((+))s)

jsonData = rdquordquo

if data

83 建立 Robot跟 API 75

Nodejs中文電子書

jsonData = JSONparse(data[1])

else

如果沒有任何 Match 嘗試直接 parse

jsonData = JSONparse(data)

catch err

consolelog(rdquo[Comet] Errorrdquo data err)

用 TryCatch 避免處理 JSON 出錯導致整個中斷

try

只傳入 json 的 data 部分

callback null jsonDatadata

catch err

consolelog(rdquo[Comet]Error Parse JSONrdquo + data err)

繼續執行

callback null data ||

後面的 get跟 post就簡單多了

get (path callback) -gt

request(rdquoGETrdquo path null callback)

post (path body callback) -gt

request(rdquoPOSTrdquo path body callback)

接著處理取的 Comet網址的 getChannel

getChannel -gt

self =

get rdquoAPPRealtimegetUserChannelrdquo (error data) -gt

if error

檢查是否有 comet server

if datacomet server

selfchannel = datacomet server

如果沒有 Channel Ready 就嘗試連接會失敗

selfemit(rsquochannel readyrsquo)

那麼先來處理 Plurk Adaper好處理的部份

send (plruk id strings⋯)-gt

跟 Reply 一樣直接交給 reply 做

reply plurk id strings⋯

76 8 製作一個 Hubot的 Plurk Adapter

Nodejs中文電子書

reply (plurk id strings⋯) -gt

stringsforEach (message) =gt

botreply(plruk id message)

接著把 run處理好就可以上線運作摟

run -gt

self =

options =

key processenvHUBOT PLURK KEY

secret processenvHUBOT PLURK SECRET

token processenvHUBOT PLURK TOKEN

token secret processenvHUBOT PLURK TOKEN SECRET

創建剛剛的 API

bot = new PlurkStreaming(options)

依照 Twitter 的 new RobotTextMessage 會沒有反應所以參考 hubot-minecraft 的方式

r = robotconstructor

處理噗浪河道訊息

doPlurk = (data)-gt

檢查是否為回噗

if dataresponse

datacontent raw = dataresponsecontent raw

datauser id = dataresponseuser id

確定有噗浪 ID 跟訊息

if dataplurk id and datacontent raw

selfreceive new rTextMessage(dataplurk id datacontent raw)

取得 Comet Server 完成開始第一次 Comet 連接

boton rdquochannel readyrdquo () -gt

botplurk selfdoPlurk

上一次 Comet 完成繼續 Polling

boton rdquonextPlurkrdquo ()-gt

botplurk selfdoPlurk

定時接受好友邀請

do botacceptFriends

bot = bot

83 建立 Robot跟 API 77

Nodejs中文電子書

終於完成 AdapterHubot專案裡面的 scripts資料夾內是互動部分不需要像 Adapter如此大費周章處理只新增檔案並且設計好對白之後就會回噗了我開發用的機器人在此大家可以去跟他玩玩[httpplurkcomelct9620 bot](httpplurkcomelct9620 bot)

84 原始資料提供

bull [製 作 一 個 Hubot 的 噗 浪 Adapter](http revo-skillfrosttw blog20120318create-a-hubot-plurk-adapter)

78 8 製作一個 Hubot的 Plurk Adapter

9

Nodejs好用工具介紹

91 logio

Real-time log monitoring in your browser

httplogioorg

nodejs (gt=0412 lt=0611)

npm config set unsafe-perm true

npm install -g --prefix=usrlocal logio

logio server start

httplocalhost8998

79

Nodejs中文電子書

80 9 Nodejs好用工具介紹

10

精選文章

101 Express

bull 使用 NodeJS + Express從 GETPOST Request取值 [邀稿中]

bull link2 [邀稿中]

bull link3 [邀稿中]

81

Nodejs中文電子書

82 10 精選文章

11

參考資源

111 Nodejs書籍

bull Programming Nodejs April 2012 (est) Travis Swicegood Pragmatic Bookshelf

bull e Little Book on CoffeeScript January 2012 Alex MacCaw OrsquoReilly Media

bull Node for Front-End Developers January 2012 Garann Means OrsquoReilly Media

bull Building Hypermedia APIs with HTML5 and Node November 2011 MikeAmundsen OrsquoReilly Media

bull Node Web Development August 2011 David Herron PacktPub

bull CoffeeScript Accelerated JavaScript Development July 2011 Trevor BurnhamPragmatic Bookshelf

bull Getting Started with GEO CouchDB and Nodejs July 2011 Mick omp-son OrsquoReilly Media

bull What Is Node July 2011 Brett McLaughlin OrsquoReilly Media

bull Node Up and Running May 2011 Tom Hughes-Croucher Mike WilsonOrsquoReilly Media

83

Nodejs中文電子書

bull Hands-on Nodejs e Nodejs introduction and API reference May 2011 Pe-dro Teixeira LeanPub

bull e Node Beginner Book Manuel Kiessling

bull Mastering Nodejs visionmedia

112 Nodejs影音教學

bull e Node Sessions e Best of OSCON 2011 August 2011 OrsquoReilly Media(Video)

bull Tom Hughes-Croucher on Node March 2011 OrsquoReilly Media (Video)

113 Nodejs教學網站

bull Node Tuts

114 Nodejs課程

bull Introduction To Nodejs Van Nguyen CodeLesson

84 11 參考資源

Page 28: Nodejs Wiki

Nodejs中文電子書

循序執行

循序執行可以協助把非常深的巢狀 callback結構攤平例如用這樣的簡單模組來做(serialjs)

moduleexports = function(funs) var c = 0 if(isArrayOfFunctions(funs))

throw(lsquoArgument type was not matched Should be array of func-tionsrsquo)

return function()

var args = Arrayprototypeslicecall(arguments 0) if((cgt=funslength))

c++ return funs[c-1]apply(this args)

function isArrayOfFunctions(f ) if(typeof f == lsquoobjectrsquo) return false if(flength)return false if(fconcat) return false if(fsplice) return false var i = 0 for(iltflength i++)

if(typeof f[i] == lsquofunctionrsquo) return false

return true

簡單的測試範例(testSerialjs)使用 fs 模組確定某個 path 是檔案然後讀取印出檔案內容這樣會用到兩層的 callback所以測試中有使用serial的版本與 nested callbacks的版本做對照

var serial = require(lsquoserialrsquo) fs = require(lsquofsrsquo) path = lsquodclientjsrsquo cb = serial([ func-tion(err data)

if(err)

if(dataisFile) fsreadFile(path cb)

22 2 JavaScript與 NodeJS

Nodejs中文電子書

else consolelog(err)

function(err data)

if(err) consolelog(lsquo[ attened by searial]rsquo) con-solelog(datatoString(lsquoutf8rsquo))

else consolelog(err)

]) fsstat(path cb)

fsstat(path function(err data) 第一層 callback if(err)

if(dataisFile)

fsreadFile(path function(err data) 第二層 callback if(err)

consolelog(lsquo[nested callbacks]rsquo) con-solelog(datatoString(lsquoutf8rsquo))

else consolelog(err)

)

else consolelog(err)

)

關鍵在於這些 callback的執行是有順序性的所以利用 serial返回的一個函數 cb來取代這些 callback然後在 cb中控制每次會循序呼叫的函數

26 流程控制 23

Nodejs中文電子書

就可以把巢狀的 callback攤平成循序的 function陣列(就是傳給 serial函數的參數)

測試中的dclientjs 是一個簡單的 dnode 測試程式放在跟 testSerialjs同一個目錄

var dnode = require(lsquodnodersquo)

dnodeconnect(8000 lsquolocalhostrsquo function(remote)

remoterestart(function(str) consolelog(str) processexit()

)

)

執行測試程式後出現結果

[ attened by searial] var dnode = require(lsquodnodersquo)

dnodeconnect(8000 lsquolocalhostrsquo function(remote)

remoterestart(function(str) consolelog(str) processexit()

)

)

[nested callbacks] var dnode = require(lsquodnodersquo)

dnodeconnect(8000 lsquolocalhostrsquo function(remote)

remoterestart(function(str) consolelog(str) processexit()

)

)

對照起來看兩種寫法的結果其實是一樣的但是利用 serialjs巢狀的 callback結構就會消失

不過這樣也只限於順序單純的狀況如果函數執行的順序比較複雜

(不只是一直線)還是需要用功能更完整的流程控制模組比較好例如

httpsgithubcomcaolanasync

24 2 JavaScript與 NodeJS

3

Nodejs安裝與設定

本篇將講解如何在各個不同 OS建立 NodeJS環境目前 NodeJS 048版本環境架設方式需依賴 Linux指令才可編譯完成當然在不同作業系統中也已經有 NodeJS package可以直接使用指令快速架設以下各不同作業系統解說如何安裝 NodeJS

31 Ubuntu Linux

更新推薦使用 nvm

git clone gitgithubcomcreationixnvmgit ~nvm

echo rdquo ~nvmnvmshrdquo gtgt ~bashrc

nvm install v0614

nvm alias default v0614

以 上 可 參 考 http dreamerslabcom blog tw how-to-setup-a-node-js-development-environment-on-ubuntu-11-04

使用 APT套件管理工具是常見的方法以下是使用社群提供的 PPA安裝方式

sudo apt-get install python-software-properties

sudo add-apt-repository ppachris-leanodejs-devel

25

Nodejs中文電子書

sudo apt-get update

sudo apt-get install nodejs

32 Other Linux

Linux很適合作為 NodeJS的伺服器作業系統及開發環境安裝前請先確認以下套件已正確安裝

bull curl (wget)用來下載檔案的工具

bull git先進的版本控制工具

bull g++ GNU C++軟體編譯工具

bull make GNU軟體專案建置工具

安裝指令如下如設有權限問題請在指令前面加上 sudo

git clone httpsgithubcomjoyentnodegit

cd node

git checkout v067

configure

make

sudo make install

接著測試 nodeJS是否正常執行

node --version

出現版本訊息即表示安裝成功

33 Windows

nodeJS 在 v060 版本之後開始正式支援 windows native直接使用nodeexe 就可以執行程式支援性完全與 linux 相同更棒的部份就是不需經過編譯經過下載之後簡單設定完成立即開發 node程式

26 3 Nodejs安裝與設定

Nodejs中文電子書

下載 nodejs安裝檔案 lthttpnodejsorgdownloadgt

如此完成 windows native nodeexe安裝接著可以進入 command line執行測試在 command line輸入rdquonoderdquo會出現 nodejs版本訊息畫面表示安裝完成

33 Windows 27

Nodejs中文電子書

28 3 Nodejs安裝與設定

4

Nodejs基礎

前篇文章已經由介紹安裝至設定都有完整介紹nodeJS 內部除了javascript常用的函式 (function)物件 (object)之外也有許多不同的自訂物件nodeJS預設建立這些物件為核心物件是為了要讓開發流程更為這些資料在官方文件已經具有許多具體說明接下來將會介紹在開發 nodeJS程式時常見的物件特性與使用方法

41 nodejs http伺服器建立

在 lsquonodejs官方網站 lthttpnodejsorggtlsquo裡面有舉一個最簡單的 HTTP伺服器建立一開始初步就是建立一個伺服器平台讓 nodejs可以與瀏覽器互相行為每種語言一開始的程式建立都是以 Hello world開始最初也從 Hello world帶各位進入 nodejs的世界

輸入以下程式碼儲存檔案為 node basic http hello worldjs

var server

ip = rdquo127001rdquo

port = 1337

http = require(rsquohttprsquo)

server = httpcreateServer(function (req res)

29

Nodejs中文電子書

reswriteHead(200 rsquoContent-Typersquo rsquotextplainrsquo)

resend(rsquoHello Worldnrsquo)

)

serverlisten(port ip)

consolelog(rdquoServer running at httprdquo + ip + rdquordquo + port)

程式碼解講一開始需要有幾個基本的變數

bull ip 機器本身的 ip位置因為使用本地端因此設定為 127001

bull port 需要開通的阜號通常設定為 http port 80因範例不希望與基本 port相衝隨意設定為 1337

在 nodejs 的程式中有許多預設的模組可以使用因此需要使用require方法將模組引入在這邊我們需要使用 http這個模組因此將 http載入Http模組裡面內建有許多方法可以使用這邊採用 createServer創建一個基本的 http伺服器再將 http伺服器給予一個 server變數裡面的回呼函式 (call back function)可以載入 http伺服器的資料與回應方法 (requestresponse)在程式裡面就可以看到我們直接回應給瀏覽器端所需的 Header回應內容

reswriteHead(200 rsquoContent-Typersquo rsquotextplainrsquo)

resend(rsquoHello Worldnrsquo)

Http伺服器需要設定 port ip在最後需要設定 Http監聽需要使用到listen事件監聽所有 Http伺服器行為

httplisten(port ip)

所有事情都完成之後需要確認伺服器正確執行因此使用 console在javascript裡就有這個原生物件console所印出的資料都會顯示於 nodejs伺服器頁面這邊印出的資料並不會傳送到使用者頁面上之後許多除壞

(debug)都會用到 console物件

consolelog(rdquoServer running at httprdquo + ip + rdquordquo + port)

30 4 Nodejs基礎

Nodejs中文電子書

42 nodejs http路徑建立

前面已經介紹如何建立一個簡單的 http伺服器接下來本章節將會介紹如何處理伺服器路徑 (rout)問題在 http的協定下所有從瀏覽器發出的要求 (request)都需要經過處理路徑上的建立也是如此

路徑就是指伺服器 ip位置或者是網域名稱之後對於伺服器給予的要求修改剛才的 hello world檔案修改如下

server = httpcreateServer(function (req res)

consolelog(requrl)

reswriteHead(200 rsquoContent-Typersquo rsquotextplainrsquo)

resend(rsquohello worldnrsquo)

)

重新啟動 nodejs程式後在瀏覽器端測試一下路徑行為結果如下圖

當在瀏覽器輸入 http1270011337test 在伺服器端會收到兩個要求一個是我們輸入的test要求另外一個則是faviconicotest的路徑要求http伺服器本身需要經過程式設定才有辦法回應給瀏覽器端所需要的回應在伺服器中所有的路徑要求都是需要被解析才有辦法取得資料從

42 nodejs http路徑建立 31

Nodejs中文電子書

上面解說可以了解到在 nodejs當中所有的路徑都需要經過設定未經過設定的路由會讓瀏覽器無法取得任何資料導致錯誤頁面的發生底下將會解

說如何設定路由同時避免發生錯誤情形先前 nodejs程式需要增加一些修改才能讓使用者透過瀏覽器在不同路徑時有不同的結果根據剛才

的程式做如下的修改

var server

ip = rdquo127001rdquo

port = 1337

http = require(rsquohttprsquo)

url = require(rsquourlrsquo)

path

server = httpcreateServer(function (req res)

path = urlparse(requrl)

reswriteHead(200 rsquoContent-Typersquo rsquotextplainrsquo)

switch (pathpathname)

case rdquoindexrdquo

resend(rsquoI am indexnrsquo)

break

case rdquotestrdquo

resend(rsquothis is test pagenrsquo)

break

default

resend(rsquodefault pagenrsquo)

break

)

serverlisten(port ip)

consolelog(rdquoServer running at httprdquo + ip + rdquordquo + port)

程式做了片段的修改首先載入 url模組另外增加一個 path變數url模組就跟如同他的命名一般專門處理 url字串處理裡面提供了許多方法來解決路徑上的問題因為從瀏覽器發出的要求路徑可能會帶有多種需求

或者 GET參數組合等因此我們需要將路徑單純化取用路徑部分的資料即可例如使用者可能會送出 http1270011337testsend=1如果直接

32 4 Nodejs基礎

Nodejs中文電子書

信任 requrl就會收到結果為testsend=1所以需要透過 url模組的方法將路徑資料過濾

在這邊使用 urlparse的方法裡面帶入網址格式資料會回傳路徑資料為了後需方便使用將回傳的資料設定到 path變數當中在回傳的路徑資料裡面包含資訊如下圖

這邊只需要使用單純的路徑要求直接取用 pathpathname就可以達到我們的目的

最後要做路徑的判別在不同的路徑可以指定不同的輸出在範例中

有三個可能結果第一個從瀏覽器輸入index就會顯示 index結果test 就會呈現出 test頁面最後如果都不符合預期的輸入會直接顯示 default的頁面最後的預防可以讓瀏覽器不會出現非預期結果讓程式的可靠性提昇

底下為測試結果

42 nodejs http路徑建立 33

Nodejs中文電子書

43 nodejs檔案讀取

前面已經介紹如何使用路由(rount)做出不同的回應實際應用只有在瀏覽器只有輸出幾個文字資料總是不夠的在本章節中將介紹如何使

用檔案讀取輸出檔案資料讓使用者在前端瀏覽器也可以讀取到完整的

html css javascript檔案輸出

檔案管理最重要的部分就是 lsquoFile system lthttpnodejsorgdocslatestapifshtmlgtlsquo這個模組此模組可以針對檔案做管理監控讀取等行為裡面有許多預設的方法底下是檔案輸出的基本範例底下會有兩個檔案

第一個是靜態 html檔案

ltDOCTYPE htmlgt

lthtml xmlns=rdquohttpwwww3org1999xhtmlrdquo xmllang=rdquozh-CNrdquo lang=rdquozh-CNrdquogt

ltheadgt

ltmeta http-equiv=rdquoContent-Typerdquo content=rdquotexthtml charset=UTF-7rdquo gt

lttitlegtnodejs index html filelttitlegt

ltheadgt

ltbodygt

34 4 Nodejs基礎

Nodejs中文電子書

lth1gtnodejs index html filelth1gt

ltbodygt

lthtmlgt

另一個為 nodejs程式

var fs = require(rdquofsrdquo)

filename = rdquostaticindexhtmlrdquo

encode = rdquoutf8rdquo

fsreadFile(filename encode function(err file)

consolelog(file)

)

一開始直接載入 le system模組 載入名稱為 fs讀取檔案主要使

用的方法為 readFile裡面以三個參數路徑 ( le path) 編碼方式 (encoding)

回應函式 (callback)路徑必須要設定為靜態 html所在位置才能指定到正確的檔案靜態檔案的編碼方式也必須正確這邊使用靜態檔案的編

碼為 utf8如果編碼設定錯誤nodejs讀取出來檔案結果會使用 byte raw格式輸出如果錯誤編碼格式會導致輸出資料為 byte raw

回應函式中裡面會使用兩個變數error為錯誤資訊如果讀取的檔案不存在或者發生錯誤error數值會是 true如果成功讀取資料 error將會是 falsecontent則是檔案內容資料讀取後將會把資料全數丟到 content這個變數當中

最後程式的輸出結果畫面如下

43 nodejs檔案讀取 35

Nodejs中文電子書

44 nodejs http靜態檔案輸出

前面已經了解如何讀取本地端檔案接下來將配合 http伺服器路由讓每個路由都能夠輸出相對應的靜態 html檔案首先新增加幾個靜態 html檔案

ltDOCTYPE htmlgt

lthtml xmlns=rdquohttpwwww3org1999xhtmlrdquo xmllang=rdquozh-CNrdquo lang=rdquozh-CNrdquogt

ltheadgt

ltmeta http-equiv=rdquoContent-Typerdquo content=rdquotexthtml charset=UTF-7rdquo gt

lttitlegtnodejs index html filelttitlegt

ltheadgt

ltbodygt

lth1gtnodejs index html filelth1gt

ltbodygt

lthtmlgt

ltDOCTYPE htmlgt

lthtml xmlns=rdquohttpwwww3org1999xhtmlrdquo xmllang=rdquozh-CNrdquo lang=rdquozh-CNrdquogt

ltheadgt

ltmeta http-equiv=rdquoContent-Typerdquo content=rdquotexthtml charset=UTF-7rdquo gt

lttitlegtnodejs test html filelttitlegt

ltheadgt

ltbodygt

lth1gtnodejs test html filelth1gt

ltbodygt

lthtmlgt

ltDOCTYPE htmlgt

lthtml xmlns=rdquohttpwwww3org1999xhtmlrdquo xmllang=rdquozh-CNrdquo lang=rdquozh-CNrdquogt

ltheadgt

ltmeta http-equiv=rdquoContent-Typerdquo content=rdquotexthtml charset=UTF-7rdquo gt

lttitlegtnodejs static html filelttitlegt

ltheadgt

ltbodygt

lth1gtnodejs static html filelth1gt

ltbodygt

lthtmlgt

36 4 Nodejs基礎

Nodejs中文電子書

準備一個包含基本路由功能的 http伺服器

var server

ip = rdquo127001rdquo

port = 1337

http = require(rsquohttprsquo)

url = require(rsquourlrsquo)

server = httpcreateServer(function (req res)

var path = urlparse(requrl)

)

serverlisten(port ip)

consolelog(rdquoServer running at httprdquo + ip + rdquordquo + port)

加入 le system 模組使用 readFile 的功能將這一段程式放置於

createServer的回應函式中

fsreadFile(filePath encode function(err file)

)

readFile的回應函式裡面加入頁面輸出讓瀏覽器可以正確讀到檔案在這邊我們設定讀取的檔案為 html 靜態檔案所以 Content-type 設定為texthtml讀取到檔案的內容將會正確輸出成 html靜態檔案

fsreadFile(filePath encode function(err file)

reswriteHead(200 rsquoContent-Typersquo rsquotexthtmlrsquo)

reswrite(file)

resend()

)

到這邊為止基本的程式內容都已經完成剩下一些細節的調整首先

路徑上必須做調整目前的靜態檔案全部都放置於 static資料夾底下設定一個變數來記住資料夾位置

接著將瀏覽器發出要求路徑與資料夾組合讀取正確 html靜態檔案使用者有可能會輸入錯誤路徑所以在讀取檔案的時候要加入錯誤處理

同時回應 404伺服器無法正確回應的 http header格式

加入這些細節的修改一個基本的 http 靜態 html輸出伺服器就完成了完整程式碼如下

44 nodejs http靜態檔案輸出 37

Nodejs中文電子書

var server

ip = rdquo127001rdquo

port = 1337

http = require(rsquohttprsquo)

fs = require(rdquofsrdquo)

folderPath = rdquostaticrdquo

url = require(rsquourlrsquo)

path

filePath

encode = rdquoutf8rdquo

server = httpcreateServer(function (req res)

path = urlparse(requrl)

filePath = folderPath + pathpathname

fsreadFile(filePath encode function(err file)

if (err)

reswriteHead(404 rsquoContent-Typersquo rsquotextplainrsquo)

resend()

return

reswriteHead(200 rsquoContent-Typersquo rsquotextapplicationrsquo)

reswrite(file)

resend()

)

)

serverlisten(port ip)

consolelog(rdquoServer running at httprdquo + ip + rdquordquo + port)

45 nodejs http GET資料擷取

http伺服器中除了路由之外另一個最常使用的方法就是擷取 GET資料本單元將會介紹如何透過基本 http伺服器擷取瀏覽器傳來的要求擷取 GET資料

在 http協定中GET參數都是藉由 URL從瀏覽器發出要求送至伺服器

38 4 Nodejs基礎

Nodejs中文電子書

端基本的傳送網址格式可能如下

http127001testsend=1amptest=2

上面這段網址裡面的 GET參數就是 send而這個變數的數值就為 1如果想要在 http伺服器取得 GET資料需要在瀏覽器給予的要求 (request)做處理

首先需要載入 query string這個模組這個模組主要是用來將字串資料

過濾後轉換成javascript物件

qs = require(rsquoquerystringrsquo)

接著在第一階段利用 url模組過濾瀏覽器發出的 URL資料後將回應的物件裡面的 query這個變數是一個字串值資料過濾後如下

send=1amptest=2

透過 query string使用 parse這個方法將資料轉換成 javascript物件就表示 GET的資料已經被伺服器端正式擷取下來

path = urlparse(requrl)

parameter = qsparse(pathquery)

整個 nodejs http GET參數完整擷取程式碼如下

var server

ip = rdquo127001rdquo

port = 1337

http = require(rsquohttprsquo)

qs = require(rsquoquerystringrsquo)

url = require(rsquourlrsquo)

server = httpcreateServer(function (req res)

var path = urlparse(requrl)

parameter = qsparse(pathquery)

consoledir(parameter)

reswriteHead(200 rsquoContent-Typersquo rsquotextplainrsquo)

reswrite(rsquoBrowser test GET parameternrsquo)

resend()

)

45 nodejs http GET資料擷取 39

Nodejs中文電子書

serverlisten(port ip)

consolelog(rdquoServer running at httprdquo + ip + rdquordquo + port)

程式運作之後由瀏覽器輸入要求網址之後nodejs伺服器端回應資料為

Server running at http1270011337

send rsquo1rsquo test rsquo1rsquo

46 本章結語

前面所解說的部份一大部分主要是處理 http伺服器基本問題雖然在某些部分有牽扯到 http 伺服器基本運作原理主要還是希望可以藉由這些基本範例練習 nodejs練習回應函式與語法串接的特點習慣編寫javascript風格程式當然直接這樣開發 nodejs是非常辛苦的接下來在模組實戰開發的部份將會介紹特定的模組一步一步帶領各位從無到有進行

nodejs應用程式開發

40 4 Nodejs基礎

5

NPM套件管理工具

npm全名為 Node Package Manager是 Nodejs的套件(package)管理工具類似 Perl的 ppm或 PHP的 PEAR等安裝 npm後使用npm install

module name指令即可安裝新套件維護管理套件的工作會更加輕鬆

npm可以讓Nodejs的開發者直接利用擴充線上的套件庫(packagesregistry)加速軟體專案的開發npm提供很友善的搜尋功能可以快速找到安裝需要的套件當這些套件發行新版本時npm也可以協助開發者自動更新這些套件

npm不僅可用於安裝新的套件它也支援搜尋列出已安裝模組及更新的功能

51 安裝 NPM

Nodejs在 063版本開始內建 npm讀者安裝的版本若是此版本或更新的版本就可以略過以下安裝說明

若要檢查 npm是否正確安裝可以使用以下的指令

npm -v

41

Nodejs中文電子書

執行結果說明

若 npm正確安裝執行 npm -v將會看到類似 110-2的版本訊息

若讀者安裝的 Nodejs版本比較舊或是有興趣嘗試自己動手安裝 npm工具則可以參考以下的說明

安裝於Windows系統

Nodejs for Windows 於 062 版開始內建 npm使用 nodejsorg 官方提供的安裝程式不需要進一步的設定就可以立即使用 npm指令對於Windows的開發者來說大幅降低環境設定的問題與門檻

除了使用 Nodejs內建的 npm讀者也可以從 npm官方提供的以下網址

httpnpmjsorgdist

這是由 npm提供的 Fancy Windows Install版本請下載壓縮檔(例如npm-110-3zip)並將壓縮檔內容解壓縮至 Nodejs的安裝路徑(例如CProgram Filesnodejs)

解壓縮後在 Nodejs的安裝路徑下應該有以下的檔案及資料夾

bull npmcmd(檔案)

bull node modules(資料夾)

安裝於 Linux系統

Ubuntu Linux的使用者可以加入 NPM Unoffcial PPA這個 repository即可使用 apt-get完成 npm安裝

42 5 NPM套件管理工具

Nodejs中文電子書

Ubuntu Linux使用 apt-get安裝 npm

sudo apt-get install python-software-properties

sudo add-apt-repository ppagias-kay-leenpm

sudo apt-get update

sudo apt-get install npm

npm官方提供的安裝程式 installsh可以適用於大多數的 Linux系統使用這個安裝程式請先確認

1 系統已安裝 curl工具(請使用 curl --version查看版本訊息)

2 已安裝 Nodejs並且 PATH正確設置

3 Nodejs的版本必須大於 04x

以下為 npm提供的安裝指令

curl httpnpmjsorginstallsh | sh

安裝成功會看到如下訊息

installsh安裝成功的訊息

npm10105 homeUSERNAMElocalnodelibnode_modulesnpm

It worked

安裝於Mac OS X

建議採用與 Nodejs相同的方式進行 npm的安裝例如使用MacPorts安裝 Nodejs就同樣使用MacPorts安裝 npm這樣對日後的維護才會更方便容易

使用 MacPorts安裝 npm是本書比較建議的方式它可以讓 npm的安裝移除及更新工作自動化將會幫助開發者節省寶貴時間

51 安裝 NPM 43

Nodejs中文電子書

安裝MacPorts的提示

在MacPorts網站可以取得 OS X系統版本對應的安裝程式(例如 106或 107)httpwwwmacportsorg安裝過程會詢問系統管理者密碼使用預設的選項完成安裝即可安

裝MacPorts之後在終端機執行 port -v將會看到MacPorts的版本訊息

安裝 npm之前先更新MacPorts的套件清單以確保安裝的 npm是最新版本

sudo port -d selfupdate

接著安裝 npm

sudo port install npm

若讀者的 Nodejs並非使用MacPorts安裝則不建議使用MacPorts安裝npm因為 MacPorts會自動檢查並安裝相依套件而 npm相依 nodejs所以MacPorts也會一併將 nodejs套件安裝造成先前讀者使用其它方式安裝的 nodejs被覆蓋

讀者可以先使用 MacPorts安裝 curl(sudo port install curl)

再參考 Linux的 installsh安裝方式即可使用 npm官方提供的安裝程式

NPM安裝後測試

npm是指令列工具(command-line tool)使用時請先打開系統的文字終端機工具

測試 npm安裝與設定是否正確請輸入指令如下

npm -v

或是

npm --version

如果 npm已經正確安裝設定就會顯示版本訊息

44 5 NPM套件管理工具

Nodejs中文電子書

執行結果(範例)

110-2

52 使用 NPM安裝套件

npm目前擁有超過 6000種套件(packages)可以在 npm registry使用關鍵字搜尋套件

httpsearchnpmjsorg

舉例來說在關鍵字欄位輸入「coffee-script」下方的清單就會自動列出包含 coffee-script關鍵字的套件

接著我們回到終端機模式的操作npm的指令工具本身就可以完成套

件搜尋的任務

例如以下的指令同樣可以找出 coffee-script相關套件

npm search coffee-script

以下是搜尋結果的參考畫面

52 使用 NPM安裝套件 45

Nodejs中文電子書

找到需要的套件後(例如 express)即可使用以下指令安裝

npm install coffee-script

值得注意的一點是使用 npm install會將指定的套件安裝在工

作目錄(Working Directory)的 node modules資料夾下

以Windows為例如果執行 npm install的目錄位於

Cproject1

那麼 npm將會自動建立一個 node modules的子目錄(如果不存在)

Cproject1node modules

並且將下載的套件放置於這個子目錄例如

Cproject1node modulescoffee-script

這個設計讓專案可以個別管理相依的套件並且可以在專案佈署或發

行時將這些套件(位於 node modules)一併打包方便其它專案的使用者不必再重新下載套件

這個 npm install的預設安裝模式為 local(本地)只會變更當前專案的資料夾不會影響系統

另一種安裝模式稱為 global(全域)這種模式會將套件安裝到系統資

料夾也就是 npm安裝路徑的 node modules資料夾例如

CProgram Filesnodejsnode modules

46 5 NPM套件管理工具

Nodejs中文電子書

是否要使用全域安裝可以依照套件是否提供新指令來判斷舉例來說express 套件提供 express 這個指令而 coffee-script 則提供 coffee

指令

在 local 安 裝 模 式 中這 些 指 令 的 程 式 檔 案會 被 安 裝 到node modules的 bin這個隱藏資料夾下除非將bin的路徑加入 PATH環境變數否則要執行這些指令將會相當不便

為了方便指令的執行我們可以在 npm install 加上 -g 或 --

global參數啟用 global安裝模式例如

npm install -g coffee-script

npm install -g express

使用 global安裝模式需要注意執行權限的問題若權限不足可能會出現類似以下的錯誤訊息

npm ERR Error EACCES permission denied rsquorsquo

npm ERR

npm ERR Please try running this command again as rootAdministrator

要獲得足夠得執行權限請參考以下說明

bull Windows 7 或 2008 以上在「命令提示字元」的捷徑按右鍵選擇「以系統管理員身分執行」執行 npm指令時就會具有 Administrator身分

bull Mac OS X或 Linux系統可以使用 sudo指令例如

sudo npm install -g express

bull Linux系統可以使用 root權限登入或是以「sudo su -」切換成 root身分(使用 root權限操作系統相當危險因此並不建議使用這種方式)

若加上 -g參數使用 npm install -g coffee-script完成安裝

後就可以在終端機執行 coffee指令例如

coffee -v

52 使用 NPM安裝套件 47

Nodejs中文電子書

執行結果(範例)

CoffeeScript version 120

53 套件的更新及維護

除了前一節說明的 search 及 install 用法npm 還提供其他許多指令(commands)

使用 npm help可以查詢可用的指令

npm help

執行結果(部分)

where ltcommandgt is one of

adduser apihelp author bin bugs c cache completion

config deprecate docs edit explore faq find get

help help-search home i info init install la link

list ll ln login ls outdated owner pack prefix

prune publish r rb rebuild remove restart rm root

run-script s se search set show star start stop

submodule tag test un uninstall unlink unpublish

unstar up update version view whoami

使用 npm help command可以查詢指令的詳細用法例如

npm help list

接下來本節要介紹開發過程常用的 npm指令

使用 list可以列出已安裝套件

npm list

48 5 NPM套件管理工具

Nodejs中文電子書

執行結果(範例)

-- coffee-script120

- express256

- connect185

| -- formidable108

-- mime124

-- mkdirp007

-- qs041

檢視某個套件的詳細資訊例如

npm show express

升級所有套件(如果該套件已發佈更新版本)

npm update

升級指定的套件

npm update express

移除指定的套件

npm uninstall express

54 使用 packagejson

對於正式的 Nodejs專案可以建立一個命名為 packagejson的設

定檔(純文字格式)檔案內容參考範例如下

54 使用 packagejson 49

Nodejs中文電子書

packagejson(範例)

rdquonamerdquo rdquoapplication-namerdquo

rdquoversionrdquo rdquo001rdquo

rdquoprivaterdquo true

rdquodependenciesrdquo

rdquoexpressrdquo rdquo255rdquo

rdquocoffee-scriptrdquo rdquolatestrdquo

rdquomongooserdquo rdquogt= 253rdquo

其中 name與 version依照專案的需求設置

需要注意的是 dependencies的設定它用於指定專案相依的套件名

稱及版本

bull rdquoexpressrdquo rdquo255rdquo

代表此專案相依版本 255的 express套件

bull rdquocoffee-scriptrdquo rdquolatestrdquo

使用最新版的 coffee-script套件(每次更新都會檢查新版)

bull rdquomongooserdquo rdquogt= 253rdquo

使用版本大於 253的 mongoose套件

假設某個套件的新版可能造成專案無法正常運作就必須指定套件的

版本避免專案的程式碼來不及更新以相容新版套件通常在開發初期的

專案需要盡可能維持新套件的相容性(以取得套件的更新或修正)可以

用「gt=」設定最低相容的版本或是使用「latest」設定永遠保持最新

套件

50 5 NPM套件管理工具

6

Express介紹

在前面的 nodejs基礎當中介紹許多許多開設 http的使用方法及介紹以及許多基本的 nodejs基本應用

接下來要介紹一個套件稱為 express [Express](httpexpressjscom)這個套件主要幫忙解決許多 nodejs http server所需要的基本服務讓開發 httpservice變得更為容易不需要像之前需要透過層層模組(module)才有辦法開始編寫自己的程式

這個套件是由 TJ Holowaychuk 製作而成的套件裡面包含基本的路由處理 (route)http資料處理(GETPOSTPUT)另外還與樣板套件(jshtml template engine)搭配同時也可以處理許多複雜化的問題

61 Express安裝

安裝方式十分簡單只要透過之前介紹的 NPM就可以使用簡單的指令安裝指令如下

這邊建議需要將此套件安裝成為全域模組方便日後使用

51

Nodejs中文電子書

62 Express基本操作

express的使用也十分簡單先來建立一個基本的 hello world

var app = require(rsquoexpressrsquo)createServer()

port = 1337

applisten(port)

appget(rsquorsquo function(req res)

ressend(rsquohello worldrsquo)

)

consolelog(rsquostart express servernrsquo)

可以從上面的程式碼發現基本操作與 nodejs http的建立方式沒有太大差異主要差在當我們設定路由時可以直接透過 appget方式設定回應與接受方式

63 Express路由處理

Express對於 http服務上有許多包裝讓開發者使用及設定上更為方便例如有幾個路由設定那我們就統一藉由 appget來處理

Create http server

appget(rsquorsquo function(req res)

ressend(rsquohello worldrsquo)

)

appget(rsquotestrsquo function(req res)

ressend(rsquotest renderrsquo)

)

appget(rsquouserrsquo function(req res)

ressend(rsquouser pagersquo)

)

52 6 Express介紹

Nodejs中文電子書

如上面的程式碼所表示appget可以帶入兩個參數第一個是路徑名稱設定第二個為回應函式 (call back function)回應函式裡面就如同之前的 createServer方法裡面包含 requestresponse兩個物件可供使用使用者就可以透過瀏覽器輸入不同的 url切換到不同的頁面顯示不同的結果

路由設定上也有基本的配對方式讓使用者從瀏覽器輸入的網址可以

是一個變數只要符合型態就可以有對應的頁面產出例如

Create http server

appget(rsquouseridrsquo function(req res)

ressend(rsquouser rsquo + reqparamsid)

)

appget(rsquonumberrsquo function(req res)

ressend(rsquonumber rsquo + reqparamsnumber)

)

裡 面 使 用 到number 從 網 址 輸 入 之 後 就 可 以 直 接 使 用reqparamsnumber 取得所輸入的資料變成 url 參數使用當然前面也是可以加上路徑的設定userid在瀏覽器上路徑必須符合userxxx透

過 reqparamsid就可以取到 xxx這個字串值

另外express參數處理也提供了路由參數配對處理也可以透過正規表示法作為參數設定

var app = require(rsquoexpressrsquo)createServer()

port = 1337

applisten(port)

appget(^ip((d23)((d23))((d23))((d23))) function(req res)

ressend(reqparams)

)

上面程式碼可以發現後面路由設定的型態是正規表示法裡面設定

格式為ip之後必須要加上 ip型態才會符合資料格式同時取得 ip資料已經由正規表示法將資料做分群因此可以取得 ip的四個數字

此程式執行之後可以透過瀏覽器測試輸入網址為 localhost3000ip25525510010可以從頁面獲得資料

63 Express路由處理 53

Nodejs中文電子書

此章節全部範例程式碼如下

overview

author Caesar Chi

blog clonnblogspotcom

version 20120226

create server

var app = require(rsquoexpressrsquo)createServer()

port = 1337

applisten(port)

normal style

appget(rsquorsquo function(req res)

ressend(rsquohello worldrsquo)

)

appget(rsquotestrsquo function(req res)

ressend(rsquotest renderrsquo)

)

parameter style

appget(rsquouseridrsquo function(req res)

ressend(rsquouser rsquo + reqparamsid)

)

appget(rsquonumberrsquo function(req res)

ressend(rsquonumber rsquo + reqparamsnumber)

)

REGX style

appget(^ip((d23)((d23))((d23))) function(req res)

ressend(reqparams)

)

appget(rsquorsquo function(req res)

ressend(rsquoPage not foundrsquo 404)

54 6 Express介紹

Nodejs中文電子書

)

consolelog(rsquostart express servernrsquo)

64 Express middleware

Express 裡面有一個十分好用的應用概念稱為 middleware可以透過middleware做出複雜的效果同時上面也有介紹 next方法參數傳遞就是靠 middleware的概念來傳遞參數讓開發者可以明確的控制程式邏輯

create http server

appuse(expressbodyParser())

appuse(expressmethodOverride())

appuse(expresssession())

上面都是一種 middleware的使用方式透過 appuse方式裡面載入函式執行方法回應函式會包含三個基本參數responserequestnext其中next表示下一個 middleware執行函式同時會自動將預設三個參數繼續帶往下個函式執行底下有個實驗

上面的片段程式執行後開啟瀏覽器連結上 localhost1337會發現伺服器回應結果順序如下

first middle ware

second middle ware

execute middle ware

end middleware function

從上面的結果可以得知剛才設定的 middleware都生效了在 appuse設定的 middleware是所有 url皆會執行方法如果有指定特定方法就可以使用 appget的 middleware設定在 appget函式的第二個參數就可以帶入函式或者是匿名函式只要函式裡面最後會接受 request response next這三個參數同時也有正確指定 next函式的執行時機最後都會執行到最後一個方法當然開發者也可以評估程式邏輯要執行到哪一個階段讓邏輯

可以更為分明

64 Express middleware 55

Nodejs中文電子書

65 Express路由應用

在實際開發上可能會遇到需要使用參數等方式混和變數一起使用

express裡面提供了一個很棒的處理方法 appall這個方式可以先採用基本路由配對再將設定為每個不同的處理方式開發者可以透過這個方式簡

化自己的程式邏輯

overview

author

version 20120226

create server

var app = require(rsquoexpressrsquo)createServer()

port = 1337

users = [

name rsquoClonnrsquo

name rsquoChirsquo

]

applisten(port)

appall(rsquouseridoprsquo function(req res next)

requser = users[reqparamsid]

if (requser)

next()

else

next(new Error(rsquocannot find user rsquo + reqparamsid))

)

appget(rsquouseridrsquo function(req res)

ressend(rsquoviewing rsquo + requsername)

)

appget(rsquouserideditrsquo function(req res)

ressend(rsquoediting rsquo + requsername)

)

56 6 Express介紹

Nodejs中文電子書

appget(rsquouseriddeletersquo function(req res)

ressend(rsquodeleting rsquo + requsername)

)

appget(rsquorsquo function(req res)

ressend(rsquoPage not foundrsquo 404)

)

consolelog(rsquostart express servernrsquo)

內部宣告一組預設的使用者分別給予名稱設定藉由 appall這個方法可以先將路由雛形建立再接下來設定 appget的路徑格式只要符合格式就會分配進入對應的方法中像上面的程式當中如果使用者輸入路徑為user0除了執行 appall程式之後執行 next方法就會對應到路徑設定為userid的這個方法當中如果使用者輸入路徑為user0edit就會執行到useridedit的對應方法

66 Express GET應用範例

我們準備一個使用 GET方法傳送資料的表單

ltDOCTYPE htmlgt

lthtmlgt

ltheadgt

ltmeta http-equiv=rdquoContent-Typerdquo content=rdquotexthtml charset=utf8rdquogt

lttitlegtNodejs 菜鳥筆記 (1)lttitlegt

ltlink rel=rdquostylesheetrdquo href=rdquocssstylecssrdquo type=rdquotextcssrdquo media=rdquoallrdquo gt

ltheadgt

ltbodygt

lth1gtNodejs 菜鳥筆記-註冊lth1gt

ltform id=rdquosignuprdquo method=rdquoGETrdquo action=rdquohttplocalhost3000Signuprdquogt

ltlabelgt 使用者名稱ltlabelgtltinput type=rdquotextrdquo id=rdquousernamerdquo name=rdquousernamerdquo gtltbrgt

ltlabelgt 電子郵件ltlabelgtltinput type=rdquotextrdquo id=rdquoemailrdquo name=rdquoemailrdquo gtltbrgt

ltinput type=rdquosubmitrdquo value=rdquo 註冊我的帳號rdquo gtltbrgt

ltformgt

ltbodygt

lthtmlgt

66 Express GET應用範例 57

Nodejs中文電子書

這個表單沒有什麼特別的地方我們只需要看第 9 行form 使用的method是 GET然後 action是rdquohttplocalhost3000Signupldquo等一下我們要來撰寫Signup這個 URL Path的處理程式

處理 Signup行為

我們知道所謂的 GET方法會透過 URL來把表單的值給帶過去以上面的表單來說到時候 URL會以這樣的形式傳遞

httplocalhost3000Signupusername=xxxampemail=xxx

所以要能處理這樣的資料必須有以下功能

bull 解析 URL

bull 辨別動作是 Signup

bull 解析出 username和 email

一旦能取得 username和 email的值程式就能加以應用了

處理 Signup的程式碼雛形

load module

var url = require(rsquourlrsquo)

urlData = urlparse(requrltrue)

action = urlDatapathname

reswriteHead(200 rdquoContent-Typerdquordquotexthtml charset=utf-8rdquo)

if (action === rdquoSignuprdquo)

user = urlDataquery

resend(rdquolth1gtrdquo + userusername + rdquo 歡迎您的加入 lth1gtltpgt 我們已經將會員啟用信寄至rdquo + useremail + rdquoltpgtrdquo)

首先需要加載 url module它是用來協助我們解析 URL的模組接著使用 urlparse方法第一個傳入 url字串變數也就是 requrl另外第二個參數的用意是設為 ture則引進 querystring模組來協助處理預設是 false它影響到的是 urlDataquery設為 true會傳回物件不然就只是一般的字串urlparse會將字串內容整理成一個物件我們把它指定給 urlData

action變數作為記錄 pathname這是我們稍後要來判斷目前網頁的動作是什麼接著先將 html表頭資訊 (Header)準備好再來判斷路徑邏輯如

58 6 Express介紹

Nodejs中文電子書

果是 Signup這個動作就把 urlDataquery裡的資料指定給 user然後輸出userusername和 useremail把使用者從表單註冊的資料顯示於頁面中

最後進行程式測試啟動 nodejs主程式之後開啟瀏覽器就會看到表單填寫完畢按下送出就可以看到結果了

完整 nodejs程式碼如下

var http = require(rsquohttprsquo)

url = require(rsquourlrsquo)

fs = require(rdquofsrdquo)

server

server = httpcreateServer(function (reqres)

var urlData

encode = rdquoutf8rdquo

filePath = rdquoviewexpress_get_example_formhtmlrdquo

action

urlData = urlparse(requrltrue)

action = urlDatapathname

reswriteHead(200 rdquoContent-Typerdquordquotexthtml charset=utf-8rdquo)

if (action === rdquoSignuprdquo)

user = urlDataquery

resend(rdquolth1gtrdquo + userusername + rdquo 歡迎您的加入 lth1gtltpgt 我們已經將會員啟用信寄至rdquo + useremail + rdquoltpgtrdquo)

else

fsreadFile(filePath encode function(err file)

reswrite(file)

resend()

)

)

serverlisten(3000)

consolelog(rsquoServer 跑起來了現在時間是rsquo + new Date())

66 Express GET應用範例 59

Nodejs中文電子書

67 Express POST應用範例

一開始準備基本的 html表單傳送內容以 POST方式form的 action屬性設定為 POST其餘 html內容與前一個範例應用相同

ltDOCTYPE htmlgt

lthtmlgt

ltheadgt

ltmeta http-equiv=rdquoContent-Typerdquo content=rdquotexthtml charset=utf8rdquogt

lttitlegtNodejs 菜鳥筆記 (1)lttitlegt

ltlink rel=rdquostylesheetrdquo href=rdquocssstylecssrdquo type=rdquotextcssrdquo media=rdquoallrdquo gt

ltheadgt

ltbodygt

lth1gtNodejs 菜鳥筆記-註冊lth1gt

ltform id=rdquosignuprdquo method=rdquoPOSTrdquo action=rdquohttplocalhost3000Signuprdquogt

ltlabelgt 使用者名稱ltlabelgtltinput type=rdquotextrdquo id=rdquousernamerdquo name=rdquousernamerdquo gtltbrgt

ltlabelgt 電子郵件ltlabelgtltinput type=rdquotextrdquo id=rdquoemailrdquo name=rdquoemailrdquo gtltbrgt

ltinput type=rdquosubmitrdquo value=rdquo 註冊我的帳號rdquo gtltbrgt

ltformgt

ltbodygt

lthtmlgt

nodejs的程式處理邏輯與前面 GET範例類似部分程式碼如下

qs = require(rsquoquerystringrsquo)

if (action === rdquoSignuprdquo)

formData = rsquorsquo

reqon(rdquodatardquo function (data)

formData += data

)

reqon(rdquoendrdquo function ()

user = qsparse(formData)

resend(rdquolth1gtrdquo + userusername + rdquo 歡迎您的加入 lth1gtltpgt 我們已經將會員啟用信寄至rdquo + useremail + rdquoltpgtrdquo)

)

60 6 Express介紹

Nodejs中文電子書

主要加入了rsquoquerystringrsquo這個moduel方便我們等一下解析由表單 POST回來的資料另外加入一個 formData的變數用來搜集待等一下表單回傳的資料前面的 GET範例我們只從 req拿出 url的資料這次要在利用req身上的事件處理

JavaScript 在訂閱事件時使用 addEventListener而 nodejs 使用的則是on這邊加上了監聽 data的事件會在瀏覽器傳送資料到Web Server時被執行參數是它所接收到的資料型態是字串

接著再增加 end的事件當瀏覽器的請求事件結束時它就會動作

由於瀏覽器使用 POST在上傳資料時會將資料一塊塊地上傳因為我們在監聽 data事件時透過 formData變數將它累加起來lt不過由於我們

上傳的資料很少一次就結束不過如果日後需要傳的是資料比較大的檔

案這個累加動作就很重要

當資料傳完就進到 end 事件中會用到 qsparse 來解析 formDataformData的內容是字串內容是

username=wordsmithampemail=wordsmith40somewhere

而 qsparse可以幫我們把這個 querystring轉成物件的格式也就是

username=wordsmithampemail=wordsmith40somewhere

一旦轉成物件並指定給 user之後其他的事情就和 GET方法時操作的一樣寫 response的表頭將內容回傳並將 userusername和 useremail代入到內容中

修改完成後接著執行 nodejs程式啟動 web server開啟瀏覽器進入表單測試看看POST的方式能否順利運作

完整程式碼如下

var http = require(rsquohttprsquo)

url = require(rsquourlrsquo)

fs = require(rdquofsrdquo)

qs = require(rsquoquerystringrsquo)

server

server = httpcreateServer(function (reqres)

var urlData

67 Express POST應用範例 61

Nodejs中文電子書

encode = rdquoutf8rdquo

filePath = rdquoviewexpress_post_example_formhtmlrdquo

formData

action

urlData = urlparse(requrltrue)

action = urlDatapathname

reswriteHead(200 rdquoContent-Typerdquordquotexthtml charset=utf-8rdquo)

if (action === rdquoSignuprdquo)

formData = rsquorsquo

reqon(rdquodatardquo function (data)

formData += data

)

reqon(rdquoendrdquo function ()

user = qsparse(formData)

resend(rdquolth1gtrdquo + userusername + rdquo 歡迎您的加入 lth1gtltpgt 我們已經將會員啟用信寄至rdquo + useremail + rdquoltpgtrdquo)

)

else

fsreadFile(filePath encode function(err file)

reswrite(file)

resend()

)

)

serverlisten(3000)

consolelog(rsquoServer 跑起來了現在時間是rsquo + new Date())

62 6 Express介紹

Nodejs中文電子書

68 Express AJAX應用範例

在 Nodejs要使用 Ajax傳送資料並且與之互動在接受資料的部份沒有太大的差別client端不是用 GET就是用 POST來傳資料重點在處理完後用 JSON格式回傳當然 Ajax不見得只傳 JSON格式有時是回傳一段 HTML碼不過後者對伺服器來說基本上就和前兩篇沒有差別了所以我們還是以回傳 JSON做為這一回的主題

這一回其實大多數的工作都會落在前端 Ajax上面前端要負責發送與接收資料並在接收資料後撤掉原先發送資料的表單並將取得的資料

改成 HTML格式之後放上頁面

首先先準備 HTML靜態頁面資料

ltDOCTYPE htmlgt

lthtmlgt

ltheadgt

lttitlegtNodejs 菜鳥筆記 (3)lttitlegt

ltlink rel=rdquostylesheetrdquo href=rdquocssstylecssrdquo type=rdquotextcssrdquo media=rdquoallrdquo gt

ltscript src=rdquohttpsajaxgoogleapiscomajaxlibsjquery171jqueryminjsrdquogtltscriptgt

ltheadgt

ltbodygt

lth1gtNodejs 菜鳥筆記-註冊 (用 Ajax)lth1gt

ltform id=rdquosignuprdquo action=rdquoSignuprdquo method=rdquoPOSTrdquogt

ltlabelgt 使用者名稱ltlabelgtltinput type=rdquotextrdquo id=rdquousernamerdquo name=rdquousernamerdquo gtltbrgt

ltlabelgt 電子郵件ltlabelgtltinput type=rdquotextrdquo id=rdquoemailrdquo name=rdquoemailrdquo gtltbrgt

ltinput type=rdquosubmitrdquo value=rdquo 註冊我的帳號rdquo gtltbrgt

ltformgt

ltdiv id=rdquoresultrdquogt

ltdivgt

ltscript type=rdquotextjavascriptrdquogt

$(function()$(rsquosignup input[type=rdquosubmitrdquo]rsquo)click(function(e)

var user =

userusername = $(rdquousernamerdquo)val()useremail = $(rdquoemailrdquo)val()

$post(rdquoSignuprdquouserfunction(data)greet(data)

)

68 Express AJAX應用範例 63

Nodejs中文電子書

epreventDefault()

)

var greet = function(msg)

var resultNode = $(rsquoresultrsquo)greeting = $(rsquolth2gtHirsquo+msgusername+rsquo 你的會員 id 是rsquo+ msgid +rsquo 我們會將會員啟動信寄至rsquo+msgemail+rsquolth2gtrsquo)

$(rsquosignuprsquo)hide()resultNodehtml(rdquordquo)

resultNodeappend(greeting)

)

ltscriptgt

ltbodygt

lthtmlgt

HTML頁面上準備了一個表單用來傳送註冊資料接著直接引用了Google CDN來載入 jQuery用來幫我們處理 Ajax的工作這次要傳送和接收的工作很大的變動都在 HTML頁面上的 JavaScript當中我們要做的事有 (相關 jQuery處理這邊不多做贅述指提起主要功能解說)

bull 用 jQUery取得 submit按鈕綁定它的 click動作

bull 取得表單 username和 email的值存放在 user這個物件中

bull 用 jQuery的 $post方法將 user的資料傳到 Server

bull 一旦成功取得資料後透過 greet這個 function組成回報給 user的訊息

bull 清空原本給使用者填資料的表單

bull 將 Server回傳的 usernameemail和 id這 3個資料組成回應的訊息

bull 將訊息放到原本表單的位置

經過以上的處理後一個 Ajax的表單的基本功能已經完成

接著進行 nodejs主要程式的編輯部分程式碼如下

var fs = require(rdquofsrdquo)

64 6 Express介紹

Nodejs中文電子書

qs = require(rsquoquerystringrsquo)

if (action === rdquoSignuprdquo)

formData = rsquorsquo

reqon(rdquodatardquo function (data)

formData += data

)

reqon(rdquoendrdquo function ()

var msg

user = qsparse(formData)

userid = rdquo123456rdquo

msg = JSONstringify(user)

reswriteHead(200 rdquoContent-Typerdquordquoapplicationjson charset=utf-8rdquordquoContent-Lengthrdquomsglength)

resend(msg)

)

這裡的程式和前面 POST範例基本上大同小異差別在

bull 幫 user的資料加上 id隨意存放一些文字進去讓 Server回傳的資料多於 Client端傳上來的不然會覺得 Server都沒做事

bull 增加了 msg 這個變數存放將 user 物件 JSON 文字化的結果JSONstringify這個轉換函式是 V8引擎所提供的如果你好奇的話

bull 大重點來了我們要告訴 Client端這次回傳的資料格式是 JSON所在 Content-type和 Content-Length要提供給 Client

Server很輕鬆就完成任務了最後進行程式測試啟動 nodejs主程式之後開啟瀏覽器就會看到表單填寫完畢按下送出就可以看到結果了

最後 nodejs本篇範例程式碼如下

var http = require(rsquohttprsquo)

url = require(rsquourlrsquo)

fs = require(rdquofsrdquo)

qs = require(rsquoquerystringrsquo)

server

68 Express AJAX應用範例 65

Nodejs中文電子書

server = httpcreateServer(function (reqres)

var urlData

encode = rdquoutf8rdquo

filePath = rdquoviewexpress_ajax_example_formhtmlrdquo

formData

action

urlData = urlparse(requrltrue)

action = urlDatapathname

if (action === rdquoSignuprdquo)

formData = rsquorsquo

reqon(rdquodatardquo function (data)

formData += data

)

reqon(rdquoendrdquo function ()

var msg

user = qsparse(formData)

userid = rdquo123456rdquo

msg = JSONstringify(user)

reswriteHead(200 rdquoContent-TyperdquordquoapplicationjsonrdquordquoContent-Lengthrdquomsglength)

resend(msg)

)

else

fsreadFile(filePath encode function(err file)

reswriteHead(200 rdquoContent-Typerdquordquotexthtml charset=utf-8rdquo)

reswrite(file)

resend()

)

)

serverlisten(3000)

66 6 Express介紹

Nodejs中文電子書

consolelog(rsquoServer 跑起來了現在時間是rsquo + new Date())

69 原始資料提供

bull [NodeJS初學者筆記 (1)-用 GET傳送資料] (httpithelpithomecomtwquestion10087402)

bull [NodeJS初學者筆記 (2)-用 POST傳送資料] (httpithelpithomecomtwquestion10087489)

bull [NodeJS 初學者筆記 (3)-用 Ajax 傳送資料] (httpithelpithomecomtwquestion10087627)

69 原始資料提供 67

Nodejs中文電子書

68 6 Express介紹

7

CoffeeScript

bull Spine

bull Hem

bull Spineapp

Letrsquos Have a Cup of CoffeeScript

bull es5-shim ECMAScript 5 compatibility shims for legacy JavaScript engines

ESnext

node-iform - A middleware for node-validator httpsgithubcomguileennode-iform

69

Nodejs中文電子書

70 7 CoffeeScript

8

製作一個 Hubot的 Plurk Adapter

81 應用事項提醒

此應用範例以CoffeeScript編寫主要程式架構採用Github釋放的Hubot專案以下有幾個主要注意事項請讀者注意

bull Hubot 一開始的架構除了內建的兩個 Adapter 之外其餘都要以Nodejs的Module方式才能運作

bull Hubot的 binhubot裡面寫著 npm install所以不管你怎麼改原始碼也不能改變第一點的狀況

其實上述都在討論同一件事情NodeJS的模組而且模組不能設定相對路徑之類的來安裝一定要包裝成 npm相符和格式才有辦法正確執行

82 建立 Adapter

首先需要準備兩個檔案

bull packagejson

bull plurkcoffee

71

Nodejs中文電子書

有這兩個就足以變成 NodeJS的模組了在開始 Coding之前先來設定一下 packagejson相依性

rdquonamerdquo rdquohubot-plurkrdquo

rdquoversionrdquo rdquo011rdquo

rdquomainrdquo rdquoplurkrdquo

rdquodependenciesrdquo

rdquohubotrdquo rdquogt=205rdquo

rdquooauthrdquo rdquordquo

rdquocronrdquordquordquo

這邊會使用到NodeJS的 cron模組(Module)而登入 Plurk需要OAuth標準授權才行所以也需要加載 OAuth模組

注意Hubot在讀取非內建模組時會自動在前面加上 hubot-的前置

83 建立 Robot跟 API

本篇應用基本上程式碼大部分參考 Hubot - Twitter Adapter來製作主要差異只有在使用 OAuth這個模組Twitter有 Streaming可用而 Plurk則得用 Comet方式來達到即時讀取

plurkcoffee程式碼

Robot = require(rdquohubotrdquo)robot()

Adapter = require(rdquohubtordquo)adapter()

EventEmitter = require(rdquoeventsrdquo)EventEmitter

oauth = require(rdquooauthrdquo)

cronJob = require(rdquocronrdquo)CronJob

class Plurk exntends Adapter

class PlurkStreaming exnteds EventEmitter

72 8 製作一個 Hubot的 Plurk Adapter

Nodejs中文電子書

先弄個基本架構主要 Class皆參考 Twitter Adapter命名方式接著再將 Plurk這個 Class增加幾個Method基本上只要有 run send reply就夠了而 run用來做初始化的部分

class Plurk entends Adapter

send (plurk id strings⋯) -gt

reply (plurk id strings⋯) -gt

run -gt

看起來有點東西接著來處理主要的 Plurk API結合部分

class PlurkStreaming extends EventEmitter

consuctor (options) -gt

plurk (callback) -gt

觀察河道

getChannel -gt

取得 Comet 網址

reply (plurk id message) -gt

回噗

acceptFriends -gt

接受好友

get (path callback) -gt

GET 請求

post (path body callback)-gt

POST 請求(其實是裝飾)

request (method path body callback)-gt

主要的 OAuth 請求

comet (server callback)-gt

噗浪的 Comet 傳回是 JavaScript Callback 要另外處理後才會變成 JSON

然後把注意力集中到 constructor上先把建構子弄好

constructor (options) -gt

super()

if optionskey and optionssecret and optionstoken and optionstoken secret

key = optionskey

secret = optionssecret

token = optionstoken

token secret = optionstoken secret

83 建立 Robot跟 API 73

Nodejs中文電子書

建立 OAuth 連接

consumer = new oauthOAuth(

rdquohttpwwwplurkcomOAuthrequest tokenrdquo

rdquohttpwwwplurkcomOAuthaccess tokenrdquo

key

secret

rdquo10rdquo

rdquohttpwwwplurkcomOAuthauthorizerdquo

rdquoHMAC-SHA1rdquo

)

domain = rdquowwwplurkcomrdquo

初始化取得 Comet 網址

do getChannel

else

throw new Error(rdquo 參數不足需要 Key Secret Token Token Secretrdquo)

接著來處理 request這個 method

request (method path body callback) -gt

記錄一下這次的 Request

consolelog(rdquohttpdomainpathrdquo)

Callback 這邊先不丟進去要用另一種方式處理

request = consumerget(rdquohttpdomainpathrdquo token token secret null)

requeston rdquoresponserdquo (res) -gt

reson rdquodatardquo (chunk) -gt

parseResponse(chunk+rsquorsquo callback)

reson rdquoendrdquo (data) -gt

consolelog rdquoEnd Request pathrdquo

reson rdquoerrorrdquo (data) -gt

consolelog rdquoError rdquo + data

requestend()

處理資料

parseResponse = (data callback) -gt

if datalength gt 0

用 TryCatch 避免處理 JSON 出錯導致整個中斷

try

callback null JSONparse(data)

catch err

74 8 製作一個 Hubot的 Plurk Adapter

Nodejs中文電子書

consolelog(rdquoError Parse JSONrdquo + data err)

繼續執行

callback null data ||

大致上就是這樣上面程式的架構已經將整個 Hubot Plurk Adapter完成因為在測試時竟然因為噗浪 Lag而沒讀到完整的 Comet資料然後造成程式異常為了避免這個問題發生因此需要加上為了完美呈現需要再

加上 Comet的處理所以要使用到 EventEmitter的功能

comet (server callback) -gt

在 Callback 裡面會找不到自身所以設定區域變數

self =

記錄一下這次的 Request

consolelog(rdquo[Comet] serverrdquo)

Callback 這邊先不丟進去要用另一種方式處理

request = consumerget(rdquohttpdomainpathrdquo token token secret null)

requeston rdquoresponserdquo (res) -gt

reson rdquodatardquo (chunk) -gt

parseResponse(chunk+rsquorsquo callback)

reson rdquoendrdquo (data) -gt

consolelog rdquoEnd Request pathrdquo

請求結束發出事件通知可以進行下一次請求

selfemit rdquonextPlurkrdquo

reson rdquoerrorrdquo (data) -gt

consolelog rdquoError rdquo + data

requestend()

處理資料

parseResponse = (data callback) -gt

if datalength gt 0

用 trycatch 避免失敗中斷

try

去掉 JavaScript 的 Callback

data = datamatch(CometChannelscriptCallback((+))s)

jsonData = rdquordquo

if data

83 建立 Robot跟 API 75

Nodejs中文電子書

jsonData = JSONparse(data[1])

else

如果沒有任何 Match 嘗試直接 parse

jsonData = JSONparse(data)

catch err

consolelog(rdquo[Comet] Errorrdquo data err)

用 TryCatch 避免處理 JSON 出錯導致整個中斷

try

只傳入 json 的 data 部分

callback null jsonDatadata

catch err

consolelog(rdquo[Comet]Error Parse JSONrdquo + data err)

繼續執行

callback null data ||

後面的 get跟 post就簡單多了

get (path callback) -gt

request(rdquoGETrdquo path null callback)

post (path body callback) -gt

request(rdquoPOSTrdquo path body callback)

接著處理取的 Comet網址的 getChannel

getChannel -gt

self =

get rdquoAPPRealtimegetUserChannelrdquo (error data) -gt

if error

檢查是否有 comet server

if datacomet server

selfchannel = datacomet server

如果沒有 Channel Ready 就嘗試連接會失敗

selfemit(rsquochannel readyrsquo)

那麼先來處理 Plurk Adaper好處理的部份

send (plruk id strings⋯)-gt

跟 Reply 一樣直接交給 reply 做

reply plurk id strings⋯

76 8 製作一個 Hubot的 Plurk Adapter

Nodejs中文電子書

reply (plurk id strings⋯) -gt

stringsforEach (message) =gt

botreply(plruk id message)

接著把 run處理好就可以上線運作摟

run -gt

self =

options =

key processenvHUBOT PLURK KEY

secret processenvHUBOT PLURK SECRET

token processenvHUBOT PLURK TOKEN

token secret processenvHUBOT PLURK TOKEN SECRET

創建剛剛的 API

bot = new PlurkStreaming(options)

依照 Twitter 的 new RobotTextMessage 會沒有反應所以參考 hubot-minecraft 的方式

r = robotconstructor

處理噗浪河道訊息

doPlurk = (data)-gt

檢查是否為回噗

if dataresponse

datacontent raw = dataresponsecontent raw

datauser id = dataresponseuser id

確定有噗浪 ID 跟訊息

if dataplurk id and datacontent raw

selfreceive new rTextMessage(dataplurk id datacontent raw)

取得 Comet Server 完成開始第一次 Comet 連接

boton rdquochannel readyrdquo () -gt

botplurk selfdoPlurk

上一次 Comet 完成繼續 Polling

boton rdquonextPlurkrdquo ()-gt

botplurk selfdoPlurk

定時接受好友邀請

do botacceptFriends

bot = bot

83 建立 Robot跟 API 77

Nodejs中文電子書

終於完成 AdapterHubot專案裡面的 scripts資料夾內是互動部分不需要像 Adapter如此大費周章處理只新增檔案並且設計好對白之後就會回噗了我開發用的機器人在此大家可以去跟他玩玩[httpplurkcomelct9620 bot](httpplurkcomelct9620 bot)

84 原始資料提供

bull [製 作 一 個 Hubot 的 噗 浪 Adapter](http revo-skillfrosttw blog20120318create-a-hubot-plurk-adapter)

78 8 製作一個 Hubot的 Plurk Adapter

9

Nodejs好用工具介紹

91 logio

Real-time log monitoring in your browser

httplogioorg

nodejs (gt=0412 lt=0611)

npm config set unsafe-perm true

npm install -g --prefix=usrlocal logio

logio server start

httplocalhost8998

79

Nodejs中文電子書

80 9 Nodejs好用工具介紹

10

精選文章

101 Express

bull 使用 NodeJS + Express從 GETPOST Request取值 [邀稿中]

bull link2 [邀稿中]

bull link3 [邀稿中]

81

Nodejs中文電子書

82 10 精選文章

11

參考資源

111 Nodejs書籍

bull Programming Nodejs April 2012 (est) Travis Swicegood Pragmatic Bookshelf

bull e Little Book on CoffeeScript January 2012 Alex MacCaw OrsquoReilly Media

bull Node for Front-End Developers January 2012 Garann Means OrsquoReilly Media

bull Building Hypermedia APIs with HTML5 and Node November 2011 MikeAmundsen OrsquoReilly Media

bull Node Web Development August 2011 David Herron PacktPub

bull CoffeeScript Accelerated JavaScript Development July 2011 Trevor BurnhamPragmatic Bookshelf

bull Getting Started with GEO CouchDB and Nodejs July 2011 Mick omp-son OrsquoReilly Media

bull What Is Node July 2011 Brett McLaughlin OrsquoReilly Media

bull Node Up and Running May 2011 Tom Hughes-Croucher Mike WilsonOrsquoReilly Media

83

Nodejs中文電子書

bull Hands-on Nodejs e Nodejs introduction and API reference May 2011 Pe-dro Teixeira LeanPub

bull e Node Beginner Book Manuel Kiessling

bull Mastering Nodejs visionmedia

112 Nodejs影音教學

bull e Node Sessions e Best of OSCON 2011 August 2011 OrsquoReilly Media(Video)

bull Tom Hughes-Croucher on Node March 2011 OrsquoReilly Media (Video)

113 Nodejs教學網站

bull Node Tuts

114 Nodejs課程

bull Introduction To Nodejs Van Nguyen CodeLesson

84 11 參考資源

Page 29: Nodejs Wiki

Nodejs中文電子書

else consolelog(err)

function(err data)

if(err) consolelog(lsquo[ attened by searial]rsquo) con-solelog(datatoString(lsquoutf8rsquo))

else consolelog(err)

]) fsstat(path cb)

fsstat(path function(err data) 第一層 callback if(err)

if(dataisFile)

fsreadFile(path function(err data) 第二層 callback if(err)

consolelog(lsquo[nested callbacks]rsquo) con-solelog(datatoString(lsquoutf8rsquo))

else consolelog(err)

)

else consolelog(err)

)

關鍵在於這些 callback的執行是有順序性的所以利用 serial返回的一個函數 cb來取代這些 callback然後在 cb中控制每次會循序呼叫的函數

26 流程控制 23

Nodejs中文電子書

就可以把巢狀的 callback攤平成循序的 function陣列(就是傳給 serial函數的參數)

測試中的dclientjs 是一個簡單的 dnode 測試程式放在跟 testSerialjs同一個目錄

var dnode = require(lsquodnodersquo)

dnodeconnect(8000 lsquolocalhostrsquo function(remote)

remoterestart(function(str) consolelog(str) processexit()

)

)

執行測試程式後出現結果

[ attened by searial] var dnode = require(lsquodnodersquo)

dnodeconnect(8000 lsquolocalhostrsquo function(remote)

remoterestart(function(str) consolelog(str) processexit()

)

)

[nested callbacks] var dnode = require(lsquodnodersquo)

dnodeconnect(8000 lsquolocalhostrsquo function(remote)

remoterestart(function(str) consolelog(str) processexit()

)

)

對照起來看兩種寫法的結果其實是一樣的但是利用 serialjs巢狀的 callback結構就會消失

不過這樣也只限於順序單純的狀況如果函數執行的順序比較複雜

(不只是一直線)還是需要用功能更完整的流程控制模組比較好例如

httpsgithubcomcaolanasync

24 2 JavaScript與 NodeJS

3

Nodejs安裝與設定

本篇將講解如何在各個不同 OS建立 NodeJS環境目前 NodeJS 048版本環境架設方式需依賴 Linux指令才可編譯完成當然在不同作業系統中也已經有 NodeJS package可以直接使用指令快速架設以下各不同作業系統解說如何安裝 NodeJS

31 Ubuntu Linux

更新推薦使用 nvm

git clone gitgithubcomcreationixnvmgit ~nvm

echo rdquo ~nvmnvmshrdquo gtgt ~bashrc

nvm install v0614

nvm alias default v0614

以 上 可 參 考 http dreamerslabcom blog tw how-to-setup-a-node-js-development-environment-on-ubuntu-11-04

使用 APT套件管理工具是常見的方法以下是使用社群提供的 PPA安裝方式

sudo apt-get install python-software-properties

sudo add-apt-repository ppachris-leanodejs-devel

25

Nodejs中文電子書

sudo apt-get update

sudo apt-get install nodejs

32 Other Linux

Linux很適合作為 NodeJS的伺服器作業系統及開發環境安裝前請先確認以下套件已正確安裝

bull curl (wget)用來下載檔案的工具

bull git先進的版本控制工具

bull g++ GNU C++軟體編譯工具

bull make GNU軟體專案建置工具

安裝指令如下如設有權限問題請在指令前面加上 sudo

git clone httpsgithubcomjoyentnodegit

cd node

git checkout v067

configure

make

sudo make install

接著測試 nodeJS是否正常執行

node --version

出現版本訊息即表示安裝成功

33 Windows

nodeJS 在 v060 版本之後開始正式支援 windows native直接使用nodeexe 就可以執行程式支援性完全與 linux 相同更棒的部份就是不需經過編譯經過下載之後簡單設定完成立即開發 node程式

26 3 Nodejs安裝與設定

Nodejs中文電子書

下載 nodejs安裝檔案 lthttpnodejsorgdownloadgt

如此完成 windows native nodeexe安裝接著可以進入 command line執行測試在 command line輸入rdquonoderdquo會出現 nodejs版本訊息畫面表示安裝完成

33 Windows 27

Nodejs中文電子書

28 3 Nodejs安裝與設定

4

Nodejs基礎

前篇文章已經由介紹安裝至設定都有完整介紹nodeJS 內部除了javascript常用的函式 (function)物件 (object)之外也有許多不同的自訂物件nodeJS預設建立這些物件為核心物件是為了要讓開發流程更為這些資料在官方文件已經具有許多具體說明接下來將會介紹在開發 nodeJS程式時常見的物件特性與使用方法

41 nodejs http伺服器建立

在 lsquonodejs官方網站 lthttpnodejsorggtlsquo裡面有舉一個最簡單的 HTTP伺服器建立一開始初步就是建立一個伺服器平台讓 nodejs可以與瀏覽器互相行為每種語言一開始的程式建立都是以 Hello world開始最初也從 Hello world帶各位進入 nodejs的世界

輸入以下程式碼儲存檔案為 node basic http hello worldjs

var server

ip = rdquo127001rdquo

port = 1337

http = require(rsquohttprsquo)

server = httpcreateServer(function (req res)

29

Nodejs中文電子書

reswriteHead(200 rsquoContent-Typersquo rsquotextplainrsquo)

resend(rsquoHello Worldnrsquo)

)

serverlisten(port ip)

consolelog(rdquoServer running at httprdquo + ip + rdquordquo + port)

程式碼解講一開始需要有幾個基本的變數

bull ip 機器本身的 ip位置因為使用本地端因此設定為 127001

bull port 需要開通的阜號通常設定為 http port 80因範例不希望與基本 port相衝隨意設定為 1337

在 nodejs 的程式中有許多預設的模組可以使用因此需要使用require方法將模組引入在這邊我們需要使用 http這個模組因此將 http載入Http模組裡面內建有許多方法可以使用這邊採用 createServer創建一個基本的 http伺服器再將 http伺服器給予一個 server變數裡面的回呼函式 (call back function)可以載入 http伺服器的資料與回應方法 (requestresponse)在程式裡面就可以看到我們直接回應給瀏覽器端所需的 Header回應內容

reswriteHead(200 rsquoContent-Typersquo rsquotextplainrsquo)

resend(rsquoHello Worldnrsquo)

Http伺服器需要設定 port ip在最後需要設定 Http監聽需要使用到listen事件監聽所有 Http伺服器行為

httplisten(port ip)

所有事情都完成之後需要確認伺服器正確執行因此使用 console在javascript裡就有這個原生物件console所印出的資料都會顯示於 nodejs伺服器頁面這邊印出的資料並不會傳送到使用者頁面上之後許多除壞

(debug)都會用到 console物件

consolelog(rdquoServer running at httprdquo + ip + rdquordquo + port)

30 4 Nodejs基礎

Nodejs中文電子書

42 nodejs http路徑建立

前面已經介紹如何建立一個簡單的 http伺服器接下來本章節將會介紹如何處理伺服器路徑 (rout)問題在 http的協定下所有從瀏覽器發出的要求 (request)都需要經過處理路徑上的建立也是如此

路徑就是指伺服器 ip位置或者是網域名稱之後對於伺服器給予的要求修改剛才的 hello world檔案修改如下

server = httpcreateServer(function (req res)

consolelog(requrl)

reswriteHead(200 rsquoContent-Typersquo rsquotextplainrsquo)

resend(rsquohello worldnrsquo)

)

重新啟動 nodejs程式後在瀏覽器端測試一下路徑行為結果如下圖

當在瀏覽器輸入 http1270011337test 在伺服器端會收到兩個要求一個是我們輸入的test要求另外一個則是faviconicotest的路徑要求http伺服器本身需要經過程式設定才有辦法回應給瀏覽器端所需要的回應在伺服器中所有的路徑要求都是需要被解析才有辦法取得資料從

42 nodejs http路徑建立 31

Nodejs中文電子書

上面解說可以了解到在 nodejs當中所有的路徑都需要經過設定未經過設定的路由會讓瀏覽器無法取得任何資料導致錯誤頁面的發生底下將會解

說如何設定路由同時避免發生錯誤情形先前 nodejs程式需要增加一些修改才能讓使用者透過瀏覽器在不同路徑時有不同的結果根據剛才

的程式做如下的修改

var server

ip = rdquo127001rdquo

port = 1337

http = require(rsquohttprsquo)

url = require(rsquourlrsquo)

path

server = httpcreateServer(function (req res)

path = urlparse(requrl)

reswriteHead(200 rsquoContent-Typersquo rsquotextplainrsquo)

switch (pathpathname)

case rdquoindexrdquo

resend(rsquoI am indexnrsquo)

break

case rdquotestrdquo

resend(rsquothis is test pagenrsquo)

break

default

resend(rsquodefault pagenrsquo)

break

)

serverlisten(port ip)

consolelog(rdquoServer running at httprdquo + ip + rdquordquo + port)

程式做了片段的修改首先載入 url模組另外增加一個 path變數url模組就跟如同他的命名一般專門處理 url字串處理裡面提供了許多方法來解決路徑上的問題因為從瀏覽器發出的要求路徑可能會帶有多種需求

或者 GET參數組合等因此我們需要將路徑單純化取用路徑部分的資料即可例如使用者可能會送出 http1270011337testsend=1如果直接

32 4 Nodejs基礎

Nodejs中文電子書

信任 requrl就會收到結果為testsend=1所以需要透過 url模組的方法將路徑資料過濾

在這邊使用 urlparse的方法裡面帶入網址格式資料會回傳路徑資料為了後需方便使用將回傳的資料設定到 path變數當中在回傳的路徑資料裡面包含資訊如下圖

這邊只需要使用單純的路徑要求直接取用 pathpathname就可以達到我們的目的

最後要做路徑的判別在不同的路徑可以指定不同的輸出在範例中

有三個可能結果第一個從瀏覽器輸入index就會顯示 index結果test 就會呈現出 test頁面最後如果都不符合預期的輸入會直接顯示 default的頁面最後的預防可以讓瀏覽器不會出現非預期結果讓程式的可靠性提昇

底下為測試結果

42 nodejs http路徑建立 33

Nodejs中文電子書

43 nodejs檔案讀取

前面已經介紹如何使用路由(rount)做出不同的回應實際應用只有在瀏覽器只有輸出幾個文字資料總是不夠的在本章節中將介紹如何使

用檔案讀取輸出檔案資料讓使用者在前端瀏覽器也可以讀取到完整的

html css javascript檔案輸出

檔案管理最重要的部分就是 lsquoFile system lthttpnodejsorgdocslatestapifshtmlgtlsquo這個模組此模組可以針對檔案做管理監控讀取等行為裡面有許多預設的方法底下是檔案輸出的基本範例底下會有兩個檔案

第一個是靜態 html檔案

ltDOCTYPE htmlgt

lthtml xmlns=rdquohttpwwww3org1999xhtmlrdquo xmllang=rdquozh-CNrdquo lang=rdquozh-CNrdquogt

ltheadgt

ltmeta http-equiv=rdquoContent-Typerdquo content=rdquotexthtml charset=UTF-7rdquo gt

lttitlegtnodejs index html filelttitlegt

ltheadgt

ltbodygt

34 4 Nodejs基礎

Nodejs中文電子書

lth1gtnodejs index html filelth1gt

ltbodygt

lthtmlgt

另一個為 nodejs程式

var fs = require(rdquofsrdquo)

filename = rdquostaticindexhtmlrdquo

encode = rdquoutf8rdquo

fsreadFile(filename encode function(err file)

consolelog(file)

)

一開始直接載入 le system模組 載入名稱為 fs讀取檔案主要使

用的方法為 readFile裡面以三個參數路徑 ( le path) 編碼方式 (encoding)

回應函式 (callback)路徑必須要設定為靜態 html所在位置才能指定到正確的檔案靜態檔案的編碼方式也必須正確這邊使用靜態檔案的編

碼為 utf8如果編碼設定錯誤nodejs讀取出來檔案結果會使用 byte raw格式輸出如果錯誤編碼格式會導致輸出資料為 byte raw

回應函式中裡面會使用兩個變數error為錯誤資訊如果讀取的檔案不存在或者發生錯誤error數值會是 true如果成功讀取資料 error將會是 falsecontent則是檔案內容資料讀取後將會把資料全數丟到 content這個變數當中

最後程式的輸出結果畫面如下

43 nodejs檔案讀取 35

Nodejs中文電子書

44 nodejs http靜態檔案輸出

前面已經了解如何讀取本地端檔案接下來將配合 http伺服器路由讓每個路由都能夠輸出相對應的靜態 html檔案首先新增加幾個靜態 html檔案

ltDOCTYPE htmlgt

lthtml xmlns=rdquohttpwwww3org1999xhtmlrdquo xmllang=rdquozh-CNrdquo lang=rdquozh-CNrdquogt

ltheadgt

ltmeta http-equiv=rdquoContent-Typerdquo content=rdquotexthtml charset=UTF-7rdquo gt

lttitlegtnodejs index html filelttitlegt

ltheadgt

ltbodygt

lth1gtnodejs index html filelth1gt

ltbodygt

lthtmlgt

ltDOCTYPE htmlgt

lthtml xmlns=rdquohttpwwww3org1999xhtmlrdquo xmllang=rdquozh-CNrdquo lang=rdquozh-CNrdquogt

ltheadgt

ltmeta http-equiv=rdquoContent-Typerdquo content=rdquotexthtml charset=UTF-7rdquo gt

lttitlegtnodejs test html filelttitlegt

ltheadgt

ltbodygt

lth1gtnodejs test html filelth1gt

ltbodygt

lthtmlgt

ltDOCTYPE htmlgt

lthtml xmlns=rdquohttpwwww3org1999xhtmlrdquo xmllang=rdquozh-CNrdquo lang=rdquozh-CNrdquogt

ltheadgt

ltmeta http-equiv=rdquoContent-Typerdquo content=rdquotexthtml charset=UTF-7rdquo gt

lttitlegtnodejs static html filelttitlegt

ltheadgt

ltbodygt

lth1gtnodejs static html filelth1gt

ltbodygt

lthtmlgt

36 4 Nodejs基礎

Nodejs中文電子書

準備一個包含基本路由功能的 http伺服器

var server

ip = rdquo127001rdquo

port = 1337

http = require(rsquohttprsquo)

url = require(rsquourlrsquo)

server = httpcreateServer(function (req res)

var path = urlparse(requrl)

)

serverlisten(port ip)

consolelog(rdquoServer running at httprdquo + ip + rdquordquo + port)

加入 le system 模組使用 readFile 的功能將這一段程式放置於

createServer的回應函式中

fsreadFile(filePath encode function(err file)

)

readFile的回應函式裡面加入頁面輸出讓瀏覽器可以正確讀到檔案在這邊我們設定讀取的檔案為 html 靜態檔案所以 Content-type 設定為texthtml讀取到檔案的內容將會正確輸出成 html靜態檔案

fsreadFile(filePath encode function(err file)

reswriteHead(200 rsquoContent-Typersquo rsquotexthtmlrsquo)

reswrite(file)

resend()

)

到這邊為止基本的程式內容都已經完成剩下一些細節的調整首先

路徑上必須做調整目前的靜態檔案全部都放置於 static資料夾底下設定一個變數來記住資料夾位置

接著將瀏覽器發出要求路徑與資料夾組合讀取正確 html靜態檔案使用者有可能會輸入錯誤路徑所以在讀取檔案的時候要加入錯誤處理

同時回應 404伺服器無法正確回應的 http header格式

加入這些細節的修改一個基本的 http 靜態 html輸出伺服器就完成了完整程式碼如下

44 nodejs http靜態檔案輸出 37

Nodejs中文電子書

var server

ip = rdquo127001rdquo

port = 1337

http = require(rsquohttprsquo)

fs = require(rdquofsrdquo)

folderPath = rdquostaticrdquo

url = require(rsquourlrsquo)

path

filePath

encode = rdquoutf8rdquo

server = httpcreateServer(function (req res)

path = urlparse(requrl)

filePath = folderPath + pathpathname

fsreadFile(filePath encode function(err file)

if (err)

reswriteHead(404 rsquoContent-Typersquo rsquotextplainrsquo)

resend()

return

reswriteHead(200 rsquoContent-Typersquo rsquotextapplicationrsquo)

reswrite(file)

resend()

)

)

serverlisten(port ip)

consolelog(rdquoServer running at httprdquo + ip + rdquordquo + port)

45 nodejs http GET資料擷取

http伺服器中除了路由之外另一個最常使用的方法就是擷取 GET資料本單元將會介紹如何透過基本 http伺服器擷取瀏覽器傳來的要求擷取 GET資料

在 http協定中GET參數都是藉由 URL從瀏覽器發出要求送至伺服器

38 4 Nodejs基礎

Nodejs中文電子書

端基本的傳送網址格式可能如下

http127001testsend=1amptest=2

上面這段網址裡面的 GET參數就是 send而這個變數的數值就為 1如果想要在 http伺服器取得 GET資料需要在瀏覽器給予的要求 (request)做處理

首先需要載入 query string這個模組這個模組主要是用來將字串資料

過濾後轉換成javascript物件

qs = require(rsquoquerystringrsquo)

接著在第一階段利用 url模組過濾瀏覽器發出的 URL資料後將回應的物件裡面的 query這個變數是一個字串值資料過濾後如下

send=1amptest=2

透過 query string使用 parse這個方法將資料轉換成 javascript物件就表示 GET的資料已經被伺服器端正式擷取下來

path = urlparse(requrl)

parameter = qsparse(pathquery)

整個 nodejs http GET參數完整擷取程式碼如下

var server

ip = rdquo127001rdquo

port = 1337

http = require(rsquohttprsquo)

qs = require(rsquoquerystringrsquo)

url = require(rsquourlrsquo)

server = httpcreateServer(function (req res)

var path = urlparse(requrl)

parameter = qsparse(pathquery)

consoledir(parameter)

reswriteHead(200 rsquoContent-Typersquo rsquotextplainrsquo)

reswrite(rsquoBrowser test GET parameternrsquo)

resend()

)

45 nodejs http GET資料擷取 39

Nodejs中文電子書

serverlisten(port ip)

consolelog(rdquoServer running at httprdquo + ip + rdquordquo + port)

程式運作之後由瀏覽器輸入要求網址之後nodejs伺服器端回應資料為

Server running at http1270011337

send rsquo1rsquo test rsquo1rsquo

46 本章結語

前面所解說的部份一大部分主要是處理 http伺服器基本問題雖然在某些部分有牽扯到 http 伺服器基本運作原理主要還是希望可以藉由這些基本範例練習 nodejs練習回應函式與語法串接的特點習慣編寫javascript風格程式當然直接這樣開發 nodejs是非常辛苦的接下來在模組實戰開發的部份將會介紹特定的模組一步一步帶領各位從無到有進行

nodejs應用程式開發

40 4 Nodejs基礎

5

NPM套件管理工具

npm全名為 Node Package Manager是 Nodejs的套件(package)管理工具類似 Perl的 ppm或 PHP的 PEAR等安裝 npm後使用npm install

module name指令即可安裝新套件維護管理套件的工作會更加輕鬆

npm可以讓Nodejs的開發者直接利用擴充線上的套件庫(packagesregistry)加速軟體專案的開發npm提供很友善的搜尋功能可以快速找到安裝需要的套件當這些套件發行新版本時npm也可以協助開發者自動更新這些套件

npm不僅可用於安裝新的套件它也支援搜尋列出已安裝模組及更新的功能

51 安裝 NPM

Nodejs在 063版本開始內建 npm讀者安裝的版本若是此版本或更新的版本就可以略過以下安裝說明

若要檢查 npm是否正確安裝可以使用以下的指令

npm -v

41

Nodejs中文電子書

執行結果說明

若 npm正確安裝執行 npm -v將會看到類似 110-2的版本訊息

若讀者安裝的 Nodejs版本比較舊或是有興趣嘗試自己動手安裝 npm工具則可以參考以下的說明

安裝於Windows系統

Nodejs for Windows 於 062 版開始內建 npm使用 nodejsorg 官方提供的安裝程式不需要進一步的設定就可以立即使用 npm指令對於Windows的開發者來說大幅降低環境設定的問題與門檻

除了使用 Nodejs內建的 npm讀者也可以從 npm官方提供的以下網址

httpnpmjsorgdist

這是由 npm提供的 Fancy Windows Install版本請下載壓縮檔(例如npm-110-3zip)並將壓縮檔內容解壓縮至 Nodejs的安裝路徑(例如CProgram Filesnodejs)

解壓縮後在 Nodejs的安裝路徑下應該有以下的檔案及資料夾

bull npmcmd(檔案)

bull node modules(資料夾)

安裝於 Linux系統

Ubuntu Linux的使用者可以加入 NPM Unoffcial PPA這個 repository即可使用 apt-get完成 npm安裝

42 5 NPM套件管理工具

Nodejs中文電子書

Ubuntu Linux使用 apt-get安裝 npm

sudo apt-get install python-software-properties

sudo add-apt-repository ppagias-kay-leenpm

sudo apt-get update

sudo apt-get install npm

npm官方提供的安裝程式 installsh可以適用於大多數的 Linux系統使用這個安裝程式請先確認

1 系統已安裝 curl工具(請使用 curl --version查看版本訊息)

2 已安裝 Nodejs並且 PATH正確設置

3 Nodejs的版本必須大於 04x

以下為 npm提供的安裝指令

curl httpnpmjsorginstallsh | sh

安裝成功會看到如下訊息

installsh安裝成功的訊息

npm10105 homeUSERNAMElocalnodelibnode_modulesnpm

It worked

安裝於Mac OS X

建議採用與 Nodejs相同的方式進行 npm的安裝例如使用MacPorts安裝 Nodejs就同樣使用MacPorts安裝 npm這樣對日後的維護才會更方便容易

使用 MacPorts安裝 npm是本書比較建議的方式它可以讓 npm的安裝移除及更新工作自動化將會幫助開發者節省寶貴時間

51 安裝 NPM 43

Nodejs中文電子書

安裝MacPorts的提示

在MacPorts網站可以取得 OS X系統版本對應的安裝程式(例如 106或 107)httpwwwmacportsorg安裝過程會詢問系統管理者密碼使用預設的選項完成安裝即可安

裝MacPorts之後在終端機執行 port -v將會看到MacPorts的版本訊息

安裝 npm之前先更新MacPorts的套件清單以確保安裝的 npm是最新版本

sudo port -d selfupdate

接著安裝 npm

sudo port install npm

若讀者的 Nodejs並非使用MacPorts安裝則不建議使用MacPorts安裝npm因為 MacPorts會自動檢查並安裝相依套件而 npm相依 nodejs所以MacPorts也會一併將 nodejs套件安裝造成先前讀者使用其它方式安裝的 nodejs被覆蓋

讀者可以先使用 MacPorts安裝 curl(sudo port install curl)

再參考 Linux的 installsh安裝方式即可使用 npm官方提供的安裝程式

NPM安裝後測試

npm是指令列工具(command-line tool)使用時請先打開系統的文字終端機工具

測試 npm安裝與設定是否正確請輸入指令如下

npm -v

或是

npm --version

如果 npm已經正確安裝設定就會顯示版本訊息

44 5 NPM套件管理工具

Nodejs中文電子書

執行結果(範例)

110-2

52 使用 NPM安裝套件

npm目前擁有超過 6000種套件(packages)可以在 npm registry使用關鍵字搜尋套件

httpsearchnpmjsorg

舉例來說在關鍵字欄位輸入「coffee-script」下方的清單就會自動列出包含 coffee-script關鍵字的套件

接著我們回到終端機模式的操作npm的指令工具本身就可以完成套

件搜尋的任務

例如以下的指令同樣可以找出 coffee-script相關套件

npm search coffee-script

以下是搜尋結果的參考畫面

52 使用 NPM安裝套件 45

Nodejs中文電子書

找到需要的套件後(例如 express)即可使用以下指令安裝

npm install coffee-script

值得注意的一點是使用 npm install會將指定的套件安裝在工

作目錄(Working Directory)的 node modules資料夾下

以Windows為例如果執行 npm install的目錄位於

Cproject1

那麼 npm將會自動建立一個 node modules的子目錄(如果不存在)

Cproject1node modules

並且將下載的套件放置於這個子目錄例如

Cproject1node modulescoffee-script

這個設計讓專案可以個別管理相依的套件並且可以在專案佈署或發

行時將這些套件(位於 node modules)一併打包方便其它專案的使用者不必再重新下載套件

這個 npm install的預設安裝模式為 local(本地)只會變更當前專案的資料夾不會影響系統

另一種安裝模式稱為 global(全域)這種模式會將套件安裝到系統資

料夾也就是 npm安裝路徑的 node modules資料夾例如

CProgram Filesnodejsnode modules

46 5 NPM套件管理工具

Nodejs中文電子書

是否要使用全域安裝可以依照套件是否提供新指令來判斷舉例來說express 套件提供 express 這個指令而 coffee-script 則提供 coffee

指令

在 local 安 裝 模 式 中這 些 指 令 的 程 式 檔 案會 被 安 裝 到node modules的 bin這個隱藏資料夾下除非將bin的路徑加入 PATH環境變數否則要執行這些指令將會相當不便

為了方便指令的執行我們可以在 npm install 加上 -g 或 --

global參數啟用 global安裝模式例如

npm install -g coffee-script

npm install -g express

使用 global安裝模式需要注意執行權限的問題若權限不足可能會出現類似以下的錯誤訊息

npm ERR Error EACCES permission denied rsquorsquo

npm ERR

npm ERR Please try running this command again as rootAdministrator

要獲得足夠得執行權限請參考以下說明

bull Windows 7 或 2008 以上在「命令提示字元」的捷徑按右鍵選擇「以系統管理員身分執行」執行 npm指令時就會具有 Administrator身分

bull Mac OS X或 Linux系統可以使用 sudo指令例如

sudo npm install -g express

bull Linux系統可以使用 root權限登入或是以「sudo su -」切換成 root身分(使用 root權限操作系統相當危險因此並不建議使用這種方式)

若加上 -g參數使用 npm install -g coffee-script完成安裝

後就可以在終端機執行 coffee指令例如

coffee -v

52 使用 NPM安裝套件 47

Nodejs中文電子書

執行結果(範例)

CoffeeScript version 120

53 套件的更新及維護

除了前一節說明的 search 及 install 用法npm 還提供其他許多指令(commands)

使用 npm help可以查詢可用的指令

npm help

執行結果(部分)

where ltcommandgt is one of

adduser apihelp author bin bugs c cache completion

config deprecate docs edit explore faq find get

help help-search home i info init install la link

list ll ln login ls outdated owner pack prefix

prune publish r rb rebuild remove restart rm root

run-script s se search set show star start stop

submodule tag test un uninstall unlink unpublish

unstar up update version view whoami

使用 npm help command可以查詢指令的詳細用法例如

npm help list

接下來本節要介紹開發過程常用的 npm指令

使用 list可以列出已安裝套件

npm list

48 5 NPM套件管理工具

Nodejs中文電子書

執行結果(範例)

-- coffee-script120

- express256

- connect185

| -- formidable108

-- mime124

-- mkdirp007

-- qs041

檢視某個套件的詳細資訊例如

npm show express

升級所有套件(如果該套件已發佈更新版本)

npm update

升級指定的套件

npm update express

移除指定的套件

npm uninstall express

54 使用 packagejson

對於正式的 Nodejs專案可以建立一個命名為 packagejson的設

定檔(純文字格式)檔案內容參考範例如下

54 使用 packagejson 49

Nodejs中文電子書

packagejson(範例)

rdquonamerdquo rdquoapplication-namerdquo

rdquoversionrdquo rdquo001rdquo

rdquoprivaterdquo true

rdquodependenciesrdquo

rdquoexpressrdquo rdquo255rdquo

rdquocoffee-scriptrdquo rdquolatestrdquo

rdquomongooserdquo rdquogt= 253rdquo

其中 name與 version依照專案的需求設置

需要注意的是 dependencies的設定它用於指定專案相依的套件名

稱及版本

bull rdquoexpressrdquo rdquo255rdquo

代表此專案相依版本 255的 express套件

bull rdquocoffee-scriptrdquo rdquolatestrdquo

使用最新版的 coffee-script套件(每次更新都會檢查新版)

bull rdquomongooserdquo rdquogt= 253rdquo

使用版本大於 253的 mongoose套件

假設某個套件的新版可能造成專案無法正常運作就必須指定套件的

版本避免專案的程式碼來不及更新以相容新版套件通常在開發初期的

專案需要盡可能維持新套件的相容性(以取得套件的更新或修正)可以

用「gt=」設定最低相容的版本或是使用「latest」設定永遠保持最新

套件

50 5 NPM套件管理工具

6

Express介紹

在前面的 nodejs基礎當中介紹許多許多開設 http的使用方法及介紹以及許多基本的 nodejs基本應用

接下來要介紹一個套件稱為 express [Express](httpexpressjscom)這個套件主要幫忙解決許多 nodejs http server所需要的基本服務讓開發 httpservice變得更為容易不需要像之前需要透過層層模組(module)才有辦法開始編寫自己的程式

這個套件是由 TJ Holowaychuk 製作而成的套件裡面包含基本的路由處理 (route)http資料處理(GETPOSTPUT)另外還與樣板套件(jshtml template engine)搭配同時也可以處理許多複雜化的問題

61 Express安裝

安裝方式十分簡單只要透過之前介紹的 NPM就可以使用簡單的指令安裝指令如下

這邊建議需要將此套件安裝成為全域模組方便日後使用

51

Nodejs中文電子書

62 Express基本操作

express的使用也十分簡單先來建立一個基本的 hello world

var app = require(rsquoexpressrsquo)createServer()

port = 1337

applisten(port)

appget(rsquorsquo function(req res)

ressend(rsquohello worldrsquo)

)

consolelog(rsquostart express servernrsquo)

可以從上面的程式碼發現基本操作與 nodejs http的建立方式沒有太大差異主要差在當我們設定路由時可以直接透過 appget方式設定回應與接受方式

63 Express路由處理

Express對於 http服務上有許多包裝讓開發者使用及設定上更為方便例如有幾個路由設定那我們就統一藉由 appget來處理

Create http server

appget(rsquorsquo function(req res)

ressend(rsquohello worldrsquo)

)

appget(rsquotestrsquo function(req res)

ressend(rsquotest renderrsquo)

)

appget(rsquouserrsquo function(req res)

ressend(rsquouser pagersquo)

)

52 6 Express介紹

Nodejs中文電子書

如上面的程式碼所表示appget可以帶入兩個參數第一個是路徑名稱設定第二個為回應函式 (call back function)回應函式裡面就如同之前的 createServer方法裡面包含 requestresponse兩個物件可供使用使用者就可以透過瀏覽器輸入不同的 url切換到不同的頁面顯示不同的結果

路由設定上也有基本的配對方式讓使用者從瀏覽器輸入的網址可以

是一個變數只要符合型態就可以有對應的頁面產出例如

Create http server

appget(rsquouseridrsquo function(req res)

ressend(rsquouser rsquo + reqparamsid)

)

appget(rsquonumberrsquo function(req res)

ressend(rsquonumber rsquo + reqparamsnumber)

)

裡 面 使 用 到number 從 網 址 輸 入 之 後 就 可 以 直 接 使 用reqparamsnumber 取得所輸入的資料變成 url 參數使用當然前面也是可以加上路徑的設定userid在瀏覽器上路徑必須符合userxxx透

過 reqparamsid就可以取到 xxx這個字串值

另外express參數處理也提供了路由參數配對處理也可以透過正規表示法作為參數設定

var app = require(rsquoexpressrsquo)createServer()

port = 1337

applisten(port)

appget(^ip((d23)((d23))((d23))((d23))) function(req res)

ressend(reqparams)

)

上面程式碼可以發現後面路由設定的型態是正規表示法裡面設定

格式為ip之後必須要加上 ip型態才會符合資料格式同時取得 ip資料已經由正規表示法將資料做分群因此可以取得 ip的四個數字

此程式執行之後可以透過瀏覽器測試輸入網址為 localhost3000ip25525510010可以從頁面獲得資料

63 Express路由處理 53

Nodejs中文電子書

此章節全部範例程式碼如下

overview

author Caesar Chi

blog clonnblogspotcom

version 20120226

create server

var app = require(rsquoexpressrsquo)createServer()

port = 1337

applisten(port)

normal style

appget(rsquorsquo function(req res)

ressend(rsquohello worldrsquo)

)

appget(rsquotestrsquo function(req res)

ressend(rsquotest renderrsquo)

)

parameter style

appget(rsquouseridrsquo function(req res)

ressend(rsquouser rsquo + reqparamsid)

)

appget(rsquonumberrsquo function(req res)

ressend(rsquonumber rsquo + reqparamsnumber)

)

REGX style

appget(^ip((d23)((d23))((d23))) function(req res)

ressend(reqparams)

)

appget(rsquorsquo function(req res)

ressend(rsquoPage not foundrsquo 404)

54 6 Express介紹

Nodejs中文電子書

)

consolelog(rsquostart express servernrsquo)

64 Express middleware

Express 裡面有一個十分好用的應用概念稱為 middleware可以透過middleware做出複雜的效果同時上面也有介紹 next方法參數傳遞就是靠 middleware的概念來傳遞參數讓開發者可以明確的控制程式邏輯

create http server

appuse(expressbodyParser())

appuse(expressmethodOverride())

appuse(expresssession())

上面都是一種 middleware的使用方式透過 appuse方式裡面載入函式執行方法回應函式會包含三個基本參數responserequestnext其中next表示下一個 middleware執行函式同時會自動將預設三個參數繼續帶往下個函式執行底下有個實驗

上面的片段程式執行後開啟瀏覽器連結上 localhost1337會發現伺服器回應結果順序如下

first middle ware

second middle ware

execute middle ware

end middleware function

從上面的結果可以得知剛才設定的 middleware都生效了在 appuse設定的 middleware是所有 url皆會執行方法如果有指定特定方法就可以使用 appget的 middleware設定在 appget函式的第二個參數就可以帶入函式或者是匿名函式只要函式裡面最後會接受 request response next這三個參數同時也有正確指定 next函式的執行時機最後都會執行到最後一個方法當然開發者也可以評估程式邏輯要執行到哪一個階段讓邏輯

可以更為分明

64 Express middleware 55

Nodejs中文電子書

65 Express路由應用

在實際開發上可能會遇到需要使用參數等方式混和變數一起使用

express裡面提供了一個很棒的處理方法 appall這個方式可以先採用基本路由配對再將設定為每個不同的處理方式開發者可以透過這個方式簡

化自己的程式邏輯

overview

author

version 20120226

create server

var app = require(rsquoexpressrsquo)createServer()

port = 1337

users = [

name rsquoClonnrsquo

name rsquoChirsquo

]

applisten(port)

appall(rsquouseridoprsquo function(req res next)

requser = users[reqparamsid]

if (requser)

next()

else

next(new Error(rsquocannot find user rsquo + reqparamsid))

)

appget(rsquouseridrsquo function(req res)

ressend(rsquoviewing rsquo + requsername)

)

appget(rsquouserideditrsquo function(req res)

ressend(rsquoediting rsquo + requsername)

)

56 6 Express介紹

Nodejs中文電子書

appget(rsquouseriddeletersquo function(req res)

ressend(rsquodeleting rsquo + requsername)

)

appget(rsquorsquo function(req res)

ressend(rsquoPage not foundrsquo 404)

)

consolelog(rsquostart express servernrsquo)

內部宣告一組預設的使用者分別給予名稱設定藉由 appall這個方法可以先將路由雛形建立再接下來設定 appget的路徑格式只要符合格式就會分配進入對應的方法中像上面的程式當中如果使用者輸入路徑為user0除了執行 appall程式之後執行 next方法就會對應到路徑設定為userid的這個方法當中如果使用者輸入路徑為user0edit就會執行到useridedit的對應方法

66 Express GET應用範例

我們準備一個使用 GET方法傳送資料的表單

ltDOCTYPE htmlgt

lthtmlgt

ltheadgt

ltmeta http-equiv=rdquoContent-Typerdquo content=rdquotexthtml charset=utf8rdquogt

lttitlegtNodejs 菜鳥筆記 (1)lttitlegt

ltlink rel=rdquostylesheetrdquo href=rdquocssstylecssrdquo type=rdquotextcssrdquo media=rdquoallrdquo gt

ltheadgt

ltbodygt

lth1gtNodejs 菜鳥筆記-註冊lth1gt

ltform id=rdquosignuprdquo method=rdquoGETrdquo action=rdquohttplocalhost3000Signuprdquogt

ltlabelgt 使用者名稱ltlabelgtltinput type=rdquotextrdquo id=rdquousernamerdquo name=rdquousernamerdquo gtltbrgt

ltlabelgt 電子郵件ltlabelgtltinput type=rdquotextrdquo id=rdquoemailrdquo name=rdquoemailrdquo gtltbrgt

ltinput type=rdquosubmitrdquo value=rdquo 註冊我的帳號rdquo gtltbrgt

ltformgt

ltbodygt

lthtmlgt

66 Express GET應用範例 57

Nodejs中文電子書

這個表單沒有什麼特別的地方我們只需要看第 9 行form 使用的method是 GET然後 action是rdquohttplocalhost3000Signupldquo等一下我們要來撰寫Signup這個 URL Path的處理程式

處理 Signup行為

我們知道所謂的 GET方法會透過 URL來把表單的值給帶過去以上面的表單來說到時候 URL會以這樣的形式傳遞

httplocalhost3000Signupusername=xxxampemail=xxx

所以要能處理這樣的資料必須有以下功能

bull 解析 URL

bull 辨別動作是 Signup

bull 解析出 username和 email

一旦能取得 username和 email的值程式就能加以應用了

處理 Signup的程式碼雛形

load module

var url = require(rsquourlrsquo)

urlData = urlparse(requrltrue)

action = urlDatapathname

reswriteHead(200 rdquoContent-Typerdquordquotexthtml charset=utf-8rdquo)

if (action === rdquoSignuprdquo)

user = urlDataquery

resend(rdquolth1gtrdquo + userusername + rdquo 歡迎您的加入 lth1gtltpgt 我們已經將會員啟用信寄至rdquo + useremail + rdquoltpgtrdquo)

首先需要加載 url module它是用來協助我們解析 URL的模組接著使用 urlparse方法第一個傳入 url字串變數也就是 requrl另外第二個參數的用意是設為 ture則引進 querystring模組來協助處理預設是 false它影響到的是 urlDataquery設為 true會傳回物件不然就只是一般的字串urlparse會將字串內容整理成一個物件我們把它指定給 urlData

action變數作為記錄 pathname這是我們稍後要來判斷目前網頁的動作是什麼接著先將 html表頭資訊 (Header)準備好再來判斷路徑邏輯如

58 6 Express介紹

Nodejs中文電子書

果是 Signup這個動作就把 urlDataquery裡的資料指定給 user然後輸出userusername和 useremail把使用者從表單註冊的資料顯示於頁面中

最後進行程式測試啟動 nodejs主程式之後開啟瀏覽器就會看到表單填寫完畢按下送出就可以看到結果了

完整 nodejs程式碼如下

var http = require(rsquohttprsquo)

url = require(rsquourlrsquo)

fs = require(rdquofsrdquo)

server

server = httpcreateServer(function (reqres)

var urlData

encode = rdquoutf8rdquo

filePath = rdquoviewexpress_get_example_formhtmlrdquo

action

urlData = urlparse(requrltrue)

action = urlDatapathname

reswriteHead(200 rdquoContent-Typerdquordquotexthtml charset=utf-8rdquo)

if (action === rdquoSignuprdquo)

user = urlDataquery

resend(rdquolth1gtrdquo + userusername + rdquo 歡迎您的加入 lth1gtltpgt 我們已經將會員啟用信寄至rdquo + useremail + rdquoltpgtrdquo)

else

fsreadFile(filePath encode function(err file)

reswrite(file)

resend()

)

)

serverlisten(3000)

consolelog(rsquoServer 跑起來了現在時間是rsquo + new Date())

66 Express GET應用範例 59

Nodejs中文電子書

67 Express POST應用範例

一開始準備基本的 html表單傳送內容以 POST方式form的 action屬性設定為 POST其餘 html內容與前一個範例應用相同

ltDOCTYPE htmlgt

lthtmlgt

ltheadgt

ltmeta http-equiv=rdquoContent-Typerdquo content=rdquotexthtml charset=utf8rdquogt

lttitlegtNodejs 菜鳥筆記 (1)lttitlegt

ltlink rel=rdquostylesheetrdquo href=rdquocssstylecssrdquo type=rdquotextcssrdquo media=rdquoallrdquo gt

ltheadgt

ltbodygt

lth1gtNodejs 菜鳥筆記-註冊lth1gt

ltform id=rdquosignuprdquo method=rdquoPOSTrdquo action=rdquohttplocalhost3000Signuprdquogt

ltlabelgt 使用者名稱ltlabelgtltinput type=rdquotextrdquo id=rdquousernamerdquo name=rdquousernamerdquo gtltbrgt

ltlabelgt 電子郵件ltlabelgtltinput type=rdquotextrdquo id=rdquoemailrdquo name=rdquoemailrdquo gtltbrgt

ltinput type=rdquosubmitrdquo value=rdquo 註冊我的帳號rdquo gtltbrgt

ltformgt

ltbodygt

lthtmlgt

nodejs的程式處理邏輯與前面 GET範例類似部分程式碼如下

qs = require(rsquoquerystringrsquo)

if (action === rdquoSignuprdquo)

formData = rsquorsquo

reqon(rdquodatardquo function (data)

formData += data

)

reqon(rdquoendrdquo function ()

user = qsparse(formData)

resend(rdquolth1gtrdquo + userusername + rdquo 歡迎您的加入 lth1gtltpgt 我們已經將會員啟用信寄至rdquo + useremail + rdquoltpgtrdquo)

)

60 6 Express介紹

Nodejs中文電子書

主要加入了rsquoquerystringrsquo這個moduel方便我們等一下解析由表單 POST回來的資料另外加入一個 formData的變數用來搜集待等一下表單回傳的資料前面的 GET範例我們只從 req拿出 url的資料這次要在利用req身上的事件處理

JavaScript 在訂閱事件時使用 addEventListener而 nodejs 使用的則是on這邊加上了監聽 data的事件會在瀏覽器傳送資料到Web Server時被執行參數是它所接收到的資料型態是字串

接著再增加 end的事件當瀏覽器的請求事件結束時它就會動作

由於瀏覽器使用 POST在上傳資料時會將資料一塊塊地上傳因為我們在監聽 data事件時透過 formData變數將它累加起來lt不過由於我們

上傳的資料很少一次就結束不過如果日後需要傳的是資料比較大的檔

案這個累加動作就很重要

當資料傳完就進到 end 事件中會用到 qsparse 來解析 formDataformData的內容是字串內容是

username=wordsmithampemail=wordsmith40somewhere

而 qsparse可以幫我們把這個 querystring轉成物件的格式也就是

username=wordsmithampemail=wordsmith40somewhere

一旦轉成物件並指定給 user之後其他的事情就和 GET方法時操作的一樣寫 response的表頭將內容回傳並將 userusername和 useremail代入到內容中

修改完成後接著執行 nodejs程式啟動 web server開啟瀏覽器進入表單測試看看POST的方式能否順利運作

完整程式碼如下

var http = require(rsquohttprsquo)

url = require(rsquourlrsquo)

fs = require(rdquofsrdquo)

qs = require(rsquoquerystringrsquo)

server

server = httpcreateServer(function (reqres)

var urlData

67 Express POST應用範例 61

Nodejs中文電子書

encode = rdquoutf8rdquo

filePath = rdquoviewexpress_post_example_formhtmlrdquo

formData

action

urlData = urlparse(requrltrue)

action = urlDatapathname

reswriteHead(200 rdquoContent-Typerdquordquotexthtml charset=utf-8rdquo)

if (action === rdquoSignuprdquo)

formData = rsquorsquo

reqon(rdquodatardquo function (data)

formData += data

)

reqon(rdquoendrdquo function ()

user = qsparse(formData)

resend(rdquolth1gtrdquo + userusername + rdquo 歡迎您的加入 lth1gtltpgt 我們已經將會員啟用信寄至rdquo + useremail + rdquoltpgtrdquo)

)

else

fsreadFile(filePath encode function(err file)

reswrite(file)

resend()

)

)

serverlisten(3000)

consolelog(rsquoServer 跑起來了現在時間是rsquo + new Date())

62 6 Express介紹

Nodejs中文電子書

68 Express AJAX應用範例

在 Nodejs要使用 Ajax傳送資料並且與之互動在接受資料的部份沒有太大的差別client端不是用 GET就是用 POST來傳資料重點在處理完後用 JSON格式回傳當然 Ajax不見得只傳 JSON格式有時是回傳一段 HTML碼不過後者對伺服器來說基本上就和前兩篇沒有差別了所以我們還是以回傳 JSON做為這一回的主題

這一回其實大多數的工作都會落在前端 Ajax上面前端要負責發送與接收資料並在接收資料後撤掉原先發送資料的表單並將取得的資料

改成 HTML格式之後放上頁面

首先先準備 HTML靜態頁面資料

ltDOCTYPE htmlgt

lthtmlgt

ltheadgt

lttitlegtNodejs 菜鳥筆記 (3)lttitlegt

ltlink rel=rdquostylesheetrdquo href=rdquocssstylecssrdquo type=rdquotextcssrdquo media=rdquoallrdquo gt

ltscript src=rdquohttpsajaxgoogleapiscomajaxlibsjquery171jqueryminjsrdquogtltscriptgt

ltheadgt

ltbodygt

lth1gtNodejs 菜鳥筆記-註冊 (用 Ajax)lth1gt

ltform id=rdquosignuprdquo action=rdquoSignuprdquo method=rdquoPOSTrdquogt

ltlabelgt 使用者名稱ltlabelgtltinput type=rdquotextrdquo id=rdquousernamerdquo name=rdquousernamerdquo gtltbrgt

ltlabelgt 電子郵件ltlabelgtltinput type=rdquotextrdquo id=rdquoemailrdquo name=rdquoemailrdquo gtltbrgt

ltinput type=rdquosubmitrdquo value=rdquo 註冊我的帳號rdquo gtltbrgt

ltformgt

ltdiv id=rdquoresultrdquogt

ltdivgt

ltscript type=rdquotextjavascriptrdquogt

$(function()$(rsquosignup input[type=rdquosubmitrdquo]rsquo)click(function(e)

var user =

userusername = $(rdquousernamerdquo)val()useremail = $(rdquoemailrdquo)val()

$post(rdquoSignuprdquouserfunction(data)greet(data)

)

68 Express AJAX應用範例 63

Nodejs中文電子書

epreventDefault()

)

var greet = function(msg)

var resultNode = $(rsquoresultrsquo)greeting = $(rsquolth2gtHirsquo+msgusername+rsquo 你的會員 id 是rsquo+ msgid +rsquo 我們會將會員啟動信寄至rsquo+msgemail+rsquolth2gtrsquo)

$(rsquosignuprsquo)hide()resultNodehtml(rdquordquo)

resultNodeappend(greeting)

)

ltscriptgt

ltbodygt

lthtmlgt

HTML頁面上準備了一個表單用來傳送註冊資料接著直接引用了Google CDN來載入 jQuery用來幫我們處理 Ajax的工作這次要傳送和接收的工作很大的變動都在 HTML頁面上的 JavaScript當中我們要做的事有 (相關 jQuery處理這邊不多做贅述指提起主要功能解說)

bull 用 jQUery取得 submit按鈕綁定它的 click動作

bull 取得表單 username和 email的值存放在 user這個物件中

bull 用 jQuery的 $post方法將 user的資料傳到 Server

bull 一旦成功取得資料後透過 greet這個 function組成回報給 user的訊息

bull 清空原本給使用者填資料的表單

bull 將 Server回傳的 usernameemail和 id這 3個資料組成回應的訊息

bull 將訊息放到原本表單的位置

經過以上的處理後一個 Ajax的表單的基本功能已經完成

接著進行 nodejs主要程式的編輯部分程式碼如下

var fs = require(rdquofsrdquo)

64 6 Express介紹

Nodejs中文電子書

qs = require(rsquoquerystringrsquo)

if (action === rdquoSignuprdquo)

formData = rsquorsquo

reqon(rdquodatardquo function (data)

formData += data

)

reqon(rdquoendrdquo function ()

var msg

user = qsparse(formData)

userid = rdquo123456rdquo

msg = JSONstringify(user)

reswriteHead(200 rdquoContent-Typerdquordquoapplicationjson charset=utf-8rdquordquoContent-Lengthrdquomsglength)

resend(msg)

)

這裡的程式和前面 POST範例基本上大同小異差別在

bull 幫 user的資料加上 id隨意存放一些文字進去讓 Server回傳的資料多於 Client端傳上來的不然會覺得 Server都沒做事

bull 增加了 msg 這個變數存放將 user 物件 JSON 文字化的結果JSONstringify這個轉換函式是 V8引擎所提供的如果你好奇的話

bull 大重點來了我們要告訴 Client端這次回傳的資料格式是 JSON所在 Content-type和 Content-Length要提供給 Client

Server很輕鬆就完成任務了最後進行程式測試啟動 nodejs主程式之後開啟瀏覽器就會看到表單填寫完畢按下送出就可以看到結果了

最後 nodejs本篇範例程式碼如下

var http = require(rsquohttprsquo)

url = require(rsquourlrsquo)

fs = require(rdquofsrdquo)

qs = require(rsquoquerystringrsquo)

server

68 Express AJAX應用範例 65

Nodejs中文電子書

server = httpcreateServer(function (reqres)

var urlData

encode = rdquoutf8rdquo

filePath = rdquoviewexpress_ajax_example_formhtmlrdquo

formData

action

urlData = urlparse(requrltrue)

action = urlDatapathname

if (action === rdquoSignuprdquo)

formData = rsquorsquo

reqon(rdquodatardquo function (data)

formData += data

)

reqon(rdquoendrdquo function ()

var msg

user = qsparse(formData)

userid = rdquo123456rdquo

msg = JSONstringify(user)

reswriteHead(200 rdquoContent-TyperdquordquoapplicationjsonrdquordquoContent-Lengthrdquomsglength)

resend(msg)

)

else

fsreadFile(filePath encode function(err file)

reswriteHead(200 rdquoContent-Typerdquordquotexthtml charset=utf-8rdquo)

reswrite(file)

resend()

)

)

serverlisten(3000)

66 6 Express介紹

Nodejs中文電子書

consolelog(rsquoServer 跑起來了現在時間是rsquo + new Date())

69 原始資料提供

bull [NodeJS初學者筆記 (1)-用 GET傳送資料] (httpithelpithomecomtwquestion10087402)

bull [NodeJS初學者筆記 (2)-用 POST傳送資料] (httpithelpithomecomtwquestion10087489)

bull [NodeJS 初學者筆記 (3)-用 Ajax 傳送資料] (httpithelpithomecomtwquestion10087627)

69 原始資料提供 67

Nodejs中文電子書

68 6 Express介紹

7

CoffeeScript

bull Spine

bull Hem

bull Spineapp

Letrsquos Have a Cup of CoffeeScript

bull es5-shim ECMAScript 5 compatibility shims for legacy JavaScript engines

ESnext

node-iform - A middleware for node-validator httpsgithubcomguileennode-iform

69

Nodejs中文電子書

70 7 CoffeeScript

8

製作一個 Hubot的 Plurk Adapter

81 應用事項提醒

此應用範例以CoffeeScript編寫主要程式架構採用Github釋放的Hubot專案以下有幾個主要注意事項請讀者注意

bull Hubot 一開始的架構除了內建的兩個 Adapter 之外其餘都要以Nodejs的Module方式才能運作

bull Hubot的 binhubot裡面寫著 npm install所以不管你怎麼改原始碼也不能改變第一點的狀況

其實上述都在討論同一件事情NodeJS的模組而且模組不能設定相對路徑之類的來安裝一定要包裝成 npm相符和格式才有辦法正確執行

82 建立 Adapter

首先需要準備兩個檔案

bull packagejson

bull plurkcoffee

71

Nodejs中文電子書

有這兩個就足以變成 NodeJS的模組了在開始 Coding之前先來設定一下 packagejson相依性

rdquonamerdquo rdquohubot-plurkrdquo

rdquoversionrdquo rdquo011rdquo

rdquomainrdquo rdquoplurkrdquo

rdquodependenciesrdquo

rdquohubotrdquo rdquogt=205rdquo

rdquooauthrdquo rdquordquo

rdquocronrdquordquordquo

這邊會使用到NodeJS的 cron模組(Module)而登入 Plurk需要OAuth標準授權才行所以也需要加載 OAuth模組

注意Hubot在讀取非內建模組時會自動在前面加上 hubot-的前置

83 建立 Robot跟 API

本篇應用基本上程式碼大部分參考 Hubot - Twitter Adapter來製作主要差異只有在使用 OAuth這個模組Twitter有 Streaming可用而 Plurk則得用 Comet方式來達到即時讀取

plurkcoffee程式碼

Robot = require(rdquohubotrdquo)robot()

Adapter = require(rdquohubtordquo)adapter()

EventEmitter = require(rdquoeventsrdquo)EventEmitter

oauth = require(rdquooauthrdquo)

cronJob = require(rdquocronrdquo)CronJob

class Plurk exntends Adapter

class PlurkStreaming exnteds EventEmitter

72 8 製作一個 Hubot的 Plurk Adapter

Nodejs中文電子書

先弄個基本架構主要 Class皆參考 Twitter Adapter命名方式接著再將 Plurk這個 Class增加幾個Method基本上只要有 run send reply就夠了而 run用來做初始化的部分

class Plurk entends Adapter

send (plurk id strings⋯) -gt

reply (plurk id strings⋯) -gt

run -gt

看起來有點東西接著來處理主要的 Plurk API結合部分

class PlurkStreaming extends EventEmitter

consuctor (options) -gt

plurk (callback) -gt

觀察河道

getChannel -gt

取得 Comet 網址

reply (plurk id message) -gt

回噗

acceptFriends -gt

接受好友

get (path callback) -gt

GET 請求

post (path body callback)-gt

POST 請求(其實是裝飾)

request (method path body callback)-gt

主要的 OAuth 請求

comet (server callback)-gt

噗浪的 Comet 傳回是 JavaScript Callback 要另外處理後才會變成 JSON

然後把注意力集中到 constructor上先把建構子弄好

constructor (options) -gt

super()

if optionskey and optionssecret and optionstoken and optionstoken secret

key = optionskey

secret = optionssecret

token = optionstoken

token secret = optionstoken secret

83 建立 Robot跟 API 73

Nodejs中文電子書

建立 OAuth 連接

consumer = new oauthOAuth(

rdquohttpwwwplurkcomOAuthrequest tokenrdquo

rdquohttpwwwplurkcomOAuthaccess tokenrdquo

key

secret

rdquo10rdquo

rdquohttpwwwplurkcomOAuthauthorizerdquo

rdquoHMAC-SHA1rdquo

)

domain = rdquowwwplurkcomrdquo

初始化取得 Comet 網址

do getChannel

else

throw new Error(rdquo 參數不足需要 Key Secret Token Token Secretrdquo)

接著來處理 request這個 method

request (method path body callback) -gt

記錄一下這次的 Request

consolelog(rdquohttpdomainpathrdquo)

Callback 這邊先不丟進去要用另一種方式處理

request = consumerget(rdquohttpdomainpathrdquo token token secret null)

requeston rdquoresponserdquo (res) -gt

reson rdquodatardquo (chunk) -gt

parseResponse(chunk+rsquorsquo callback)

reson rdquoendrdquo (data) -gt

consolelog rdquoEnd Request pathrdquo

reson rdquoerrorrdquo (data) -gt

consolelog rdquoError rdquo + data

requestend()

處理資料

parseResponse = (data callback) -gt

if datalength gt 0

用 TryCatch 避免處理 JSON 出錯導致整個中斷

try

callback null JSONparse(data)

catch err

74 8 製作一個 Hubot的 Plurk Adapter

Nodejs中文電子書

consolelog(rdquoError Parse JSONrdquo + data err)

繼續執行

callback null data ||

大致上就是這樣上面程式的架構已經將整個 Hubot Plurk Adapter完成因為在測試時竟然因為噗浪 Lag而沒讀到完整的 Comet資料然後造成程式異常為了避免這個問題發生因此需要加上為了完美呈現需要再

加上 Comet的處理所以要使用到 EventEmitter的功能

comet (server callback) -gt

在 Callback 裡面會找不到自身所以設定區域變數

self =

記錄一下這次的 Request

consolelog(rdquo[Comet] serverrdquo)

Callback 這邊先不丟進去要用另一種方式處理

request = consumerget(rdquohttpdomainpathrdquo token token secret null)

requeston rdquoresponserdquo (res) -gt

reson rdquodatardquo (chunk) -gt

parseResponse(chunk+rsquorsquo callback)

reson rdquoendrdquo (data) -gt

consolelog rdquoEnd Request pathrdquo

請求結束發出事件通知可以進行下一次請求

selfemit rdquonextPlurkrdquo

reson rdquoerrorrdquo (data) -gt

consolelog rdquoError rdquo + data

requestend()

處理資料

parseResponse = (data callback) -gt

if datalength gt 0

用 trycatch 避免失敗中斷

try

去掉 JavaScript 的 Callback

data = datamatch(CometChannelscriptCallback((+))s)

jsonData = rdquordquo

if data

83 建立 Robot跟 API 75

Nodejs中文電子書

jsonData = JSONparse(data[1])

else

如果沒有任何 Match 嘗試直接 parse

jsonData = JSONparse(data)

catch err

consolelog(rdquo[Comet] Errorrdquo data err)

用 TryCatch 避免處理 JSON 出錯導致整個中斷

try

只傳入 json 的 data 部分

callback null jsonDatadata

catch err

consolelog(rdquo[Comet]Error Parse JSONrdquo + data err)

繼續執行

callback null data ||

後面的 get跟 post就簡單多了

get (path callback) -gt

request(rdquoGETrdquo path null callback)

post (path body callback) -gt

request(rdquoPOSTrdquo path body callback)

接著處理取的 Comet網址的 getChannel

getChannel -gt

self =

get rdquoAPPRealtimegetUserChannelrdquo (error data) -gt

if error

檢查是否有 comet server

if datacomet server

selfchannel = datacomet server

如果沒有 Channel Ready 就嘗試連接會失敗

selfemit(rsquochannel readyrsquo)

那麼先來處理 Plurk Adaper好處理的部份

send (plruk id strings⋯)-gt

跟 Reply 一樣直接交給 reply 做

reply plurk id strings⋯

76 8 製作一個 Hubot的 Plurk Adapter

Nodejs中文電子書

reply (plurk id strings⋯) -gt

stringsforEach (message) =gt

botreply(plruk id message)

接著把 run處理好就可以上線運作摟

run -gt

self =

options =

key processenvHUBOT PLURK KEY

secret processenvHUBOT PLURK SECRET

token processenvHUBOT PLURK TOKEN

token secret processenvHUBOT PLURK TOKEN SECRET

創建剛剛的 API

bot = new PlurkStreaming(options)

依照 Twitter 的 new RobotTextMessage 會沒有反應所以參考 hubot-minecraft 的方式

r = robotconstructor

處理噗浪河道訊息

doPlurk = (data)-gt

檢查是否為回噗

if dataresponse

datacontent raw = dataresponsecontent raw

datauser id = dataresponseuser id

確定有噗浪 ID 跟訊息

if dataplurk id and datacontent raw

selfreceive new rTextMessage(dataplurk id datacontent raw)

取得 Comet Server 完成開始第一次 Comet 連接

boton rdquochannel readyrdquo () -gt

botplurk selfdoPlurk

上一次 Comet 完成繼續 Polling

boton rdquonextPlurkrdquo ()-gt

botplurk selfdoPlurk

定時接受好友邀請

do botacceptFriends

bot = bot

83 建立 Robot跟 API 77

Nodejs中文電子書

終於完成 AdapterHubot專案裡面的 scripts資料夾內是互動部分不需要像 Adapter如此大費周章處理只新增檔案並且設計好對白之後就會回噗了我開發用的機器人在此大家可以去跟他玩玩[httpplurkcomelct9620 bot](httpplurkcomelct9620 bot)

84 原始資料提供

bull [製 作 一 個 Hubot 的 噗 浪 Adapter](http revo-skillfrosttw blog20120318create-a-hubot-plurk-adapter)

78 8 製作一個 Hubot的 Plurk Adapter

9

Nodejs好用工具介紹

91 logio

Real-time log monitoring in your browser

httplogioorg

nodejs (gt=0412 lt=0611)

npm config set unsafe-perm true

npm install -g --prefix=usrlocal logio

logio server start

httplocalhost8998

79

Nodejs中文電子書

80 9 Nodejs好用工具介紹

10

精選文章

101 Express

bull 使用 NodeJS + Express從 GETPOST Request取值 [邀稿中]

bull link2 [邀稿中]

bull link3 [邀稿中]

81

Nodejs中文電子書

82 10 精選文章

11

參考資源

111 Nodejs書籍

bull Programming Nodejs April 2012 (est) Travis Swicegood Pragmatic Bookshelf

bull e Little Book on CoffeeScript January 2012 Alex MacCaw OrsquoReilly Media

bull Node for Front-End Developers January 2012 Garann Means OrsquoReilly Media

bull Building Hypermedia APIs with HTML5 and Node November 2011 MikeAmundsen OrsquoReilly Media

bull Node Web Development August 2011 David Herron PacktPub

bull CoffeeScript Accelerated JavaScript Development July 2011 Trevor BurnhamPragmatic Bookshelf

bull Getting Started with GEO CouchDB and Nodejs July 2011 Mick omp-son OrsquoReilly Media

bull What Is Node July 2011 Brett McLaughlin OrsquoReilly Media

bull Node Up and Running May 2011 Tom Hughes-Croucher Mike WilsonOrsquoReilly Media

83

Nodejs中文電子書

bull Hands-on Nodejs e Nodejs introduction and API reference May 2011 Pe-dro Teixeira LeanPub

bull e Node Beginner Book Manuel Kiessling

bull Mastering Nodejs visionmedia

112 Nodejs影音教學

bull e Node Sessions e Best of OSCON 2011 August 2011 OrsquoReilly Media(Video)

bull Tom Hughes-Croucher on Node March 2011 OrsquoReilly Media (Video)

113 Nodejs教學網站

bull Node Tuts

114 Nodejs課程

bull Introduction To Nodejs Van Nguyen CodeLesson

84 11 參考資源

Page 30: Nodejs Wiki

Nodejs中文電子書

就可以把巢狀的 callback攤平成循序的 function陣列(就是傳給 serial函數的參數)

測試中的dclientjs 是一個簡單的 dnode 測試程式放在跟 testSerialjs同一個目錄

var dnode = require(lsquodnodersquo)

dnodeconnect(8000 lsquolocalhostrsquo function(remote)

remoterestart(function(str) consolelog(str) processexit()

)

)

執行測試程式後出現結果

[ attened by searial] var dnode = require(lsquodnodersquo)

dnodeconnect(8000 lsquolocalhostrsquo function(remote)

remoterestart(function(str) consolelog(str) processexit()

)

)

[nested callbacks] var dnode = require(lsquodnodersquo)

dnodeconnect(8000 lsquolocalhostrsquo function(remote)

remoterestart(function(str) consolelog(str) processexit()

)

)

對照起來看兩種寫法的結果其實是一樣的但是利用 serialjs巢狀的 callback結構就會消失

不過這樣也只限於順序單純的狀況如果函數執行的順序比較複雜

(不只是一直線)還是需要用功能更完整的流程控制模組比較好例如

httpsgithubcomcaolanasync

24 2 JavaScript與 NodeJS

3

Nodejs安裝與設定

本篇將講解如何在各個不同 OS建立 NodeJS環境目前 NodeJS 048版本環境架設方式需依賴 Linux指令才可編譯完成當然在不同作業系統中也已經有 NodeJS package可以直接使用指令快速架設以下各不同作業系統解說如何安裝 NodeJS

31 Ubuntu Linux

更新推薦使用 nvm

git clone gitgithubcomcreationixnvmgit ~nvm

echo rdquo ~nvmnvmshrdquo gtgt ~bashrc

nvm install v0614

nvm alias default v0614

以 上 可 參 考 http dreamerslabcom blog tw how-to-setup-a-node-js-development-environment-on-ubuntu-11-04

使用 APT套件管理工具是常見的方法以下是使用社群提供的 PPA安裝方式

sudo apt-get install python-software-properties

sudo add-apt-repository ppachris-leanodejs-devel

25

Nodejs中文電子書

sudo apt-get update

sudo apt-get install nodejs

32 Other Linux

Linux很適合作為 NodeJS的伺服器作業系統及開發環境安裝前請先確認以下套件已正確安裝

bull curl (wget)用來下載檔案的工具

bull git先進的版本控制工具

bull g++ GNU C++軟體編譯工具

bull make GNU軟體專案建置工具

安裝指令如下如設有權限問題請在指令前面加上 sudo

git clone httpsgithubcomjoyentnodegit

cd node

git checkout v067

configure

make

sudo make install

接著測試 nodeJS是否正常執行

node --version

出現版本訊息即表示安裝成功

33 Windows

nodeJS 在 v060 版本之後開始正式支援 windows native直接使用nodeexe 就可以執行程式支援性完全與 linux 相同更棒的部份就是不需經過編譯經過下載之後簡單設定完成立即開發 node程式

26 3 Nodejs安裝與設定

Nodejs中文電子書

下載 nodejs安裝檔案 lthttpnodejsorgdownloadgt

如此完成 windows native nodeexe安裝接著可以進入 command line執行測試在 command line輸入rdquonoderdquo會出現 nodejs版本訊息畫面表示安裝完成

33 Windows 27

Nodejs中文電子書

28 3 Nodejs安裝與設定

4

Nodejs基礎

前篇文章已經由介紹安裝至設定都有完整介紹nodeJS 內部除了javascript常用的函式 (function)物件 (object)之外也有許多不同的自訂物件nodeJS預設建立這些物件為核心物件是為了要讓開發流程更為這些資料在官方文件已經具有許多具體說明接下來將會介紹在開發 nodeJS程式時常見的物件特性與使用方法

41 nodejs http伺服器建立

在 lsquonodejs官方網站 lthttpnodejsorggtlsquo裡面有舉一個最簡單的 HTTP伺服器建立一開始初步就是建立一個伺服器平台讓 nodejs可以與瀏覽器互相行為每種語言一開始的程式建立都是以 Hello world開始最初也從 Hello world帶各位進入 nodejs的世界

輸入以下程式碼儲存檔案為 node basic http hello worldjs

var server

ip = rdquo127001rdquo

port = 1337

http = require(rsquohttprsquo)

server = httpcreateServer(function (req res)

29

Nodejs中文電子書

reswriteHead(200 rsquoContent-Typersquo rsquotextplainrsquo)

resend(rsquoHello Worldnrsquo)

)

serverlisten(port ip)

consolelog(rdquoServer running at httprdquo + ip + rdquordquo + port)

程式碼解講一開始需要有幾個基本的變數

bull ip 機器本身的 ip位置因為使用本地端因此設定為 127001

bull port 需要開通的阜號通常設定為 http port 80因範例不希望與基本 port相衝隨意設定為 1337

在 nodejs 的程式中有許多預設的模組可以使用因此需要使用require方法將模組引入在這邊我們需要使用 http這個模組因此將 http載入Http模組裡面內建有許多方法可以使用這邊採用 createServer創建一個基本的 http伺服器再將 http伺服器給予一個 server變數裡面的回呼函式 (call back function)可以載入 http伺服器的資料與回應方法 (requestresponse)在程式裡面就可以看到我們直接回應給瀏覽器端所需的 Header回應內容

reswriteHead(200 rsquoContent-Typersquo rsquotextplainrsquo)

resend(rsquoHello Worldnrsquo)

Http伺服器需要設定 port ip在最後需要設定 Http監聽需要使用到listen事件監聽所有 Http伺服器行為

httplisten(port ip)

所有事情都完成之後需要確認伺服器正確執行因此使用 console在javascript裡就有這個原生物件console所印出的資料都會顯示於 nodejs伺服器頁面這邊印出的資料並不會傳送到使用者頁面上之後許多除壞

(debug)都會用到 console物件

consolelog(rdquoServer running at httprdquo + ip + rdquordquo + port)

30 4 Nodejs基礎

Nodejs中文電子書

42 nodejs http路徑建立

前面已經介紹如何建立一個簡單的 http伺服器接下來本章節將會介紹如何處理伺服器路徑 (rout)問題在 http的協定下所有從瀏覽器發出的要求 (request)都需要經過處理路徑上的建立也是如此

路徑就是指伺服器 ip位置或者是網域名稱之後對於伺服器給予的要求修改剛才的 hello world檔案修改如下

server = httpcreateServer(function (req res)

consolelog(requrl)

reswriteHead(200 rsquoContent-Typersquo rsquotextplainrsquo)

resend(rsquohello worldnrsquo)

)

重新啟動 nodejs程式後在瀏覽器端測試一下路徑行為結果如下圖

當在瀏覽器輸入 http1270011337test 在伺服器端會收到兩個要求一個是我們輸入的test要求另外一個則是faviconicotest的路徑要求http伺服器本身需要經過程式設定才有辦法回應給瀏覽器端所需要的回應在伺服器中所有的路徑要求都是需要被解析才有辦法取得資料從

42 nodejs http路徑建立 31

Nodejs中文電子書

上面解說可以了解到在 nodejs當中所有的路徑都需要經過設定未經過設定的路由會讓瀏覽器無法取得任何資料導致錯誤頁面的發生底下將會解

說如何設定路由同時避免發生錯誤情形先前 nodejs程式需要增加一些修改才能讓使用者透過瀏覽器在不同路徑時有不同的結果根據剛才

的程式做如下的修改

var server

ip = rdquo127001rdquo

port = 1337

http = require(rsquohttprsquo)

url = require(rsquourlrsquo)

path

server = httpcreateServer(function (req res)

path = urlparse(requrl)

reswriteHead(200 rsquoContent-Typersquo rsquotextplainrsquo)

switch (pathpathname)

case rdquoindexrdquo

resend(rsquoI am indexnrsquo)

break

case rdquotestrdquo

resend(rsquothis is test pagenrsquo)

break

default

resend(rsquodefault pagenrsquo)

break

)

serverlisten(port ip)

consolelog(rdquoServer running at httprdquo + ip + rdquordquo + port)

程式做了片段的修改首先載入 url模組另外增加一個 path變數url模組就跟如同他的命名一般專門處理 url字串處理裡面提供了許多方法來解決路徑上的問題因為從瀏覽器發出的要求路徑可能會帶有多種需求

或者 GET參數組合等因此我們需要將路徑單純化取用路徑部分的資料即可例如使用者可能會送出 http1270011337testsend=1如果直接

32 4 Nodejs基礎

Nodejs中文電子書

信任 requrl就會收到結果為testsend=1所以需要透過 url模組的方法將路徑資料過濾

在這邊使用 urlparse的方法裡面帶入網址格式資料會回傳路徑資料為了後需方便使用將回傳的資料設定到 path變數當中在回傳的路徑資料裡面包含資訊如下圖

這邊只需要使用單純的路徑要求直接取用 pathpathname就可以達到我們的目的

最後要做路徑的判別在不同的路徑可以指定不同的輸出在範例中

有三個可能結果第一個從瀏覽器輸入index就會顯示 index結果test 就會呈現出 test頁面最後如果都不符合預期的輸入會直接顯示 default的頁面最後的預防可以讓瀏覽器不會出現非預期結果讓程式的可靠性提昇

底下為測試結果

42 nodejs http路徑建立 33

Nodejs中文電子書

43 nodejs檔案讀取

前面已經介紹如何使用路由(rount)做出不同的回應實際應用只有在瀏覽器只有輸出幾個文字資料總是不夠的在本章節中將介紹如何使

用檔案讀取輸出檔案資料讓使用者在前端瀏覽器也可以讀取到完整的

html css javascript檔案輸出

檔案管理最重要的部分就是 lsquoFile system lthttpnodejsorgdocslatestapifshtmlgtlsquo這個模組此模組可以針對檔案做管理監控讀取等行為裡面有許多預設的方法底下是檔案輸出的基本範例底下會有兩個檔案

第一個是靜態 html檔案

ltDOCTYPE htmlgt

lthtml xmlns=rdquohttpwwww3org1999xhtmlrdquo xmllang=rdquozh-CNrdquo lang=rdquozh-CNrdquogt

ltheadgt

ltmeta http-equiv=rdquoContent-Typerdquo content=rdquotexthtml charset=UTF-7rdquo gt

lttitlegtnodejs index html filelttitlegt

ltheadgt

ltbodygt

34 4 Nodejs基礎

Nodejs中文電子書

lth1gtnodejs index html filelth1gt

ltbodygt

lthtmlgt

另一個為 nodejs程式

var fs = require(rdquofsrdquo)

filename = rdquostaticindexhtmlrdquo

encode = rdquoutf8rdquo

fsreadFile(filename encode function(err file)

consolelog(file)

)

一開始直接載入 le system模組 載入名稱為 fs讀取檔案主要使

用的方法為 readFile裡面以三個參數路徑 ( le path) 編碼方式 (encoding)

回應函式 (callback)路徑必須要設定為靜態 html所在位置才能指定到正確的檔案靜態檔案的編碼方式也必須正確這邊使用靜態檔案的編

碼為 utf8如果編碼設定錯誤nodejs讀取出來檔案結果會使用 byte raw格式輸出如果錯誤編碼格式會導致輸出資料為 byte raw

回應函式中裡面會使用兩個變數error為錯誤資訊如果讀取的檔案不存在或者發生錯誤error數值會是 true如果成功讀取資料 error將會是 falsecontent則是檔案內容資料讀取後將會把資料全數丟到 content這個變數當中

最後程式的輸出結果畫面如下

43 nodejs檔案讀取 35

Nodejs中文電子書

44 nodejs http靜態檔案輸出

前面已經了解如何讀取本地端檔案接下來將配合 http伺服器路由讓每個路由都能夠輸出相對應的靜態 html檔案首先新增加幾個靜態 html檔案

ltDOCTYPE htmlgt

lthtml xmlns=rdquohttpwwww3org1999xhtmlrdquo xmllang=rdquozh-CNrdquo lang=rdquozh-CNrdquogt

ltheadgt

ltmeta http-equiv=rdquoContent-Typerdquo content=rdquotexthtml charset=UTF-7rdquo gt

lttitlegtnodejs index html filelttitlegt

ltheadgt

ltbodygt

lth1gtnodejs index html filelth1gt

ltbodygt

lthtmlgt

ltDOCTYPE htmlgt

lthtml xmlns=rdquohttpwwww3org1999xhtmlrdquo xmllang=rdquozh-CNrdquo lang=rdquozh-CNrdquogt

ltheadgt

ltmeta http-equiv=rdquoContent-Typerdquo content=rdquotexthtml charset=UTF-7rdquo gt

lttitlegtnodejs test html filelttitlegt

ltheadgt

ltbodygt

lth1gtnodejs test html filelth1gt

ltbodygt

lthtmlgt

ltDOCTYPE htmlgt

lthtml xmlns=rdquohttpwwww3org1999xhtmlrdquo xmllang=rdquozh-CNrdquo lang=rdquozh-CNrdquogt

ltheadgt

ltmeta http-equiv=rdquoContent-Typerdquo content=rdquotexthtml charset=UTF-7rdquo gt

lttitlegtnodejs static html filelttitlegt

ltheadgt

ltbodygt

lth1gtnodejs static html filelth1gt

ltbodygt

lthtmlgt

36 4 Nodejs基礎

Nodejs中文電子書

準備一個包含基本路由功能的 http伺服器

var server

ip = rdquo127001rdquo

port = 1337

http = require(rsquohttprsquo)

url = require(rsquourlrsquo)

server = httpcreateServer(function (req res)

var path = urlparse(requrl)

)

serverlisten(port ip)

consolelog(rdquoServer running at httprdquo + ip + rdquordquo + port)

加入 le system 模組使用 readFile 的功能將這一段程式放置於

createServer的回應函式中

fsreadFile(filePath encode function(err file)

)

readFile的回應函式裡面加入頁面輸出讓瀏覽器可以正確讀到檔案在這邊我們設定讀取的檔案為 html 靜態檔案所以 Content-type 設定為texthtml讀取到檔案的內容將會正確輸出成 html靜態檔案

fsreadFile(filePath encode function(err file)

reswriteHead(200 rsquoContent-Typersquo rsquotexthtmlrsquo)

reswrite(file)

resend()

)

到這邊為止基本的程式內容都已經完成剩下一些細節的調整首先

路徑上必須做調整目前的靜態檔案全部都放置於 static資料夾底下設定一個變數來記住資料夾位置

接著將瀏覽器發出要求路徑與資料夾組合讀取正確 html靜態檔案使用者有可能會輸入錯誤路徑所以在讀取檔案的時候要加入錯誤處理

同時回應 404伺服器無法正確回應的 http header格式

加入這些細節的修改一個基本的 http 靜態 html輸出伺服器就完成了完整程式碼如下

44 nodejs http靜態檔案輸出 37

Nodejs中文電子書

var server

ip = rdquo127001rdquo

port = 1337

http = require(rsquohttprsquo)

fs = require(rdquofsrdquo)

folderPath = rdquostaticrdquo

url = require(rsquourlrsquo)

path

filePath

encode = rdquoutf8rdquo

server = httpcreateServer(function (req res)

path = urlparse(requrl)

filePath = folderPath + pathpathname

fsreadFile(filePath encode function(err file)

if (err)

reswriteHead(404 rsquoContent-Typersquo rsquotextplainrsquo)

resend()

return

reswriteHead(200 rsquoContent-Typersquo rsquotextapplicationrsquo)

reswrite(file)

resend()

)

)

serverlisten(port ip)

consolelog(rdquoServer running at httprdquo + ip + rdquordquo + port)

45 nodejs http GET資料擷取

http伺服器中除了路由之外另一個最常使用的方法就是擷取 GET資料本單元將會介紹如何透過基本 http伺服器擷取瀏覽器傳來的要求擷取 GET資料

在 http協定中GET參數都是藉由 URL從瀏覽器發出要求送至伺服器

38 4 Nodejs基礎

Nodejs中文電子書

端基本的傳送網址格式可能如下

http127001testsend=1amptest=2

上面這段網址裡面的 GET參數就是 send而這個變數的數值就為 1如果想要在 http伺服器取得 GET資料需要在瀏覽器給予的要求 (request)做處理

首先需要載入 query string這個模組這個模組主要是用來將字串資料

過濾後轉換成javascript物件

qs = require(rsquoquerystringrsquo)

接著在第一階段利用 url模組過濾瀏覽器發出的 URL資料後將回應的物件裡面的 query這個變數是一個字串值資料過濾後如下

send=1amptest=2

透過 query string使用 parse這個方法將資料轉換成 javascript物件就表示 GET的資料已經被伺服器端正式擷取下來

path = urlparse(requrl)

parameter = qsparse(pathquery)

整個 nodejs http GET參數完整擷取程式碼如下

var server

ip = rdquo127001rdquo

port = 1337

http = require(rsquohttprsquo)

qs = require(rsquoquerystringrsquo)

url = require(rsquourlrsquo)

server = httpcreateServer(function (req res)

var path = urlparse(requrl)

parameter = qsparse(pathquery)

consoledir(parameter)

reswriteHead(200 rsquoContent-Typersquo rsquotextplainrsquo)

reswrite(rsquoBrowser test GET parameternrsquo)

resend()

)

45 nodejs http GET資料擷取 39

Nodejs中文電子書

serverlisten(port ip)

consolelog(rdquoServer running at httprdquo + ip + rdquordquo + port)

程式運作之後由瀏覽器輸入要求網址之後nodejs伺服器端回應資料為

Server running at http1270011337

send rsquo1rsquo test rsquo1rsquo

46 本章結語

前面所解說的部份一大部分主要是處理 http伺服器基本問題雖然在某些部分有牽扯到 http 伺服器基本運作原理主要還是希望可以藉由這些基本範例練習 nodejs練習回應函式與語法串接的特點習慣編寫javascript風格程式當然直接這樣開發 nodejs是非常辛苦的接下來在模組實戰開發的部份將會介紹特定的模組一步一步帶領各位從無到有進行

nodejs應用程式開發

40 4 Nodejs基礎

5

NPM套件管理工具

npm全名為 Node Package Manager是 Nodejs的套件(package)管理工具類似 Perl的 ppm或 PHP的 PEAR等安裝 npm後使用npm install

module name指令即可安裝新套件維護管理套件的工作會更加輕鬆

npm可以讓Nodejs的開發者直接利用擴充線上的套件庫(packagesregistry)加速軟體專案的開發npm提供很友善的搜尋功能可以快速找到安裝需要的套件當這些套件發行新版本時npm也可以協助開發者自動更新這些套件

npm不僅可用於安裝新的套件它也支援搜尋列出已安裝模組及更新的功能

51 安裝 NPM

Nodejs在 063版本開始內建 npm讀者安裝的版本若是此版本或更新的版本就可以略過以下安裝說明

若要檢查 npm是否正確安裝可以使用以下的指令

npm -v

41

Nodejs中文電子書

執行結果說明

若 npm正確安裝執行 npm -v將會看到類似 110-2的版本訊息

若讀者安裝的 Nodejs版本比較舊或是有興趣嘗試自己動手安裝 npm工具則可以參考以下的說明

安裝於Windows系統

Nodejs for Windows 於 062 版開始內建 npm使用 nodejsorg 官方提供的安裝程式不需要進一步的設定就可以立即使用 npm指令對於Windows的開發者來說大幅降低環境設定的問題與門檻

除了使用 Nodejs內建的 npm讀者也可以從 npm官方提供的以下網址

httpnpmjsorgdist

這是由 npm提供的 Fancy Windows Install版本請下載壓縮檔(例如npm-110-3zip)並將壓縮檔內容解壓縮至 Nodejs的安裝路徑(例如CProgram Filesnodejs)

解壓縮後在 Nodejs的安裝路徑下應該有以下的檔案及資料夾

bull npmcmd(檔案)

bull node modules(資料夾)

安裝於 Linux系統

Ubuntu Linux的使用者可以加入 NPM Unoffcial PPA這個 repository即可使用 apt-get完成 npm安裝

42 5 NPM套件管理工具

Nodejs中文電子書

Ubuntu Linux使用 apt-get安裝 npm

sudo apt-get install python-software-properties

sudo add-apt-repository ppagias-kay-leenpm

sudo apt-get update

sudo apt-get install npm

npm官方提供的安裝程式 installsh可以適用於大多數的 Linux系統使用這個安裝程式請先確認

1 系統已安裝 curl工具(請使用 curl --version查看版本訊息)

2 已安裝 Nodejs並且 PATH正確設置

3 Nodejs的版本必須大於 04x

以下為 npm提供的安裝指令

curl httpnpmjsorginstallsh | sh

安裝成功會看到如下訊息

installsh安裝成功的訊息

npm10105 homeUSERNAMElocalnodelibnode_modulesnpm

It worked

安裝於Mac OS X

建議採用與 Nodejs相同的方式進行 npm的安裝例如使用MacPorts安裝 Nodejs就同樣使用MacPorts安裝 npm這樣對日後的維護才會更方便容易

使用 MacPorts安裝 npm是本書比較建議的方式它可以讓 npm的安裝移除及更新工作自動化將會幫助開發者節省寶貴時間

51 安裝 NPM 43

Nodejs中文電子書

安裝MacPorts的提示

在MacPorts網站可以取得 OS X系統版本對應的安裝程式(例如 106或 107)httpwwwmacportsorg安裝過程會詢問系統管理者密碼使用預設的選項完成安裝即可安

裝MacPorts之後在終端機執行 port -v將會看到MacPorts的版本訊息

安裝 npm之前先更新MacPorts的套件清單以確保安裝的 npm是最新版本

sudo port -d selfupdate

接著安裝 npm

sudo port install npm

若讀者的 Nodejs並非使用MacPorts安裝則不建議使用MacPorts安裝npm因為 MacPorts會自動檢查並安裝相依套件而 npm相依 nodejs所以MacPorts也會一併將 nodejs套件安裝造成先前讀者使用其它方式安裝的 nodejs被覆蓋

讀者可以先使用 MacPorts安裝 curl(sudo port install curl)

再參考 Linux的 installsh安裝方式即可使用 npm官方提供的安裝程式

NPM安裝後測試

npm是指令列工具(command-line tool)使用時請先打開系統的文字終端機工具

測試 npm安裝與設定是否正確請輸入指令如下

npm -v

或是

npm --version

如果 npm已經正確安裝設定就會顯示版本訊息

44 5 NPM套件管理工具

Nodejs中文電子書

執行結果(範例)

110-2

52 使用 NPM安裝套件

npm目前擁有超過 6000種套件(packages)可以在 npm registry使用關鍵字搜尋套件

httpsearchnpmjsorg

舉例來說在關鍵字欄位輸入「coffee-script」下方的清單就會自動列出包含 coffee-script關鍵字的套件

接著我們回到終端機模式的操作npm的指令工具本身就可以完成套

件搜尋的任務

例如以下的指令同樣可以找出 coffee-script相關套件

npm search coffee-script

以下是搜尋結果的參考畫面

52 使用 NPM安裝套件 45

Nodejs中文電子書

找到需要的套件後(例如 express)即可使用以下指令安裝

npm install coffee-script

值得注意的一點是使用 npm install會將指定的套件安裝在工

作目錄(Working Directory)的 node modules資料夾下

以Windows為例如果執行 npm install的目錄位於

Cproject1

那麼 npm將會自動建立一個 node modules的子目錄(如果不存在)

Cproject1node modules

並且將下載的套件放置於這個子目錄例如

Cproject1node modulescoffee-script

這個設計讓專案可以個別管理相依的套件並且可以在專案佈署或發

行時將這些套件(位於 node modules)一併打包方便其它專案的使用者不必再重新下載套件

這個 npm install的預設安裝模式為 local(本地)只會變更當前專案的資料夾不會影響系統

另一種安裝模式稱為 global(全域)這種模式會將套件安裝到系統資

料夾也就是 npm安裝路徑的 node modules資料夾例如

CProgram Filesnodejsnode modules

46 5 NPM套件管理工具

Nodejs中文電子書

是否要使用全域安裝可以依照套件是否提供新指令來判斷舉例來說express 套件提供 express 這個指令而 coffee-script 則提供 coffee

指令

在 local 安 裝 模 式 中這 些 指 令 的 程 式 檔 案會 被 安 裝 到node modules的 bin這個隱藏資料夾下除非將bin的路徑加入 PATH環境變數否則要執行這些指令將會相當不便

為了方便指令的執行我們可以在 npm install 加上 -g 或 --

global參數啟用 global安裝模式例如

npm install -g coffee-script

npm install -g express

使用 global安裝模式需要注意執行權限的問題若權限不足可能會出現類似以下的錯誤訊息

npm ERR Error EACCES permission denied rsquorsquo

npm ERR

npm ERR Please try running this command again as rootAdministrator

要獲得足夠得執行權限請參考以下說明

bull Windows 7 或 2008 以上在「命令提示字元」的捷徑按右鍵選擇「以系統管理員身分執行」執行 npm指令時就會具有 Administrator身分

bull Mac OS X或 Linux系統可以使用 sudo指令例如

sudo npm install -g express

bull Linux系統可以使用 root權限登入或是以「sudo su -」切換成 root身分(使用 root權限操作系統相當危險因此並不建議使用這種方式)

若加上 -g參數使用 npm install -g coffee-script完成安裝

後就可以在終端機執行 coffee指令例如

coffee -v

52 使用 NPM安裝套件 47

Nodejs中文電子書

執行結果(範例)

CoffeeScript version 120

53 套件的更新及維護

除了前一節說明的 search 及 install 用法npm 還提供其他許多指令(commands)

使用 npm help可以查詢可用的指令

npm help

執行結果(部分)

where ltcommandgt is one of

adduser apihelp author bin bugs c cache completion

config deprecate docs edit explore faq find get

help help-search home i info init install la link

list ll ln login ls outdated owner pack prefix

prune publish r rb rebuild remove restart rm root

run-script s se search set show star start stop

submodule tag test un uninstall unlink unpublish

unstar up update version view whoami

使用 npm help command可以查詢指令的詳細用法例如

npm help list

接下來本節要介紹開發過程常用的 npm指令

使用 list可以列出已安裝套件

npm list

48 5 NPM套件管理工具

Nodejs中文電子書

執行結果(範例)

-- coffee-script120

- express256

- connect185

| -- formidable108

-- mime124

-- mkdirp007

-- qs041

檢視某個套件的詳細資訊例如

npm show express

升級所有套件(如果該套件已發佈更新版本)

npm update

升級指定的套件

npm update express

移除指定的套件

npm uninstall express

54 使用 packagejson

對於正式的 Nodejs專案可以建立一個命名為 packagejson的設

定檔(純文字格式)檔案內容參考範例如下

54 使用 packagejson 49

Nodejs中文電子書

packagejson(範例)

rdquonamerdquo rdquoapplication-namerdquo

rdquoversionrdquo rdquo001rdquo

rdquoprivaterdquo true

rdquodependenciesrdquo

rdquoexpressrdquo rdquo255rdquo

rdquocoffee-scriptrdquo rdquolatestrdquo

rdquomongooserdquo rdquogt= 253rdquo

其中 name與 version依照專案的需求設置

需要注意的是 dependencies的設定它用於指定專案相依的套件名

稱及版本

bull rdquoexpressrdquo rdquo255rdquo

代表此專案相依版本 255的 express套件

bull rdquocoffee-scriptrdquo rdquolatestrdquo

使用最新版的 coffee-script套件(每次更新都會檢查新版)

bull rdquomongooserdquo rdquogt= 253rdquo

使用版本大於 253的 mongoose套件

假設某個套件的新版可能造成專案無法正常運作就必須指定套件的

版本避免專案的程式碼來不及更新以相容新版套件通常在開發初期的

專案需要盡可能維持新套件的相容性(以取得套件的更新或修正)可以

用「gt=」設定最低相容的版本或是使用「latest」設定永遠保持最新

套件

50 5 NPM套件管理工具

6

Express介紹

在前面的 nodejs基礎當中介紹許多許多開設 http的使用方法及介紹以及許多基本的 nodejs基本應用

接下來要介紹一個套件稱為 express [Express](httpexpressjscom)這個套件主要幫忙解決許多 nodejs http server所需要的基本服務讓開發 httpservice變得更為容易不需要像之前需要透過層層模組(module)才有辦法開始編寫自己的程式

這個套件是由 TJ Holowaychuk 製作而成的套件裡面包含基本的路由處理 (route)http資料處理(GETPOSTPUT)另外還與樣板套件(jshtml template engine)搭配同時也可以處理許多複雜化的問題

61 Express安裝

安裝方式十分簡單只要透過之前介紹的 NPM就可以使用簡單的指令安裝指令如下

這邊建議需要將此套件安裝成為全域模組方便日後使用

51

Nodejs中文電子書

62 Express基本操作

express的使用也十分簡單先來建立一個基本的 hello world

var app = require(rsquoexpressrsquo)createServer()

port = 1337

applisten(port)

appget(rsquorsquo function(req res)

ressend(rsquohello worldrsquo)

)

consolelog(rsquostart express servernrsquo)

可以從上面的程式碼發現基本操作與 nodejs http的建立方式沒有太大差異主要差在當我們設定路由時可以直接透過 appget方式設定回應與接受方式

63 Express路由處理

Express對於 http服務上有許多包裝讓開發者使用及設定上更為方便例如有幾個路由設定那我們就統一藉由 appget來處理

Create http server

appget(rsquorsquo function(req res)

ressend(rsquohello worldrsquo)

)

appget(rsquotestrsquo function(req res)

ressend(rsquotest renderrsquo)

)

appget(rsquouserrsquo function(req res)

ressend(rsquouser pagersquo)

)

52 6 Express介紹

Nodejs中文電子書

如上面的程式碼所表示appget可以帶入兩個參數第一個是路徑名稱設定第二個為回應函式 (call back function)回應函式裡面就如同之前的 createServer方法裡面包含 requestresponse兩個物件可供使用使用者就可以透過瀏覽器輸入不同的 url切換到不同的頁面顯示不同的結果

路由設定上也有基本的配對方式讓使用者從瀏覽器輸入的網址可以

是一個變數只要符合型態就可以有對應的頁面產出例如

Create http server

appget(rsquouseridrsquo function(req res)

ressend(rsquouser rsquo + reqparamsid)

)

appget(rsquonumberrsquo function(req res)

ressend(rsquonumber rsquo + reqparamsnumber)

)

裡 面 使 用 到number 從 網 址 輸 入 之 後 就 可 以 直 接 使 用reqparamsnumber 取得所輸入的資料變成 url 參數使用當然前面也是可以加上路徑的設定userid在瀏覽器上路徑必須符合userxxx透

過 reqparamsid就可以取到 xxx這個字串值

另外express參數處理也提供了路由參數配對處理也可以透過正規表示法作為參數設定

var app = require(rsquoexpressrsquo)createServer()

port = 1337

applisten(port)

appget(^ip((d23)((d23))((d23))((d23))) function(req res)

ressend(reqparams)

)

上面程式碼可以發現後面路由設定的型態是正規表示法裡面設定

格式為ip之後必須要加上 ip型態才會符合資料格式同時取得 ip資料已經由正規表示法將資料做分群因此可以取得 ip的四個數字

此程式執行之後可以透過瀏覽器測試輸入網址為 localhost3000ip25525510010可以從頁面獲得資料

63 Express路由處理 53

Nodejs中文電子書

此章節全部範例程式碼如下

overview

author Caesar Chi

blog clonnblogspotcom

version 20120226

create server

var app = require(rsquoexpressrsquo)createServer()

port = 1337

applisten(port)

normal style

appget(rsquorsquo function(req res)

ressend(rsquohello worldrsquo)

)

appget(rsquotestrsquo function(req res)

ressend(rsquotest renderrsquo)

)

parameter style

appget(rsquouseridrsquo function(req res)

ressend(rsquouser rsquo + reqparamsid)

)

appget(rsquonumberrsquo function(req res)

ressend(rsquonumber rsquo + reqparamsnumber)

)

REGX style

appget(^ip((d23)((d23))((d23))) function(req res)

ressend(reqparams)

)

appget(rsquorsquo function(req res)

ressend(rsquoPage not foundrsquo 404)

54 6 Express介紹

Nodejs中文電子書

)

consolelog(rsquostart express servernrsquo)

64 Express middleware

Express 裡面有一個十分好用的應用概念稱為 middleware可以透過middleware做出複雜的效果同時上面也有介紹 next方法參數傳遞就是靠 middleware的概念來傳遞參數讓開發者可以明確的控制程式邏輯

create http server

appuse(expressbodyParser())

appuse(expressmethodOverride())

appuse(expresssession())

上面都是一種 middleware的使用方式透過 appuse方式裡面載入函式執行方法回應函式會包含三個基本參數responserequestnext其中next表示下一個 middleware執行函式同時會自動將預設三個參數繼續帶往下個函式執行底下有個實驗

上面的片段程式執行後開啟瀏覽器連結上 localhost1337會發現伺服器回應結果順序如下

first middle ware

second middle ware

execute middle ware

end middleware function

從上面的結果可以得知剛才設定的 middleware都生效了在 appuse設定的 middleware是所有 url皆會執行方法如果有指定特定方法就可以使用 appget的 middleware設定在 appget函式的第二個參數就可以帶入函式或者是匿名函式只要函式裡面最後會接受 request response next這三個參數同時也有正確指定 next函式的執行時機最後都會執行到最後一個方法當然開發者也可以評估程式邏輯要執行到哪一個階段讓邏輯

可以更為分明

64 Express middleware 55

Nodejs中文電子書

65 Express路由應用

在實際開發上可能會遇到需要使用參數等方式混和變數一起使用

express裡面提供了一個很棒的處理方法 appall這個方式可以先採用基本路由配對再將設定為每個不同的處理方式開發者可以透過這個方式簡

化自己的程式邏輯

overview

author

version 20120226

create server

var app = require(rsquoexpressrsquo)createServer()

port = 1337

users = [

name rsquoClonnrsquo

name rsquoChirsquo

]

applisten(port)

appall(rsquouseridoprsquo function(req res next)

requser = users[reqparamsid]

if (requser)

next()

else

next(new Error(rsquocannot find user rsquo + reqparamsid))

)

appget(rsquouseridrsquo function(req res)

ressend(rsquoviewing rsquo + requsername)

)

appget(rsquouserideditrsquo function(req res)

ressend(rsquoediting rsquo + requsername)

)

56 6 Express介紹

Nodejs中文電子書

appget(rsquouseriddeletersquo function(req res)

ressend(rsquodeleting rsquo + requsername)

)

appget(rsquorsquo function(req res)

ressend(rsquoPage not foundrsquo 404)

)

consolelog(rsquostart express servernrsquo)

內部宣告一組預設的使用者分別給予名稱設定藉由 appall這個方法可以先將路由雛形建立再接下來設定 appget的路徑格式只要符合格式就會分配進入對應的方法中像上面的程式當中如果使用者輸入路徑為user0除了執行 appall程式之後執行 next方法就會對應到路徑設定為userid的這個方法當中如果使用者輸入路徑為user0edit就會執行到useridedit的對應方法

66 Express GET應用範例

我們準備一個使用 GET方法傳送資料的表單

ltDOCTYPE htmlgt

lthtmlgt

ltheadgt

ltmeta http-equiv=rdquoContent-Typerdquo content=rdquotexthtml charset=utf8rdquogt

lttitlegtNodejs 菜鳥筆記 (1)lttitlegt

ltlink rel=rdquostylesheetrdquo href=rdquocssstylecssrdquo type=rdquotextcssrdquo media=rdquoallrdquo gt

ltheadgt

ltbodygt

lth1gtNodejs 菜鳥筆記-註冊lth1gt

ltform id=rdquosignuprdquo method=rdquoGETrdquo action=rdquohttplocalhost3000Signuprdquogt

ltlabelgt 使用者名稱ltlabelgtltinput type=rdquotextrdquo id=rdquousernamerdquo name=rdquousernamerdquo gtltbrgt

ltlabelgt 電子郵件ltlabelgtltinput type=rdquotextrdquo id=rdquoemailrdquo name=rdquoemailrdquo gtltbrgt

ltinput type=rdquosubmitrdquo value=rdquo 註冊我的帳號rdquo gtltbrgt

ltformgt

ltbodygt

lthtmlgt

66 Express GET應用範例 57

Nodejs中文電子書

這個表單沒有什麼特別的地方我們只需要看第 9 行form 使用的method是 GET然後 action是rdquohttplocalhost3000Signupldquo等一下我們要來撰寫Signup這個 URL Path的處理程式

處理 Signup行為

我們知道所謂的 GET方法會透過 URL來把表單的值給帶過去以上面的表單來說到時候 URL會以這樣的形式傳遞

httplocalhost3000Signupusername=xxxampemail=xxx

所以要能處理這樣的資料必須有以下功能

bull 解析 URL

bull 辨別動作是 Signup

bull 解析出 username和 email

一旦能取得 username和 email的值程式就能加以應用了

處理 Signup的程式碼雛形

load module

var url = require(rsquourlrsquo)

urlData = urlparse(requrltrue)

action = urlDatapathname

reswriteHead(200 rdquoContent-Typerdquordquotexthtml charset=utf-8rdquo)

if (action === rdquoSignuprdquo)

user = urlDataquery

resend(rdquolth1gtrdquo + userusername + rdquo 歡迎您的加入 lth1gtltpgt 我們已經將會員啟用信寄至rdquo + useremail + rdquoltpgtrdquo)

首先需要加載 url module它是用來協助我們解析 URL的模組接著使用 urlparse方法第一個傳入 url字串變數也就是 requrl另外第二個參數的用意是設為 ture則引進 querystring模組來協助處理預設是 false它影響到的是 urlDataquery設為 true會傳回物件不然就只是一般的字串urlparse會將字串內容整理成一個物件我們把它指定給 urlData

action變數作為記錄 pathname這是我們稍後要來判斷目前網頁的動作是什麼接著先將 html表頭資訊 (Header)準備好再來判斷路徑邏輯如

58 6 Express介紹

Nodejs中文電子書

果是 Signup這個動作就把 urlDataquery裡的資料指定給 user然後輸出userusername和 useremail把使用者從表單註冊的資料顯示於頁面中

最後進行程式測試啟動 nodejs主程式之後開啟瀏覽器就會看到表單填寫完畢按下送出就可以看到結果了

完整 nodejs程式碼如下

var http = require(rsquohttprsquo)

url = require(rsquourlrsquo)

fs = require(rdquofsrdquo)

server

server = httpcreateServer(function (reqres)

var urlData

encode = rdquoutf8rdquo

filePath = rdquoviewexpress_get_example_formhtmlrdquo

action

urlData = urlparse(requrltrue)

action = urlDatapathname

reswriteHead(200 rdquoContent-Typerdquordquotexthtml charset=utf-8rdquo)

if (action === rdquoSignuprdquo)

user = urlDataquery

resend(rdquolth1gtrdquo + userusername + rdquo 歡迎您的加入 lth1gtltpgt 我們已經將會員啟用信寄至rdquo + useremail + rdquoltpgtrdquo)

else

fsreadFile(filePath encode function(err file)

reswrite(file)

resend()

)

)

serverlisten(3000)

consolelog(rsquoServer 跑起來了現在時間是rsquo + new Date())

66 Express GET應用範例 59

Nodejs中文電子書

67 Express POST應用範例

一開始準備基本的 html表單傳送內容以 POST方式form的 action屬性設定為 POST其餘 html內容與前一個範例應用相同

ltDOCTYPE htmlgt

lthtmlgt

ltheadgt

ltmeta http-equiv=rdquoContent-Typerdquo content=rdquotexthtml charset=utf8rdquogt

lttitlegtNodejs 菜鳥筆記 (1)lttitlegt

ltlink rel=rdquostylesheetrdquo href=rdquocssstylecssrdquo type=rdquotextcssrdquo media=rdquoallrdquo gt

ltheadgt

ltbodygt

lth1gtNodejs 菜鳥筆記-註冊lth1gt

ltform id=rdquosignuprdquo method=rdquoPOSTrdquo action=rdquohttplocalhost3000Signuprdquogt

ltlabelgt 使用者名稱ltlabelgtltinput type=rdquotextrdquo id=rdquousernamerdquo name=rdquousernamerdquo gtltbrgt

ltlabelgt 電子郵件ltlabelgtltinput type=rdquotextrdquo id=rdquoemailrdquo name=rdquoemailrdquo gtltbrgt

ltinput type=rdquosubmitrdquo value=rdquo 註冊我的帳號rdquo gtltbrgt

ltformgt

ltbodygt

lthtmlgt

nodejs的程式處理邏輯與前面 GET範例類似部分程式碼如下

qs = require(rsquoquerystringrsquo)

if (action === rdquoSignuprdquo)

formData = rsquorsquo

reqon(rdquodatardquo function (data)

formData += data

)

reqon(rdquoendrdquo function ()

user = qsparse(formData)

resend(rdquolth1gtrdquo + userusername + rdquo 歡迎您的加入 lth1gtltpgt 我們已經將會員啟用信寄至rdquo + useremail + rdquoltpgtrdquo)

)

60 6 Express介紹

Nodejs中文電子書

主要加入了rsquoquerystringrsquo這個moduel方便我們等一下解析由表單 POST回來的資料另外加入一個 formData的變數用來搜集待等一下表單回傳的資料前面的 GET範例我們只從 req拿出 url的資料這次要在利用req身上的事件處理

JavaScript 在訂閱事件時使用 addEventListener而 nodejs 使用的則是on這邊加上了監聽 data的事件會在瀏覽器傳送資料到Web Server時被執行參數是它所接收到的資料型態是字串

接著再增加 end的事件當瀏覽器的請求事件結束時它就會動作

由於瀏覽器使用 POST在上傳資料時會將資料一塊塊地上傳因為我們在監聽 data事件時透過 formData變數將它累加起來lt不過由於我們

上傳的資料很少一次就結束不過如果日後需要傳的是資料比較大的檔

案這個累加動作就很重要

當資料傳完就進到 end 事件中會用到 qsparse 來解析 formDataformData的內容是字串內容是

username=wordsmithampemail=wordsmith40somewhere

而 qsparse可以幫我們把這個 querystring轉成物件的格式也就是

username=wordsmithampemail=wordsmith40somewhere

一旦轉成物件並指定給 user之後其他的事情就和 GET方法時操作的一樣寫 response的表頭將內容回傳並將 userusername和 useremail代入到內容中

修改完成後接著執行 nodejs程式啟動 web server開啟瀏覽器進入表單測試看看POST的方式能否順利運作

完整程式碼如下

var http = require(rsquohttprsquo)

url = require(rsquourlrsquo)

fs = require(rdquofsrdquo)

qs = require(rsquoquerystringrsquo)

server

server = httpcreateServer(function (reqres)

var urlData

67 Express POST應用範例 61

Nodejs中文電子書

encode = rdquoutf8rdquo

filePath = rdquoviewexpress_post_example_formhtmlrdquo

formData

action

urlData = urlparse(requrltrue)

action = urlDatapathname

reswriteHead(200 rdquoContent-Typerdquordquotexthtml charset=utf-8rdquo)

if (action === rdquoSignuprdquo)

formData = rsquorsquo

reqon(rdquodatardquo function (data)

formData += data

)

reqon(rdquoendrdquo function ()

user = qsparse(formData)

resend(rdquolth1gtrdquo + userusername + rdquo 歡迎您的加入 lth1gtltpgt 我們已經將會員啟用信寄至rdquo + useremail + rdquoltpgtrdquo)

)

else

fsreadFile(filePath encode function(err file)

reswrite(file)

resend()

)

)

serverlisten(3000)

consolelog(rsquoServer 跑起來了現在時間是rsquo + new Date())

62 6 Express介紹

Nodejs中文電子書

68 Express AJAX應用範例

在 Nodejs要使用 Ajax傳送資料並且與之互動在接受資料的部份沒有太大的差別client端不是用 GET就是用 POST來傳資料重點在處理完後用 JSON格式回傳當然 Ajax不見得只傳 JSON格式有時是回傳一段 HTML碼不過後者對伺服器來說基本上就和前兩篇沒有差別了所以我們還是以回傳 JSON做為這一回的主題

這一回其實大多數的工作都會落在前端 Ajax上面前端要負責發送與接收資料並在接收資料後撤掉原先發送資料的表單並將取得的資料

改成 HTML格式之後放上頁面

首先先準備 HTML靜態頁面資料

ltDOCTYPE htmlgt

lthtmlgt

ltheadgt

lttitlegtNodejs 菜鳥筆記 (3)lttitlegt

ltlink rel=rdquostylesheetrdquo href=rdquocssstylecssrdquo type=rdquotextcssrdquo media=rdquoallrdquo gt

ltscript src=rdquohttpsajaxgoogleapiscomajaxlibsjquery171jqueryminjsrdquogtltscriptgt

ltheadgt

ltbodygt

lth1gtNodejs 菜鳥筆記-註冊 (用 Ajax)lth1gt

ltform id=rdquosignuprdquo action=rdquoSignuprdquo method=rdquoPOSTrdquogt

ltlabelgt 使用者名稱ltlabelgtltinput type=rdquotextrdquo id=rdquousernamerdquo name=rdquousernamerdquo gtltbrgt

ltlabelgt 電子郵件ltlabelgtltinput type=rdquotextrdquo id=rdquoemailrdquo name=rdquoemailrdquo gtltbrgt

ltinput type=rdquosubmitrdquo value=rdquo 註冊我的帳號rdquo gtltbrgt

ltformgt

ltdiv id=rdquoresultrdquogt

ltdivgt

ltscript type=rdquotextjavascriptrdquogt

$(function()$(rsquosignup input[type=rdquosubmitrdquo]rsquo)click(function(e)

var user =

userusername = $(rdquousernamerdquo)val()useremail = $(rdquoemailrdquo)val()

$post(rdquoSignuprdquouserfunction(data)greet(data)

)

68 Express AJAX應用範例 63

Nodejs中文電子書

epreventDefault()

)

var greet = function(msg)

var resultNode = $(rsquoresultrsquo)greeting = $(rsquolth2gtHirsquo+msgusername+rsquo 你的會員 id 是rsquo+ msgid +rsquo 我們會將會員啟動信寄至rsquo+msgemail+rsquolth2gtrsquo)

$(rsquosignuprsquo)hide()resultNodehtml(rdquordquo)

resultNodeappend(greeting)

)

ltscriptgt

ltbodygt

lthtmlgt

HTML頁面上準備了一個表單用來傳送註冊資料接著直接引用了Google CDN來載入 jQuery用來幫我們處理 Ajax的工作這次要傳送和接收的工作很大的變動都在 HTML頁面上的 JavaScript當中我們要做的事有 (相關 jQuery處理這邊不多做贅述指提起主要功能解說)

bull 用 jQUery取得 submit按鈕綁定它的 click動作

bull 取得表單 username和 email的值存放在 user這個物件中

bull 用 jQuery的 $post方法將 user的資料傳到 Server

bull 一旦成功取得資料後透過 greet這個 function組成回報給 user的訊息

bull 清空原本給使用者填資料的表單

bull 將 Server回傳的 usernameemail和 id這 3個資料組成回應的訊息

bull 將訊息放到原本表單的位置

經過以上的處理後一個 Ajax的表單的基本功能已經完成

接著進行 nodejs主要程式的編輯部分程式碼如下

var fs = require(rdquofsrdquo)

64 6 Express介紹

Nodejs中文電子書

qs = require(rsquoquerystringrsquo)

if (action === rdquoSignuprdquo)

formData = rsquorsquo

reqon(rdquodatardquo function (data)

formData += data

)

reqon(rdquoendrdquo function ()

var msg

user = qsparse(formData)

userid = rdquo123456rdquo

msg = JSONstringify(user)

reswriteHead(200 rdquoContent-Typerdquordquoapplicationjson charset=utf-8rdquordquoContent-Lengthrdquomsglength)

resend(msg)

)

這裡的程式和前面 POST範例基本上大同小異差別在

bull 幫 user的資料加上 id隨意存放一些文字進去讓 Server回傳的資料多於 Client端傳上來的不然會覺得 Server都沒做事

bull 增加了 msg 這個變數存放將 user 物件 JSON 文字化的結果JSONstringify這個轉換函式是 V8引擎所提供的如果你好奇的話

bull 大重點來了我們要告訴 Client端這次回傳的資料格式是 JSON所在 Content-type和 Content-Length要提供給 Client

Server很輕鬆就完成任務了最後進行程式測試啟動 nodejs主程式之後開啟瀏覽器就會看到表單填寫完畢按下送出就可以看到結果了

最後 nodejs本篇範例程式碼如下

var http = require(rsquohttprsquo)

url = require(rsquourlrsquo)

fs = require(rdquofsrdquo)

qs = require(rsquoquerystringrsquo)

server

68 Express AJAX應用範例 65

Nodejs中文電子書

server = httpcreateServer(function (reqres)

var urlData

encode = rdquoutf8rdquo

filePath = rdquoviewexpress_ajax_example_formhtmlrdquo

formData

action

urlData = urlparse(requrltrue)

action = urlDatapathname

if (action === rdquoSignuprdquo)

formData = rsquorsquo

reqon(rdquodatardquo function (data)

formData += data

)

reqon(rdquoendrdquo function ()

var msg

user = qsparse(formData)

userid = rdquo123456rdquo

msg = JSONstringify(user)

reswriteHead(200 rdquoContent-TyperdquordquoapplicationjsonrdquordquoContent-Lengthrdquomsglength)

resend(msg)

)

else

fsreadFile(filePath encode function(err file)

reswriteHead(200 rdquoContent-Typerdquordquotexthtml charset=utf-8rdquo)

reswrite(file)

resend()

)

)

serverlisten(3000)

66 6 Express介紹

Nodejs中文電子書

consolelog(rsquoServer 跑起來了現在時間是rsquo + new Date())

69 原始資料提供

bull [NodeJS初學者筆記 (1)-用 GET傳送資料] (httpithelpithomecomtwquestion10087402)

bull [NodeJS初學者筆記 (2)-用 POST傳送資料] (httpithelpithomecomtwquestion10087489)

bull [NodeJS 初學者筆記 (3)-用 Ajax 傳送資料] (httpithelpithomecomtwquestion10087627)

69 原始資料提供 67

Nodejs中文電子書

68 6 Express介紹

7

CoffeeScript

bull Spine

bull Hem

bull Spineapp

Letrsquos Have a Cup of CoffeeScript

bull es5-shim ECMAScript 5 compatibility shims for legacy JavaScript engines

ESnext

node-iform - A middleware for node-validator httpsgithubcomguileennode-iform

69

Nodejs中文電子書

70 7 CoffeeScript

8

製作一個 Hubot的 Plurk Adapter

81 應用事項提醒

此應用範例以CoffeeScript編寫主要程式架構採用Github釋放的Hubot專案以下有幾個主要注意事項請讀者注意

bull Hubot 一開始的架構除了內建的兩個 Adapter 之外其餘都要以Nodejs的Module方式才能運作

bull Hubot的 binhubot裡面寫著 npm install所以不管你怎麼改原始碼也不能改變第一點的狀況

其實上述都在討論同一件事情NodeJS的模組而且模組不能設定相對路徑之類的來安裝一定要包裝成 npm相符和格式才有辦法正確執行

82 建立 Adapter

首先需要準備兩個檔案

bull packagejson

bull plurkcoffee

71

Nodejs中文電子書

有這兩個就足以變成 NodeJS的模組了在開始 Coding之前先來設定一下 packagejson相依性

rdquonamerdquo rdquohubot-plurkrdquo

rdquoversionrdquo rdquo011rdquo

rdquomainrdquo rdquoplurkrdquo

rdquodependenciesrdquo

rdquohubotrdquo rdquogt=205rdquo

rdquooauthrdquo rdquordquo

rdquocronrdquordquordquo

這邊會使用到NodeJS的 cron模組(Module)而登入 Plurk需要OAuth標準授權才行所以也需要加載 OAuth模組

注意Hubot在讀取非內建模組時會自動在前面加上 hubot-的前置

83 建立 Robot跟 API

本篇應用基本上程式碼大部分參考 Hubot - Twitter Adapter來製作主要差異只有在使用 OAuth這個模組Twitter有 Streaming可用而 Plurk則得用 Comet方式來達到即時讀取

plurkcoffee程式碼

Robot = require(rdquohubotrdquo)robot()

Adapter = require(rdquohubtordquo)adapter()

EventEmitter = require(rdquoeventsrdquo)EventEmitter

oauth = require(rdquooauthrdquo)

cronJob = require(rdquocronrdquo)CronJob

class Plurk exntends Adapter

class PlurkStreaming exnteds EventEmitter

72 8 製作一個 Hubot的 Plurk Adapter

Nodejs中文電子書

先弄個基本架構主要 Class皆參考 Twitter Adapter命名方式接著再將 Plurk這個 Class增加幾個Method基本上只要有 run send reply就夠了而 run用來做初始化的部分

class Plurk entends Adapter

send (plurk id strings⋯) -gt

reply (plurk id strings⋯) -gt

run -gt

看起來有點東西接著來處理主要的 Plurk API結合部分

class PlurkStreaming extends EventEmitter

consuctor (options) -gt

plurk (callback) -gt

觀察河道

getChannel -gt

取得 Comet 網址

reply (plurk id message) -gt

回噗

acceptFriends -gt

接受好友

get (path callback) -gt

GET 請求

post (path body callback)-gt

POST 請求(其實是裝飾)

request (method path body callback)-gt

主要的 OAuth 請求

comet (server callback)-gt

噗浪的 Comet 傳回是 JavaScript Callback 要另外處理後才會變成 JSON

然後把注意力集中到 constructor上先把建構子弄好

constructor (options) -gt

super()

if optionskey and optionssecret and optionstoken and optionstoken secret

key = optionskey

secret = optionssecret

token = optionstoken

token secret = optionstoken secret

83 建立 Robot跟 API 73

Nodejs中文電子書

建立 OAuth 連接

consumer = new oauthOAuth(

rdquohttpwwwplurkcomOAuthrequest tokenrdquo

rdquohttpwwwplurkcomOAuthaccess tokenrdquo

key

secret

rdquo10rdquo

rdquohttpwwwplurkcomOAuthauthorizerdquo

rdquoHMAC-SHA1rdquo

)

domain = rdquowwwplurkcomrdquo

初始化取得 Comet 網址

do getChannel

else

throw new Error(rdquo 參數不足需要 Key Secret Token Token Secretrdquo)

接著來處理 request這個 method

request (method path body callback) -gt

記錄一下這次的 Request

consolelog(rdquohttpdomainpathrdquo)

Callback 這邊先不丟進去要用另一種方式處理

request = consumerget(rdquohttpdomainpathrdquo token token secret null)

requeston rdquoresponserdquo (res) -gt

reson rdquodatardquo (chunk) -gt

parseResponse(chunk+rsquorsquo callback)

reson rdquoendrdquo (data) -gt

consolelog rdquoEnd Request pathrdquo

reson rdquoerrorrdquo (data) -gt

consolelog rdquoError rdquo + data

requestend()

處理資料

parseResponse = (data callback) -gt

if datalength gt 0

用 TryCatch 避免處理 JSON 出錯導致整個中斷

try

callback null JSONparse(data)

catch err

74 8 製作一個 Hubot的 Plurk Adapter

Nodejs中文電子書

consolelog(rdquoError Parse JSONrdquo + data err)

繼續執行

callback null data ||

大致上就是這樣上面程式的架構已經將整個 Hubot Plurk Adapter完成因為在測試時竟然因為噗浪 Lag而沒讀到完整的 Comet資料然後造成程式異常為了避免這個問題發生因此需要加上為了完美呈現需要再

加上 Comet的處理所以要使用到 EventEmitter的功能

comet (server callback) -gt

在 Callback 裡面會找不到自身所以設定區域變數

self =

記錄一下這次的 Request

consolelog(rdquo[Comet] serverrdquo)

Callback 這邊先不丟進去要用另一種方式處理

request = consumerget(rdquohttpdomainpathrdquo token token secret null)

requeston rdquoresponserdquo (res) -gt

reson rdquodatardquo (chunk) -gt

parseResponse(chunk+rsquorsquo callback)

reson rdquoendrdquo (data) -gt

consolelog rdquoEnd Request pathrdquo

請求結束發出事件通知可以進行下一次請求

selfemit rdquonextPlurkrdquo

reson rdquoerrorrdquo (data) -gt

consolelog rdquoError rdquo + data

requestend()

處理資料

parseResponse = (data callback) -gt

if datalength gt 0

用 trycatch 避免失敗中斷

try

去掉 JavaScript 的 Callback

data = datamatch(CometChannelscriptCallback((+))s)

jsonData = rdquordquo

if data

83 建立 Robot跟 API 75

Nodejs中文電子書

jsonData = JSONparse(data[1])

else

如果沒有任何 Match 嘗試直接 parse

jsonData = JSONparse(data)

catch err

consolelog(rdquo[Comet] Errorrdquo data err)

用 TryCatch 避免處理 JSON 出錯導致整個中斷

try

只傳入 json 的 data 部分

callback null jsonDatadata

catch err

consolelog(rdquo[Comet]Error Parse JSONrdquo + data err)

繼續執行

callback null data ||

後面的 get跟 post就簡單多了

get (path callback) -gt

request(rdquoGETrdquo path null callback)

post (path body callback) -gt

request(rdquoPOSTrdquo path body callback)

接著處理取的 Comet網址的 getChannel

getChannel -gt

self =

get rdquoAPPRealtimegetUserChannelrdquo (error data) -gt

if error

檢查是否有 comet server

if datacomet server

selfchannel = datacomet server

如果沒有 Channel Ready 就嘗試連接會失敗

selfemit(rsquochannel readyrsquo)

那麼先來處理 Plurk Adaper好處理的部份

send (plruk id strings⋯)-gt

跟 Reply 一樣直接交給 reply 做

reply plurk id strings⋯

76 8 製作一個 Hubot的 Plurk Adapter

Nodejs中文電子書

reply (plurk id strings⋯) -gt

stringsforEach (message) =gt

botreply(plruk id message)

接著把 run處理好就可以上線運作摟

run -gt

self =

options =

key processenvHUBOT PLURK KEY

secret processenvHUBOT PLURK SECRET

token processenvHUBOT PLURK TOKEN

token secret processenvHUBOT PLURK TOKEN SECRET

創建剛剛的 API

bot = new PlurkStreaming(options)

依照 Twitter 的 new RobotTextMessage 會沒有反應所以參考 hubot-minecraft 的方式

r = robotconstructor

處理噗浪河道訊息

doPlurk = (data)-gt

檢查是否為回噗

if dataresponse

datacontent raw = dataresponsecontent raw

datauser id = dataresponseuser id

確定有噗浪 ID 跟訊息

if dataplurk id and datacontent raw

selfreceive new rTextMessage(dataplurk id datacontent raw)

取得 Comet Server 完成開始第一次 Comet 連接

boton rdquochannel readyrdquo () -gt

botplurk selfdoPlurk

上一次 Comet 完成繼續 Polling

boton rdquonextPlurkrdquo ()-gt

botplurk selfdoPlurk

定時接受好友邀請

do botacceptFriends

bot = bot

83 建立 Robot跟 API 77

Nodejs中文電子書

終於完成 AdapterHubot專案裡面的 scripts資料夾內是互動部分不需要像 Adapter如此大費周章處理只新增檔案並且設計好對白之後就會回噗了我開發用的機器人在此大家可以去跟他玩玩[httpplurkcomelct9620 bot](httpplurkcomelct9620 bot)

84 原始資料提供

bull [製 作 一 個 Hubot 的 噗 浪 Adapter](http revo-skillfrosttw blog20120318create-a-hubot-plurk-adapter)

78 8 製作一個 Hubot的 Plurk Adapter

9

Nodejs好用工具介紹

91 logio

Real-time log monitoring in your browser

httplogioorg

nodejs (gt=0412 lt=0611)

npm config set unsafe-perm true

npm install -g --prefix=usrlocal logio

logio server start

httplocalhost8998

79

Nodejs中文電子書

80 9 Nodejs好用工具介紹

10

精選文章

101 Express

bull 使用 NodeJS + Express從 GETPOST Request取值 [邀稿中]

bull link2 [邀稿中]

bull link3 [邀稿中]

81

Nodejs中文電子書

82 10 精選文章

11

參考資源

111 Nodejs書籍

bull Programming Nodejs April 2012 (est) Travis Swicegood Pragmatic Bookshelf

bull e Little Book on CoffeeScript January 2012 Alex MacCaw OrsquoReilly Media

bull Node for Front-End Developers January 2012 Garann Means OrsquoReilly Media

bull Building Hypermedia APIs with HTML5 and Node November 2011 MikeAmundsen OrsquoReilly Media

bull Node Web Development August 2011 David Herron PacktPub

bull CoffeeScript Accelerated JavaScript Development July 2011 Trevor BurnhamPragmatic Bookshelf

bull Getting Started with GEO CouchDB and Nodejs July 2011 Mick omp-son OrsquoReilly Media

bull What Is Node July 2011 Brett McLaughlin OrsquoReilly Media

bull Node Up and Running May 2011 Tom Hughes-Croucher Mike WilsonOrsquoReilly Media

83

Nodejs中文電子書

bull Hands-on Nodejs e Nodejs introduction and API reference May 2011 Pe-dro Teixeira LeanPub

bull e Node Beginner Book Manuel Kiessling

bull Mastering Nodejs visionmedia

112 Nodejs影音教學

bull e Node Sessions e Best of OSCON 2011 August 2011 OrsquoReilly Media(Video)

bull Tom Hughes-Croucher on Node March 2011 OrsquoReilly Media (Video)

113 Nodejs教學網站

bull Node Tuts

114 Nodejs課程

bull Introduction To Nodejs Van Nguyen CodeLesson

84 11 參考資源

Page 31: Nodejs Wiki

3

Nodejs安裝與設定

本篇將講解如何在各個不同 OS建立 NodeJS環境目前 NodeJS 048版本環境架設方式需依賴 Linux指令才可編譯完成當然在不同作業系統中也已經有 NodeJS package可以直接使用指令快速架設以下各不同作業系統解說如何安裝 NodeJS

31 Ubuntu Linux

更新推薦使用 nvm

git clone gitgithubcomcreationixnvmgit ~nvm

echo rdquo ~nvmnvmshrdquo gtgt ~bashrc

nvm install v0614

nvm alias default v0614

以 上 可 參 考 http dreamerslabcom blog tw how-to-setup-a-node-js-development-environment-on-ubuntu-11-04

使用 APT套件管理工具是常見的方法以下是使用社群提供的 PPA安裝方式

sudo apt-get install python-software-properties

sudo add-apt-repository ppachris-leanodejs-devel

25

Nodejs中文電子書

sudo apt-get update

sudo apt-get install nodejs

32 Other Linux

Linux很適合作為 NodeJS的伺服器作業系統及開發環境安裝前請先確認以下套件已正確安裝

bull curl (wget)用來下載檔案的工具

bull git先進的版本控制工具

bull g++ GNU C++軟體編譯工具

bull make GNU軟體專案建置工具

安裝指令如下如設有權限問題請在指令前面加上 sudo

git clone httpsgithubcomjoyentnodegit

cd node

git checkout v067

configure

make

sudo make install

接著測試 nodeJS是否正常執行

node --version

出現版本訊息即表示安裝成功

33 Windows

nodeJS 在 v060 版本之後開始正式支援 windows native直接使用nodeexe 就可以執行程式支援性完全與 linux 相同更棒的部份就是不需經過編譯經過下載之後簡單設定完成立即開發 node程式

26 3 Nodejs安裝與設定

Nodejs中文電子書

下載 nodejs安裝檔案 lthttpnodejsorgdownloadgt

如此完成 windows native nodeexe安裝接著可以進入 command line執行測試在 command line輸入rdquonoderdquo會出現 nodejs版本訊息畫面表示安裝完成

33 Windows 27

Nodejs中文電子書

28 3 Nodejs安裝與設定

4

Nodejs基礎

前篇文章已經由介紹安裝至設定都有完整介紹nodeJS 內部除了javascript常用的函式 (function)物件 (object)之外也有許多不同的自訂物件nodeJS預設建立這些物件為核心物件是為了要讓開發流程更為這些資料在官方文件已經具有許多具體說明接下來將會介紹在開發 nodeJS程式時常見的物件特性與使用方法

41 nodejs http伺服器建立

在 lsquonodejs官方網站 lthttpnodejsorggtlsquo裡面有舉一個最簡單的 HTTP伺服器建立一開始初步就是建立一個伺服器平台讓 nodejs可以與瀏覽器互相行為每種語言一開始的程式建立都是以 Hello world開始最初也從 Hello world帶各位進入 nodejs的世界

輸入以下程式碼儲存檔案為 node basic http hello worldjs

var server

ip = rdquo127001rdquo

port = 1337

http = require(rsquohttprsquo)

server = httpcreateServer(function (req res)

29

Nodejs中文電子書

reswriteHead(200 rsquoContent-Typersquo rsquotextplainrsquo)

resend(rsquoHello Worldnrsquo)

)

serverlisten(port ip)

consolelog(rdquoServer running at httprdquo + ip + rdquordquo + port)

程式碼解講一開始需要有幾個基本的變數

bull ip 機器本身的 ip位置因為使用本地端因此設定為 127001

bull port 需要開通的阜號通常設定為 http port 80因範例不希望與基本 port相衝隨意設定為 1337

在 nodejs 的程式中有許多預設的模組可以使用因此需要使用require方法將模組引入在這邊我們需要使用 http這個模組因此將 http載入Http模組裡面內建有許多方法可以使用這邊採用 createServer創建一個基本的 http伺服器再將 http伺服器給予一個 server變數裡面的回呼函式 (call back function)可以載入 http伺服器的資料與回應方法 (requestresponse)在程式裡面就可以看到我們直接回應給瀏覽器端所需的 Header回應內容

reswriteHead(200 rsquoContent-Typersquo rsquotextplainrsquo)

resend(rsquoHello Worldnrsquo)

Http伺服器需要設定 port ip在最後需要設定 Http監聽需要使用到listen事件監聽所有 Http伺服器行為

httplisten(port ip)

所有事情都完成之後需要確認伺服器正確執行因此使用 console在javascript裡就有這個原生物件console所印出的資料都會顯示於 nodejs伺服器頁面這邊印出的資料並不會傳送到使用者頁面上之後許多除壞

(debug)都會用到 console物件

consolelog(rdquoServer running at httprdquo + ip + rdquordquo + port)

30 4 Nodejs基礎

Nodejs中文電子書

42 nodejs http路徑建立

前面已經介紹如何建立一個簡單的 http伺服器接下來本章節將會介紹如何處理伺服器路徑 (rout)問題在 http的協定下所有從瀏覽器發出的要求 (request)都需要經過處理路徑上的建立也是如此

路徑就是指伺服器 ip位置或者是網域名稱之後對於伺服器給予的要求修改剛才的 hello world檔案修改如下

server = httpcreateServer(function (req res)

consolelog(requrl)

reswriteHead(200 rsquoContent-Typersquo rsquotextplainrsquo)

resend(rsquohello worldnrsquo)

)

重新啟動 nodejs程式後在瀏覽器端測試一下路徑行為結果如下圖

當在瀏覽器輸入 http1270011337test 在伺服器端會收到兩個要求一個是我們輸入的test要求另外一個則是faviconicotest的路徑要求http伺服器本身需要經過程式設定才有辦法回應給瀏覽器端所需要的回應在伺服器中所有的路徑要求都是需要被解析才有辦法取得資料從

42 nodejs http路徑建立 31

Nodejs中文電子書

上面解說可以了解到在 nodejs當中所有的路徑都需要經過設定未經過設定的路由會讓瀏覽器無法取得任何資料導致錯誤頁面的發生底下將會解

說如何設定路由同時避免發生錯誤情形先前 nodejs程式需要增加一些修改才能讓使用者透過瀏覽器在不同路徑時有不同的結果根據剛才

的程式做如下的修改

var server

ip = rdquo127001rdquo

port = 1337

http = require(rsquohttprsquo)

url = require(rsquourlrsquo)

path

server = httpcreateServer(function (req res)

path = urlparse(requrl)

reswriteHead(200 rsquoContent-Typersquo rsquotextplainrsquo)

switch (pathpathname)

case rdquoindexrdquo

resend(rsquoI am indexnrsquo)

break

case rdquotestrdquo

resend(rsquothis is test pagenrsquo)

break

default

resend(rsquodefault pagenrsquo)

break

)

serverlisten(port ip)

consolelog(rdquoServer running at httprdquo + ip + rdquordquo + port)

程式做了片段的修改首先載入 url模組另外增加一個 path變數url模組就跟如同他的命名一般專門處理 url字串處理裡面提供了許多方法來解決路徑上的問題因為從瀏覽器發出的要求路徑可能會帶有多種需求

或者 GET參數組合等因此我們需要將路徑單純化取用路徑部分的資料即可例如使用者可能會送出 http1270011337testsend=1如果直接

32 4 Nodejs基礎

Nodejs中文電子書

信任 requrl就會收到結果為testsend=1所以需要透過 url模組的方法將路徑資料過濾

在這邊使用 urlparse的方法裡面帶入網址格式資料會回傳路徑資料為了後需方便使用將回傳的資料設定到 path變數當中在回傳的路徑資料裡面包含資訊如下圖

這邊只需要使用單純的路徑要求直接取用 pathpathname就可以達到我們的目的

最後要做路徑的判別在不同的路徑可以指定不同的輸出在範例中

有三個可能結果第一個從瀏覽器輸入index就會顯示 index結果test 就會呈現出 test頁面最後如果都不符合預期的輸入會直接顯示 default的頁面最後的預防可以讓瀏覽器不會出現非預期結果讓程式的可靠性提昇

底下為測試結果

42 nodejs http路徑建立 33

Nodejs中文電子書

43 nodejs檔案讀取

前面已經介紹如何使用路由(rount)做出不同的回應實際應用只有在瀏覽器只有輸出幾個文字資料總是不夠的在本章節中將介紹如何使

用檔案讀取輸出檔案資料讓使用者在前端瀏覽器也可以讀取到完整的

html css javascript檔案輸出

檔案管理最重要的部分就是 lsquoFile system lthttpnodejsorgdocslatestapifshtmlgtlsquo這個模組此模組可以針對檔案做管理監控讀取等行為裡面有許多預設的方法底下是檔案輸出的基本範例底下會有兩個檔案

第一個是靜態 html檔案

ltDOCTYPE htmlgt

lthtml xmlns=rdquohttpwwww3org1999xhtmlrdquo xmllang=rdquozh-CNrdquo lang=rdquozh-CNrdquogt

ltheadgt

ltmeta http-equiv=rdquoContent-Typerdquo content=rdquotexthtml charset=UTF-7rdquo gt

lttitlegtnodejs index html filelttitlegt

ltheadgt

ltbodygt

34 4 Nodejs基礎

Nodejs中文電子書

lth1gtnodejs index html filelth1gt

ltbodygt

lthtmlgt

另一個為 nodejs程式

var fs = require(rdquofsrdquo)

filename = rdquostaticindexhtmlrdquo

encode = rdquoutf8rdquo

fsreadFile(filename encode function(err file)

consolelog(file)

)

一開始直接載入 le system模組 載入名稱為 fs讀取檔案主要使

用的方法為 readFile裡面以三個參數路徑 ( le path) 編碼方式 (encoding)

回應函式 (callback)路徑必須要設定為靜態 html所在位置才能指定到正確的檔案靜態檔案的編碼方式也必須正確這邊使用靜態檔案的編

碼為 utf8如果編碼設定錯誤nodejs讀取出來檔案結果會使用 byte raw格式輸出如果錯誤編碼格式會導致輸出資料為 byte raw

回應函式中裡面會使用兩個變數error為錯誤資訊如果讀取的檔案不存在或者發生錯誤error數值會是 true如果成功讀取資料 error將會是 falsecontent則是檔案內容資料讀取後將會把資料全數丟到 content這個變數當中

最後程式的輸出結果畫面如下

43 nodejs檔案讀取 35

Nodejs中文電子書

44 nodejs http靜態檔案輸出

前面已經了解如何讀取本地端檔案接下來將配合 http伺服器路由讓每個路由都能夠輸出相對應的靜態 html檔案首先新增加幾個靜態 html檔案

ltDOCTYPE htmlgt

lthtml xmlns=rdquohttpwwww3org1999xhtmlrdquo xmllang=rdquozh-CNrdquo lang=rdquozh-CNrdquogt

ltheadgt

ltmeta http-equiv=rdquoContent-Typerdquo content=rdquotexthtml charset=UTF-7rdquo gt

lttitlegtnodejs index html filelttitlegt

ltheadgt

ltbodygt

lth1gtnodejs index html filelth1gt

ltbodygt

lthtmlgt

ltDOCTYPE htmlgt

lthtml xmlns=rdquohttpwwww3org1999xhtmlrdquo xmllang=rdquozh-CNrdquo lang=rdquozh-CNrdquogt

ltheadgt

ltmeta http-equiv=rdquoContent-Typerdquo content=rdquotexthtml charset=UTF-7rdquo gt

lttitlegtnodejs test html filelttitlegt

ltheadgt

ltbodygt

lth1gtnodejs test html filelth1gt

ltbodygt

lthtmlgt

ltDOCTYPE htmlgt

lthtml xmlns=rdquohttpwwww3org1999xhtmlrdquo xmllang=rdquozh-CNrdquo lang=rdquozh-CNrdquogt

ltheadgt

ltmeta http-equiv=rdquoContent-Typerdquo content=rdquotexthtml charset=UTF-7rdquo gt

lttitlegtnodejs static html filelttitlegt

ltheadgt

ltbodygt

lth1gtnodejs static html filelth1gt

ltbodygt

lthtmlgt

36 4 Nodejs基礎

Nodejs中文電子書

準備一個包含基本路由功能的 http伺服器

var server

ip = rdquo127001rdquo

port = 1337

http = require(rsquohttprsquo)

url = require(rsquourlrsquo)

server = httpcreateServer(function (req res)

var path = urlparse(requrl)

)

serverlisten(port ip)

consolelog(rdquoServer running at httprdquo + ip + rdquordquo + port)

加入 le system 模組使用 readFile 的功能將這一段程式放置於

createServer的回應函式中

fsreadFile(filePath encode function(err file)

)

readFile的回應函式裡面加入頁面輸出讓瀏覽器可以正確讀到檔案在這邊我們設定讀取的檔案為 html 靜態檔案所以 Content-type 設定為texthtml讀取到檔案的內容將會正確輸出成 html靜態檔案

fsreadFile(filePath encode function(err file)

reswriteHead(200 rsquoContent-Typersquo rsquotexthtmlrsquo)

reswrite(file)

resend()

)

到這邊為止基本的程式內容都已經完成剩下一些細節的調整首先

路徑上必須做調整目前的靜態檔案全部都放置於 static資料夾底下設定一個變數來記住資料夾位置

接著將瀏覽器發出要求路徑與資料夾組合讀取正確 html靜態檔案使用者有可能會輸入錯誤路徑所以在讀取檔案的時候要加入錯誤處理

同時回應 404伺服器無法正確回應的 http header格式

加入這些細節的修改一個基本的 http 靜態 html輸出伺服器就完成了完整程式碼如下

44 nodejs http靜態檔案輸出 37

Nodejs中文電子書

var server

ip = rdquo127001rdquo

port = 1337

http = require(rsquohttprsquo)

fs = require(rdquofsrdquo)

folderPath = rdquostaticrdquo

url = require(rsquourlrsquo)

path

filePath

encode = rdquoutf8rdquo

server = httpcreateServer(function (req res)

path = urlparse(requrl)

filePath = folderPath + pathpathname

fsreadFile(filePath encode function(err file)

if (err)

reswriteHead(404 rsquoContent-Typersquo rsquotextplainrsquo)

resend()

return

reswriteHead(200 rsquoContent-Typersquo rsquotextapplicationrsquo)

reswrite(file)

resend()

)

)

serverlisten(port ip)

consolelog(rdquoServer running at httprdquo + ip + rdquordquo + port)

45 nodejs http GET資料擷取

http伺服器中除了路由之外另一個最常使用的方法就是擷取 GET資料本單元將會介紹如何透過基本 http伺服器擷取瀏覽器傳來的要求擷取 GET資料

在 http協定中GET參數都是藉由 URL從瀏覽器發出要求送至伺服器

38 4 Nodejs基礎

Nodejs中文電子書

端基本的傳送網址格式可能如下

http127001testsend=1amptest=2

上面這段網址裡面的 GET參數就是 send而這個變數的數值就為 1如果想要在 http伺服器取得 GET資料需要在瀏覽器給予的要求 (request)做處理

首先需要載入 query string這個模組這個模組主要是用來將字串資料

過濾後轉換成javascript物件

qs = require(rsquoquerystringrsquo)

接著在第一階段利用 url模組過濾瀏覽器發出的 URL資料後將回應的物件裡面的 query這個變數是一個字串值資料過濾後如下

send=1amptest=2

透過 query string使用 parse這個方法將資料轉換成 javascript物件就表示 GET的資料已經被伺服器端正式擷取下來

path = urlparse(requrl)

parameter = qsparse(pathquery)

整個 nodejs http GET參數完整擷取程式碼如下

var server

ip = rdquo127001rdquo

port = 1337

http = require(rsquohttprsquo)

qs = require(rsquoquerystringrsquo)

url = require(rsquourlrsquo)

server = httpcreateServer(function (req res)

var path = urlparse(requrl)

parameter = qsparse(pathquery)

consoledir(parameter)

reswriteHead(200 rsquoContent-Typersquo rsquotextplainrsquo)

reswrite(rsquoBrowser test GET parameternrsquo)

resend()

)

45 nodejs http GET資料擷取 39

Nodejs中文電子書

serverlisten(port ip)

consolelog(rdquoServer running at httprdquo + ip + rdquordquo + port)

程式運作之後由瀏覽器輸入要求網址之後nodejs伺服器端回應資料為

Server running at http1270011337

send rsquo1rsquo test rsquo1rsquo

46 本章結語

前面所解說的部份一大部分主要是處理 http伺服器基本問題雖然在某些部分有牽扯到 http 伺服器基本運作原理主要還是希望可以藉由這些基本範例練習 nodejs練習回應函式與語法串接的特點習慣編寫javascript風格程式當然直接這樣開發 nodejs是非常辛苦的接下來在模組實戰開發的部份將會介紹特定的模組一步一步帶領各位從無到有進行

nodejs應用程式開發

40 4 Nodejs基礎

5

NPM套件管理工具

npm全名為 Node Package Manager是 Nodejs的套件(package)管理工具類似 Perl的 ppm或 PHP的 PEAR等安裝 npm後使用npm install

module name指令即可安裝新套件維護管理套件的工作會更加輕鬆

npm可以讓Nodejs的開發者直接利用擴充線上的套件庫(packagesregistry)加速軟體專案的開發npm提供很友善的搜尋功能可以快速找到安裝需要的套件當這些套件發行新版本時npm也可以協助開發者自動更新這些套件

npm不僅可用於安裝新的套件它也支援搜尋列出已安裝模組及更新的功能

51 安裝 NPM

Nodejs在 063版本開始內建 npm讀者安裝的版本若是此版本或更新的版本就可以略過以下安裝說明

若要檢查 npm是否正確安裝可以使用以下的指令

npm -v

41

Nodejs中文電子書

執行結果說明

若 npm正確安裝執行 npm -v將會看到類似 110-2的版本訊息

若讀者安裝的 Nodejs版本比較舊或是有興趣嘗試自己動手安裝 npm工具則可以參考以下的說明

安裝於Windows系統

Nodejs for Windows 於 062 版開始內建 npm使用 nodejsorg 官方提供的安裝程式不需要進一步的設定就可以立即使用 npm指令對於Windows的開發者來說大幅降低環境設定的問題與門檻

除了使用 Nodejs內建的 npm讀者也可以從 npm官方提供的以下網址

httpnpmjsorgdist

這是由 npm提供的 Fancy Windows Install版本請下載壓縮檔(例如npm-110-3zip)並將壓縮檔內容解壓縮至 Nodejs的安裝路徑(例如CProgram Filesnodejs)

解壓縮後在 Nodejs的安裝路徑下應該有以下的檔案及資料夾

bull npmcmd(檔案)

bull node modules(資料夾)

安裝於 Linux系統

Ubuntu Linux的使用者可以加入 NPM Unoffcial PPA這個 repository即可使用 apt-get完成 npm安裝

42 5 NPM套件管理工具

Nodejs中文電子書

Ubuntu Linux使用 apt-get安裝 npm

sudo apt-get install python-software-properties

sudo add-apt-repository ppagias-kay-leenpm

sudo apt-get update

sudo apt-get install npm

npm官方提供的安裝程式 installsh可以適用於大多數的 Linux系統使用這個安裝程式請先確認

1 系統已安裝 curl工具(請使用 curl --version查看版本訊息)

2 已安裝 Nodejs並且 PATH正確設置

3 Nodejs的版本必須大於 04x

以下為 npm提供的安裝指令

curl httpnpmjsorginstallsh | sh

安裝成功會看到如下訊息

installsh安裝成功的訊息

npm10105 homeUSERNAMElocalnodelibnode_modulesnpm

It worked

安裝於Mac OS X

建議採用與 Nodejs相同的方式進行 npm的安裝例如使用MacPorts安裝 Nodejs就同樣使用MacPorts安裝 npm這樣對日後的維護才會更方便容易

使用 MacPorts安裝 npm是本書比較建議的方式它可以讓 npm的安裝移除及更新工作自動化將會幫助開發者節省寶貴時間

51 安裝 NPM 43

Nodejs中文電子書

安裝MacPorts的提示

在MacPorts網站可以取得 OS X系統版本對應的安裝程式(例如 106或 107)httpwwwmacportsorg安裝過程會詢問系統管理者密碼使用預設的選項完成安裝即可安

裝MacPorts之後在終端機執行 port -v將會看到MacPorts的版本訊息

安裝 npm之前先更新MacPorts的套件清單以確保安裝的 npm是最新版本

sudo port -d selfupdate

接著安裝 npm

sudo port install npm

若讀者的 Nodejs並非使用MacPorts安裝則不建議使用MacPorts安裝npm因為 MacPorts會自動檢查並安裝相依套件而 npm相依 nodejs所以MacPorts也會一併將 nodejs套件安裝造成先前讀者使用其它方式安裝的 nodejs被覆蓋

讀者可以先使用 MacPorts安裝 curl(sudo port install curl)

再參考 Linux的 installsh安裝方式即可使用 npm官方提供的安裝程式

NPM安裝後測試

npm是指令列工具(command-line tool)使用時請先打開系統的文字終端機工具

測試 npm安裝與設定是否正確請輸入指令如下

npm -v

或是

npm --version

如果 npm已經正確安裝設定就會顯示版本訊息

44 5 NPM套件管理工具

Nodejs中文電子書

執行結果(範例)

110-2

52 使用 NPM安裝套件

npm目前擁有超過 6000種套件(packages)可以在 npm registry使用關鍵字搜尋套件

httpsearchnpmjsorg

舉例來說在關鍵字欄位輸入「coffee-script」下方的清單就會自動列出包含 coffee-script關鍵字的套件

接著我們回到終端機模式的操作npm的指令工具本身就可以完成套

件搜尋的任務

例如以下的指令同樣可以找出 coffee-script相關套件

npm search coffee-script

以下是搜尋結果的參考畫面

52 使用 NPM安裝套件 45

Nodejs中文電子書

找到需要的套件後(例如 express)即可使用以下指令安裝

npm install coffee-script

值得注意的一點是使用 npm install會將指定的套件安裝在工

作目錄(Working Directory)的 node modules資料夾下

以Windows為例如果執行 npm install的目錄位於

Cproject1

那麼 npm將會自動建立一個 node modules的子目錄(如果不存在)

Cproject1node modules

並且將下載的套件放置於這個子目錄例如

Cproject1node modulescoffee-script

這個設計讓專案可以個別管理相依的套件並且可以在專案佈署或發

行時將這些套件(位於 node modules)一併打包方便其它專案的使用者不必再重新下載套件

這個 npm install的預設安裝模式為 local(本地)只會變更當前專案的資料夾不會影響系統

另一種安裝模式稱為 global(全域)這種模式會將套件安裝到系統資

料夾也就是 npm安裝路徑的 node modules資料夾例如

CProgram Filesnodejsnode modules

46 5 NPM套件管理工具

Nodejs中文電子書

是否要使用全域安裝可以依照套件是否提供新指令來判斷舉例來說express 套件提供 express 這個指令而 coffee-script 則提供 coffee

指令

在 local 安 裝 模 式 中這 些 指 令 的 程 式 檔 案會 被 安 裝 到node modules的 bin這個隱藏資料夾下除非將bin的路徑加入 PATH環境變數否則要執行這些指令將會相當不便

為了方便指令的執行我們可以在 npm install 加上 -g 或 --

global參數啟用 global安裝模式例如

npm install -g coffee-script

npm install -g express

使用 global安裝模式需要注意執行權限的問題若權限不足可能會出現類似以下的錯誤訊息

npm ERR Error EACCES permission denied rsquorsquo

npm ERR

npm ERR Please try running this command again as rootAdministrator

要獲得足夠得執行權限請參考以下說明

bull Windows 7 或 2008 以上在「命令提示字元」的捷徑按右鍵選擇「以系統管理員身分執行」執行 npm指令時就會具有 Administrator身分

bull Mac OS X或 Linux系統可以使用 sudo指令例如

sudo npm install -g express

bull Linux系統可以使用 root權限登入或是以「sudo su -」切換成 root身分(使用 root權限操作系統相當危險因此並不建議使用這種方式)

若加上 -g參數使用 npm install -g coffee-script完成安裝

後就可以在終端機執行 coffee指令例如

coffee -v

52 使用 NPM安裝套件 47

Nodejs中文電子書

執行結果(範例)

CoffeeScript version 120

53 套件的更新及維護

除了前一節說明的 search 及 install 用法npm 還提供其他許多指令(commands)

使用 npm help可以查詢可用的指令

npm help

執行結果(部分)

where ltcommandgt is one of

adduser apihelp author bin bugs c cache completion

config deprecate docs edit explore faq find get

help help-search home i info init install la link

list ll ln login ls outdated owner pack prefix

prune publish r rb rebuild remove restart rm root

run-script s se search set show star start stop

submodule tag test un uninstall unlink unpublish

unstar up update version view whoami

使用 npm help command可以查詢指令的詳細用法例如

npm help list

接下來本節要介紹開發過程常用的 npm指令

使用 list可以列出已安裝套件

npm list

48 5 NPM套件管理工具

Nodejs中文電子書

執行結果(範例)

-- coffee-script120

- express256

- connect185

| -- formidable108

-- mime124

-- mkdirp007

-- qs041

檢視某個套件的詳細資訊例如

npm show express

升級所有套件(如果該套件已發佈更新版本)

npm update

升級指定的套件

npm update express

移除指定的套件

npm uninstall express

54 使用 packagejson

對於正式的 Nodejs專案可以建立一個命名為 packagejson的設

定檔(純文字格式)檔案內容參考範例如下

54 使用 packagejson 49

Nodejs中文電子書

packagejson(範例)

rdquonamerdquo rdquoapplication-namerdquo

rdquoversionrdquo rdquo001rdquo

rdquoprivaterdquo true

rdquodependenciesrdquo

rdquoexpressrdquo rdquo255rdquo

rdquocoffee-scriptrdquo rdquolatestrdquo

rdquomongooserdquo rdquogt= 253rdquo

其中 name與 version依照專案的需求設置

需要注意的是 dependencies的設定它用於指定專案相依的套件名

稱及版本

bull rdquoexpressrdquo rdquo255rdquo

代表此專案相依版本 255的 express套件

bull rdquocoffee-scriptrdquo rdquolatestrdquo

使用最新版的 coffee-script套件(每次更新都會檢查新版)

bull rdquomongooserdquo rdquogt= 253rdquo

使用版本大於 253的 mongoose套件

假設某個套件的新版可能造成專案無法正常運作就必須指定套件的

版本避免專案的程式碼來不及更新以相容新版套件通常在開發初期的

專案需要盡可能維持新套件的相容性(以取得套件的更新或修正)可以

用「gt=」設定最低相容的版本或是使用「latest」設定永遠保持最新

套件

50 5 NPM套件管理工具

6

Express介紹

在前面的 nodejs基礎當中介紹許多許多開設 http的使用方法及介紹以及許多基本的 nodejs基本應用

接下來要介紹一個套件稱為 express [Express](httpexpressjscom)這個套件主要幫忙解決許多 nodejs http server所需要的基本服務讓開發 httpservice變得更為容易不需要像之前需要透過層層模組(module)才有辦法開始編寫自己的程式

這個套件是由 TJ Holowaychuk 製作而成的套件裡面包含基本的路由處理 (route)http資料處理(GETPOSTPUT)另外還與樣板套件(jshtml template engine)搭配同時也可以處理許多複雜化的問題

61 Express安裝

安裝方式十分簡單只要透過之前介紹的 NPM就可以使用簡單的指令安裝指令如下

這邊建議需要將此套件安裝成為全域模組方便日後使用

51

Nodejs中文電子書

62 Express基本操作

express的使用也十分簡單先來建立一個基本的 hello world

var app = require(rsquoexpressrsquo)createServer()

port = 1337

applisten(port)

appget(rsquorsquo function(req res)

ressend(rsquohello worldrsquo)

)

consolelog(rsquostart express servernrsquo)

可以從上面的程式碼發現基本操作與 nodejs http的建立方式沒有太大差異主要差在當我們設定路由時可以直接透過 appget方式設定回應與接受方式

63 Express路由處理

Express對於 http服務上有許多包裝讓開發者使用及設定上更為方便例如有幾個路由設定那我們就統一藉由 appget來處理

Create http server

appget(rsquorsquo function(req res)

ressend(rsquohello worldrsquo)

)

appget(rsquotestrsquo function(req res)

ressend(rsquotest renderrsquo)

)

appget(rsquouserrsquo function(req res)

ressend(rsquouser pagersquo)

)

52 6 Express介紹

Nodejs中文電子書

如上面的程式碼所表示appget可以帶入兩個參數第一個是路徑名稱設定第二個為回應函式 (call back function)回應函式裡面就如同之前的 createServer方法裡面包含 requestresponse兩個物件可供使用使用者就可以透過瀏覽器輸入不同的 url切換到不同的頁面顯示不同的結果

路由設定上也有基本的配對方式讓使用者從瀏覽器輸入的網址可以

是一個變數只要符合型態就可以有對應的頁面產出例如

Create http server

appget(rsquouseridrsquo function(req res)

ressend(rsquouser rsquo + reqparamsid)

)

appget(rsquonumberrsquo function(req res)

ressend(rsquonumber rsquo + reqparamsnumber)

)

裡 面 使 用 到number 從 網 址 輸 入 之 後 就 可 以 直 接 使 用reqparamsnumber 取得所輸入的資料變成 url 參數使用當然前面也是可以加上路徑的設定userid在瀏覽器上路徑必須符合userxxx透

過 reqparamsid就可以取到 xxx這個字串值

另外express參數處理也提供了路由參數配對處理也可以透過正規表示法作為參數設定

var app = require(rsquoexpressrsquo)createServer()

port = 1337

applisten(port)

appget(^ip((d23)((d23))((d23))((d23))) function(req res)

ressend(reqparams)

)

上面程式碼可以發現後面路由設定的型態是正規表示法裡面設定

格式為ip之後必須要加上 ip型態才會符合資料格式同時取得 ip資料已經由正規表示法將資料做分群因此可以取得 ip的四個數字

此程式執行之後可以透過瀏覽器測試輸入網址為 localhost3000ip25525510010可以從頁面獲得資料

63 Express路由處理 53

Nodejs中文電子書

此章節全部範例程式碼如下

overview

author Caesar Chi

blog clonnblogspotcom

version 20120226

create server

var app = require(rsquoexpressrsquo)createServer()

port = 1337

applisten(port)

normal style

appget(rsquorsquo function(req res)

ressend(rsquohello worldrsquo)

)

appget(rsquotestrsquo function(req res)

ressend(rsquotest renderrsquo)

)

parameter style

appget(rsquouseridrsquo function(req res)

ressend(rsquouser rsquo + reqparamsid)

)

appget(rsquonumberrsquo function(req res)

ressend(rsquonumber rsquo + reqparamsnumber)

)

REGX style

appget(^ip((d23)((d23))((d23))) function(req res)

ressend(reqparams)

)

appget(rsquorsquo function(req res)

ressend(rsquoPage not foundrsquo 404)

54 6 Express介紹

Nodejs中文電子書

)

consolelog(rsquostart express servernrsquo)

64 Express middleware

Express 裡面有一個十分好用的應用概念稱為 middleware可以透過middleware做出複雜的效果同時上面也有介紹 next方法參數傳遞就是靠 middleware的概念來傳遞參數讓開發者可以明確的控制程式邏輯

create http server

appuse(expressbodyParser())

appuse(expressmethodOverride())

appuse(expresssession())

上面都是一種 middleware的使用方式透過 appuse方式裡面載入函式執行方法回應函式會包含三個基本參數responserequestnext其中next表示下一個 middleware執行函式同時會自動將預設三個參數繼續帶往下個函式執行底下有個實驗

上面的片段程式執行後開啟瀏覽器連結上 localhost1337會發現伺服器回應結果順序如下

first middle ware

second middle ware

execute middle ware

end middleware function

從上面的結果可以得知剛才設定的 middleware都生效了在 appuse設定的 middleware是所有 url皆會執行方法如果有指定特定方法就可以使用 appget的 middleware設定在 appget函式的第二個參數就可以帶入函式或者是匿名函式只要函式裡面最後會接受 request response next這三個參數同時也有正確指定 next函式的執行時機最後都會執行到最後一個方法當然開發者也可以評估程式邏輯要執行到哪一個階段讓邏輯

可以更為分明

64 Express middleware 55

Nodejs中文電子書

65 Express路由應用

在實際開發上可能會遇到需要使用參數等方式混和變數一起使用

express裡面提供了一個很棒的處理方法 appall這個方式可以先採用基本路由配對再將設定為每個不同的處理方式開發者可以透過這個方式簡

化自己的程式邏輯

overview

author

version 20120226

create server

var app = require(rsquoexpressrsquo)createServer()

port = 1337

users = [

name rsquoClonnrsquo

name rsquoChirsquo

]

applisten(port)

appall(rsquouseridoprsquo function(req res next)

requser = users[reqparamsid]

if (requser)

next()

else

next(new Error(rsquocannot find user rsquo + reqparamsid))

)

appget(rsquouseridrsquo function(req res)

ressend(rsquoviewing rsquo + requsername)

)

appget(rsquouserideditrsquo function(req res)

ressend(rsquoediting rsquo + requsername)

)

56 6 Express介紹

Nodejs中文電子書

appget(rsquouseriddeletersquo function(req res)

ressend(rsquodeleting rsquo + requsername)

)

appget(rsquorsquo function(req res)

ressend(rsquoPage not foundrsquo 404)

)

consolelog(rsquostart express servernrsquo)

內部宣告一組預設的使用者分別給予名稱設定藉由 appall這個方法可以先將路由雛形建立再接下來設定 appget的路徑格式只要符合格式就會分配進入對應的方法中像上面的程式當中如果使用者輸入路徑為user0除了執行 appall程式之後執行 next方法就會對應到路徑設定為userid的這個方法當中如果使用者輸入路徑為user0edit就會執行到useridedit的對應方法

66 Express GET應用範例

我們準備一個使用 GET方法傳送資料的表單

ltDOCTYPE htmlgt

lthtmlgt

ltheadgt

ltmeta http-equiv=rdquoContent-Typerdquo content=rdquotexthtml charset=utf8rdquogt

lttitlegtNodejs 菜鳥筆記 (1)lttitlegt

ltlink rel=rdquostylesheetrdquo href=rdquocssstylecssrdquo type=rdquotextcssrdquo media=rdquoallrdquo gt

ltheadgt

ltbodygt

lth1gtNodejs 菜鳥筆記-註冊lth1gt

ltform id=rdquosignuprdquo method=rdquoGETrdquo action=rdquohttplocalhost3000Signuprdquogt

ltlabelgt 使用者名稱ltlabelgtltinput type=rdquotextrdquo id=rdquousernamerdquo name=rdquousernamerdquo gtltbrgt

ltlabelgt 電子郵件ltlabelgtltinput type=rdquotextrdquo id=rdquoemailrdquo name=rdquoemailrdquo gtltbrgt

ltinput type=rdquosubmitrdquo value=rdquo 註冊我的帳號rdquo gtltbrgt

ltformgt

ltbodygt

lthtmlgt

66 Express GET應用範例 57

Nodejs中文電子書

這個表單沒有什麼特別的地方我們只需要看第 9 行form 使用的method是 GET然後 action是rdquohttplocalhost3000Signupldquo等一下我們要來撰寫Signup這個 URL Path的處理程式

處理 Signup行為

我們知道所謂的 GET方法會透過 URL來把表單的值給帶過去以上面的表單來說到時候 URL會以這樣的形式傳遞

httplocalhost3000Signupusername=xxxampemail=xxx

所以要能處理這樣的資料必須有以下功能

bull 解析 URL

bull 辨別動作是 Signup

bull 解析出 username和 email

一旦能取得 username和 email的值程式就能加以應用了

處理 Signup的程式碼雛形

load module

var url = require(rsquourlrsquo)

urlData = urlparse(requrltrue)

action = urlDatapathname

reswriteHead(200 rdquoContent-Typerdquordquotexthtml charset=utf-8rdquo)

if (action === rdquoSignuprdquo)

user = urlDataquery

resend(rdquolth1gtrdquo + userusername + rdquo 歡迎您的加入 lth1gtltpgt 我們已經將會員啟用信寄至rdquo + useremail + rdquoltpgtrdquo)

首先需要加載 url module它是用來協助我們解析 URL的模組接著使用 urlparse方法第一個傳入 url字串變數也就是 requrl另外第二個參數的用意是設為 ture則引進 querystring模組來協助處理預設是 false它影響到的是 urlDataquery設為 true會傳回物件不然就只是一般的字串urlparse會將字串內容整理成一個物件我們把它指定給 urlData

action變數作為記錄 pathname這是我們稍後要來判斷目前網頁的動作是什麼接著先將 html表頭資訊 (Header)準備好再來判斷路徑邏輯如

58 6 Express介紹

Nodejs中文電子書

果是 Signup這個動作就把 urlDataquery裡的資料指定給 user然後輸出userusername和 useremail把使用者從表單註冊的資料顯示於頁面中

最後進行程式測試啟動 nodejs主程式之後開啟瀏覽器就會看到表單填寫完畢按下送出就可以看到結果了

完整 nodejs程式碼如下

var http = require(rsquohttprsquo)

url = require(rsquourlrsquo)

fs = require(rdquofsrdquo)

server

server = httpcreateServer(function (reqres)

var urlData

encode = rdquoutf8rdquo

filePath = rdquoviewexpress_get_example_formhtmlrdquo

action

urlData = urlparse(requrltrue)

action = urlDatapathname

reswriteHead(200 rdquoContent-Typerdquordquotexthtml charset=utf-8rdquo)

if (action === rdquoSignuprdquo)

user = urlDataquery

resend(rdquolth1gtrdquo + userusername + rdquo 歡迎您的加入 lth1gtltpgt 我們已經將會員啟用信寄至rdquo + useremail + rdquoltpgtrdquo)

else

fsreadFile(filePath encode function(err file)

reswrite(file)

resend()

)

)

serverlisten(3000)

consolelog(rsquoServer 跑起來了現在時間是rsquo + new Date())

66 Express GET應用範例 59

Nodejs中文電子書

67 Express POST應用範例

一開始準備基本的 html表單傳送內容以 POST方式form的 action屬性設定為 POST其餘 html內容與前一個範例應用相同

ltDOCTYPE htmlgt

lthtmlgt

ltheadgt

ltmeta http-equiv=rdquoContent-Typerdquo content=rdquotexthtml charset=utf8rdquogt

lttitlegtNodejs 菜鳥筆記 (1)lttitlegt

ltlink rel=rdquostylesheetrdquo href=rdquocssstylecssrdquo type=rdquotextcssrdquo media=rdquoallrdquo gt

ltheadgt

ltbodygt

lth1gtNodejs 菜鳥筆記-註冊lth1gt

ltform id=rdquosignuprdquo method=rdquoPOSTrdquo action=rdquohttplocalhost3000Signuprdquogt

ltlabelgt 使用者名稱ltlabelgtltinput type=rdquotextrdquo id=rdquousernamerdquo name=rdquousernamerdquo gtltbrgt

ltlabelgt 電子郵件ltlabelgtltinput type=rdquotextrdquo id=rdquoemailrdquo name=rdquoemailrdquo gtltbrgt

ltinput type=rdquosubmitrdquo value=rdquo 註冊我的帳號rdquo gtltbrgt

ltformgt

ltbodygt

lthtmlgt

nodejs的程式處理邏輯與前面 GET範例類似部分程式碼如下

qs = require(rsquoquerystringrsquo)

if (action === rdquoSignuprdquo)

formData = rsquorsquo

reqon(rdquodatardquo function (data)

formData += data

)

reqon(rdquoendrdquo function ()

user = qsparse(formData)

resend(rdquolth1gtrdquo + userusername + rdquo 歡迎您的加入 lth1gtltpgt 我們已經將會員啟用信寄至rdquo + useremail + rdquoltpgtrdquo)

)

60 6 Express介紹

Nodejs中文電子書

主要加入了rsquoquerystringrsquo這個moduel方便我們等一下解析由表單 POST回來的資料另外加入一個 formData的變數用來搜集待等一下表單回傳的資料前面的 GET範例我們只從 req拿出 url的資料這次要在利用req身上的事件處理

JavaScript 在訂閱事件時使用 addEventListener而 nodejs 使用的則是on這邊加上了監聽 data的事件會在瀏覽器傳送資料到Web Server時被執行參數是它所接收到的資料型態是字串

接著再增加 end的事件當瀏覽器的請求事件結束時它就會動作

由於瀏覽器使用 POST在上傳資料時會將資料一塊塊地上傳因為我們在監聽 data事件時透過 formData變數將它累加起來lt不過由於我們

上傳的資料很少一次就結束不過如果日後需要傳的是資料比較大的檔

案這個累加動作就很重要

當資料傳完就進到 end 事件中會用到 qsparse 來解析 formDataformData的內容是字串內容是

username=wordsmithampemail=wordsmith40somewhere

而 qsparse可以幫我們把這個 querystring轉成物件的格式也就是

username=wordsmithampemail=wordsmith40somewhere

一旦轉成物件並指定給 user之後其他的事情就和 GET方法時操作的一樣寫 response的表頭將內容回傳並將 userusername和 useremail代入到內容中

修改完成後接著執行 nodejs程式啟動 web server開啟瀏覽器進入表單測試看看POST的方式能否順利運作

完整程式碼如下

var http = require(rsquohttprsquo)

url = require(rsquourlrsquo)

fs = require(rdquofsrdquo)

qs = require(rsquoquerystringrsquo)

server

server = httpcreateServer(function (reqres)

var urlData

67 Express POST應用範例 61

Nodejs中文電子書

encode = rdquoutf8rdquo

filePath = rdquoviewexpress_post_example_formhtmlrdquo

formData

action

urlData = urlparse(requrltrue)

action = urlDatapathname

reswriteHead(200 rdquoContent-Typerdquordquotexthtml charset=utf-8rdquo)

if (action === rdquoSignuprdquo)

formData = rsquorsquo

reqon(rdquodatardquo function (data)

formData += data

)

reqon(rdquoendrdquo function ()

user = qsparse(formData)

resend(rdquolth1gtrdquo + userusername + rdquo 歡迎您的加入 lth1gtltpgt 我們已經將會員啟用信寄至rdquo + useremail + rdquoltpgtrdquo)

)

else

fsreadFile(filePath encode function(err file)

reswrite(file)

resend()

)

)

serverlisten(3000)

consolelog(rsquoServer 跑起來了現在時間是rsquo + new Date())

62 6 Express介紹

Nodejs中文電子書

68 Express AJAX應用範例

在 Nodejs要使用 Ajax傳送資料並且與之互動在接受資料的部份沒有太大的差別client端不是用 GET就是用 POST來傳資料重點在處理完後用 JSON格式回傳當然 Ajax不見得只傳 JSON格式有時是回傳一段 HTML碼不過後者對伺服器來說基本上就和前兩篇沒有差別了所以我們還是以回傳 JSON做為這一回的主題

這一回其實大多數的工作都會落在前端 Ajax上面前端要負責發送與接收資料並在接收資料後撤掉原先發送資料的表單並將取得的資料

改成 HTML格式之後放上頁面

首先先準備 HTML靜態頁面資料

ltDOCTYPE htmlgt

lthtmlgt

ltheadgt

lttitlegtNodejs 菜鳥筆記 (3)lttitlegt

ltlink rel=rdquostylesheetrdquo href=rdquocssstylecssrdquo type=rdquotextcssrdquo media=rdquoallrdquo gt

ltscript src=rdquohttpsajaxgoogleapiscomajaxlibsjquery171jqueryminjsrdquogtltscriptgt

ltheadgt

ltbodygt

lth1gtNodejs 菜鳥筆記-註冊 (用 Ajax)lth1gt

ltform id=rdquosignuprdquo action=rdquoSignuprdquo method=rdquoPOSTrdquogt

ltlabelgt 使用者名稱ltlabelgtltinput type=rdquotextrdquo id=rdquousernamerdquo name=rdquousernamerdquo gtltbrgt

ltlabelgt 電子郵件ltlabelgtltinput type=rdquotextrdquo id=rdquoemailrdquo name=rdquoemailrdquo gtltbrgt

ltinput type=rdquosubmitrdquo value=rdquo 註冊我的帳號rdquo gtltbrgt

ltformgt

ltdiv id=rdquoresultrdquogt

ltdivgt

ltscript type=rdquotextjavascriptrdquogt

$(function()$(rsquosignup input[type=rdquosubmitrdquo]rsquo)click(function(e)

var user =

userusername = $(rdquousernamerdquo)val()useremail = $(rdquoemailrdquo)val()

$post(rdquoSignuprdquouserfunction(data)greet(data)

)

68 Express AJAX應用範例 63

Nodejs中文電子書

epreventDefault()

)

var greet = function(msg)

var resultNode = $(rsquoresultrsquo)greeting = $(rsquolth2gtHirsquo+msgusername+rsquo 你的會員 id 是rsquo+ msgid +rsquo 我們會將會員啟動信寄至rsquo+msgemail+rsquolth2gtrsquo)

$(rsquosignuprsquo)hide()resultNodehtml(rdquordquo)

resultNodeappend(greeting)

)

ltscriptgt

ltbodygt

lthtmlgt

HTML頁面上準備了一個表單用來傳送註冊資料接著直接引用了Google CDN來載入 jQuery用來幫我們處理 Ajax的工作這次要傳送和接收的工作很大的變動都在 HTML頁面上的 JavaScript當中我們要做的事有 (相關 jQuery處理這邊不多做贅述指提起主要功能解說)

bull 用 jQUery取得 submit按鈕綁定它的 click動作

bull 取得表單 username和 email的值存放在 user這個物件中

bull 用 jQuery的 $post方法將 user的資料傳到 Server

bull 一旦成功取得資料後透過 greet這個 function組成回報給 user的訊息

bull 清空原本給使用者填資料的表單

bull 將 Server回傳的 usernameemail和 id這 3個資料組成回應的訊息

bull 將訊息放到原本表單的位置

經過以上的處理後一個 Ajax的表單的基本功能已經完成

接著進行 nodejs主要程式的編輯部分程式碼如下

var fs = require(rdquofsrdquo)

64 6 Express介紹

Nodejs中文電子書

qs = require(rsquoquerystringrsquo)

if (action === rdquoSignuprdquo)

formData = rsquorsquo

reqon(rdquodatardquo function (data)

formData += data

)

reqon(rdquoendrdquo function ()

var msg

user = qsparse(formData)

userid = rdquo123456rdquo

msg = JSONstringify(user)

reswriteHead(200 rdquoContent-Typerdquordquoapplicationjson charset=utf-8rdquordquoContent-Lengthrdquomsglength)

resend(msg)

)

這裡的程式和前面 POST範例基本上大同小異差別在

bull 幫 user的資料加上 id隨意存放一些文字進去讓 Server回傳的資料多於 Client端傳上來的不然會覺得 Server都沒做事

bull 增加了 msg 這個變數存放將 user 物件 JSON 文字化的結果JSONstringify這個轉換函式是 V8引擎所提供的如果你好奇的話

bull 大重點來了我們要告訴 Client端這次回傳的資料格式是 JSON所在 Content-type和 Content-Length要提供給 Client

Server很輕鬆就完成任務了最後進行程式測試啟動 nodejs主程式之後開啟瀏覽器就會看到表單填寫完畢按下送出就可以看到結果了

最後 nodejs本篇範例程式碼如下

var http = require(rsquohttprsquo)

url = require(rsquourlrsquo)

fs = require(rdquofsrdquo)

qs = require(rsquoquerystringrsquo)

server

68 Express AJAX應用範例 65

Nodejs中文電子書

server = httpcreateServer(function (reqres)

var urlData

encode = rdquoutf8rdquo

filePath = rdquoviewexpress_ajax_example_formhtmlrdquo

formData

action

urlData = urlparse(requrltrue)

action = urlDatapathname

if (action === rdquoSignuprdquo)

formData = rsquorsquo

reqon(rdquodatardquo function (data)

formData += data

)

reqon(rdquoendrdquo function ()

var msg

user = qsparse(formData)

userid = rdquo123456rdquo

msg = JSONstringify(user)

reswriteHead(200 rdquoContent-TyperdquordquoapplicationjsonrdquordquoContent-Lengthrdquomsglength)

resend(msg)

)

else

fsreadFile(filePath encode function(err file)

reswriteHead(200 rdquoContent-Typerdquordquotexthtml charset=utf-8rdquo)

reswrite(file)

resend()

)

)

serverlisten(3000)

66 6 Express介紹

Nodejs中文電子書

consolelog(rsquoServer 跑起來了現在時間是rsquo + new Date())

69 原始資料提供

bull [NodeJS初學者筆記 (1)-用 GET傳送資料] (httpithelpithomecomtwquestion10087402)

bull [NodeJS初學者筆記 (2)-用 POST傳送資料] (httpithelpithomecomtwquestion10087489)

bull [NodeJS 初學者筆記 (3)-用 Ajax 傳送資料] (httpithelpithomecomtwquestion10087627)

69 原始資料提供 67

Nodejs中文電子書

68 6 Express介紹

7

CoffeeScript

bull Spine

bull Hem

bull Spineapp

Letrsquos Have a Cup of CoffeeScript

bull es5-shim ECMAScript 5 compatibility shims for legacy JavaScript engines

ESnext

node-iform - A middleware for node-validator httpsgithubcomguileennode-iform

69

Nodejs中文電子書

70 7 CoffeeScript

8

製作一個 Hubot的 Plurk Adapter

81 應用事項提醒

此應用範例以CoffeeScript編寫主要程式架構採用Github釋放的Hubot專案以下有幾個主要注意事項請讀者注意

bull Hubot 一開始的架構除了內建的兩個 Adapter 之外其餘都要以Nodejs的Module方式才能運作

bull Hubot的 binhubot裡面寫著 npm install所以不管你怎麼改原始碼也不能改變第一點的狀況

其實上述都在討論同一件事情NodeJS的模組而且模組不能設定相對路徑之類的來安裝一定要包裝成 npm相符和格式才有辦法正確執行

82 建立 Adapter

首先需要準備兩個檔案

bull packagejson

bull plurkcoffee

71

Nodejs中文電子書

有這兩個就足以變成 NodeJS的模組了在開始 Coding之前先來設定一下 packagejson相依性

rdquonamerdquo rdquohubot-plurkrdquo

rdquoversionrdquo rdquo011rdquo

rdquomainrdquo rdquoplurkrdquo

rdquodependenciesrdquo

rdquohubotrdquo rdquogt=205rdquo

rdquooauthrdquo rdquordquo

rdquocronrdquordquordquo

這邊會使用到NodeJS的 cron模組(Module)而登入 Plurk需要OAuth標準授權才行所以也需要加載 OAuth模組

注意Hubot在讀取非內建模組時會自動在前面加上 hubot-的前置

83 建立 Robot跟 API

本篇應用基本上程式碼大部分參考 Hubot - Twitter Adapter來製作主要差異只有在使用 OAuth這個模組Twitter有 Streaming可用而 Plurk則得用 Comet方式來達到即時讀取

plurkcoffee程式碼

Robot = require(rdquohubotrdquo)robot()

Adapter = require(rdquohubtordquo)adapter()

EventEmitter = require(rdquoeventsrdquo)EventEmitter

oauth = require(rdquooauthrdquo)

cronJob = require(rdquocronrdquo)CronJob

class Plurk exntends Adapter

class PlurkStreaming exnteds EventEmitter

72 8 製作一個 Hubot的 Plurk Adapter

Nodejs中文電子書

先弄個基本架構主要 Class皆參考 Twitter Adapter命名方式接著再將 Plurk這個 Class增加幾個Method基本上只要有 run send reply就夠了而 run用來做初始化的部分

class Plurk entends Adapter

send (plurk id strings⋯) -gt

reply (plurk id strings⋯) -gt

run -gt

看起來有點東西接著來處理主要的 Plurk API結合部分

class PlurkStreaming extends EventEmitter

consuctor (options) -gt

plurk (callback) -gt

觀察河道

getChannel -gt

取得 Comet 網址

reply (plurk id message) -gt

回噗

acceptFriends -gt

接受好友

get (path callback) -gt

GET 請求

post (path body callback)-gt

POST 請求(其實是裝飾)

request (method path body callback)-gt

主要的 OAuth 請求

comet (server callback)-gt

噗浪的 Comet 傳回是 JavaScript Callback 要另外處理後才會變成 JSON

然後把注意力集中到 constructor上先把建構子弄好

constructor (options) -gt

super()

if optionskey and optionssecret and optionstoken and optionstoken secret

key = optionskey

secret = optionssecret

token = optionstoken

token secret = optionstoken secret

83 建立 Robot跟 API 73

Nodejs中文電子書

建立 OAuth 連接

consumer = new oauthOAuth(

rdquohttpwwwplurkcomOAuthrequest tokenrdquo

rdquohttpwwwplurkcomOAuthaccess tokenrdquo

key

secret

rdquo10rdquo

rdquohttpwwwplurkcomOAuthauthorizerdquo

rdquoHMAC-SHA1rdquo

)

domain = rdquowwwplurkcomrdquo

初始化取得 Comet 網址

do getChannel

else

throw new Error(rdquo 參數不足需要 Key Secret Token Token Secretrdquo)

接著來處理 request這個 method

request (method path body callback) -gt

記錄一下這次的 Request

consolelog(rdquohttpdomainpathrdquo)

Callback 這邊先不丟進去要用另一種方式處理

request = consumerget(rdquohttpdomainpathrdquo token token secret null)

requeston rdquoresponserdquo (res) -gt

reson rdquodatardquo (chunk) -gt

parseResponse(chunk+rsquorsquo callback)

reson rdquoendrdquo (data) -gt

consolelog rdquoEnd Request pathrdquo

reson rdquoerrorrdquo (data) -gt

consolelog rdquoError rdquo + data

requestend()

處理資料

parseResponse = (data callback) -gt

if datalength gt 0

用 TryCatch 避免處理 JSON 出錯導致整個中斷

try

callback null JSONparse(data)

catch err

74 8 製作一個 Hubot的 Plurk Adapter

Nodejs中文電子書

consolelog(rdquoError Parse JSONrdquo + data err)

繼續執行

callback null data ||

大致上就是這樣上面程式的架構已經將整個 Hubot Plurk Adapter完成因為在測試時竟然因為噗浪 Lag而沒讀到完整的 Comet資料然後造成程式異常為了避免這個問題發生因此需要加上為了完美呈現需要再

加上 Comet的處理所以要使用到 EventEmitter的功能

comet (server callback) -gt

在 Callback 裡面會找不到自身所以設定區域變數

self =

記錄一下這次的 Request

consolelog(rdquo[Comet] serverrdquo)

Callback 這邊先不丟進去要用另一種方式處理

request = consumerget(rdquohttpdomainpathrdquo token token secret null)

requeston rdquoresponserdquo (res) -gt

reson rdquodatardquo (chunk) -gt

parseResponse(chunk+rsquorsquo callback)

reson rdquoendrdquo (data) -gt

consolelog rdquoEnd Request pathrdquo

請求結束發出事件通知可以進行下一次請求

selfemit rdquonextPlurkrdquo

reson rdquoerrorrdquo (data) -gt

consolelog rdquoError rdquo + data

requestend()

處理資料

parseResponse = (data callback) -gt

if datalength gt 0

用 trycatch 避免失敗中斷

try

去掉 JavaScript 的 Callback

data = datamatch(CometChannelscriptCallback((+))s)

jsonData = rdquordquo

if data

83 建立 Robot跟 API 75

Nodejs中文電子書

jsonData = JSONparse(data[1])

else

如果沒有任何 Match 嘗試直接 parse

jsonData = JSONparse(data)

catch err

consolelog(rdquo[Comet] Errorrdquo data err)

用 TryCatch 避免處理 JSON 出錯導致整個中斷

try

只傳入 json 的 data 部分

callback null jsonDatadata

catch err

consolelog(rdquo[Comet]Error Parse JSONrdquo + data err)

繼續執行

callback null data ||

後面的 get跟 post就簡單多了

get (path callback) -gt

request(rdquoGETrdquo path null callback)

post (path body callback) -gt

request(rdquoPOSTrdquo path body callback)

接著處理取的 Comet網址的 getChannel

getChannel -gt

self =

get rdquoAPPRealtimegetUserChannelrdquo (error data) -gt

if error

檢查是否有 comet server

if datacomet server

selfchannel = datacomet server

如果沒有 Channel Ready 就嘗試連接會失敗

selfemit(rsquochannel readyrsquo)

那麼先來處理 Plurk Adaper好處理的部份

send (plruk id strings⋯)-gt

跟 Reply 一樣直接交給 reply 做

reply plurk id strings⋯

76 8 製作一個 Hubot的 Plurk Adapter

Nodejs中文電子書

reply (plurk id strings⋯) -gt

stringsforEach (message) =gt

botreply(plruk id message)

接著把 run處理好就可以上線運作摟

run -gt

self =

options =

key processenvHUBOT PLURK KEY

secret processenvHUBOT PLURK SECRET

token processenvHUBOT PLURK TOKEN

token secret processenvHUBOT PLURK TOKEN SECRET

創建剛剛的 API

bot = new PlurkStreaming(options)

依照 Twitter 的 new RobotTextMessage 會沒有反應所以參考 hubot-minecraft 的方式

r = robotconstructor

處理噗浪河道訊息

doPlurk = (data)-gt

檢查是否為回噗

if dataresponse

datacontent raw = dataresponsecontent raw

datauser id = dataresponseuser id

確定有噗浪 ID 跟訊息

if dataplurk id and datacontent raw

selfreceive new rTextMessage(dataplurk id datacontent raw)

取得 Comet Server 完成開始第一次 Comet 連接

boton rdquochannel readyrdquo () -gt

botplurk selfdoPlurk

上一次 Comet 完成繼續 Polling

boton rdquonextPlurkrdquo ()-gt

botplurk selfdoPlurk

定時接受好友邀請

do botacceptFriends

bot = bot

83 建立 Robot跟 API 77

Nodejs中文電子書

終於完成 AdapterHubot專案裡面的 scripts資料夾內是互動部分不需要像 Adapter如此大費周章處理只新增檔案並且設計好對白之後就會回噗了我開發用的機器人在此大家可以去跟他玩玩[httpplurkcomelct9620 bot](httpplurkcomelct9620 bot)

84 原始資料提供

bull [製 作 一 個 Hubot 的 噗 浪 Adapter](http revo-skillfrosttw blog20120318create-a-hubot-plurk-adapter)

78 8 製作一個 Hubot的 Plurk Adapter

9

Nodejs好用工具介紹

91 logio

Real-time log monitoring in your browser

httplogioorg

nodejs (gt=0412 lt=0611)

npm config set unsafe-perm true

npm install -g --prefix=usrlocal logio

logio server start

httplocalhost8998

79

Nodejs中文電子書

80 9 Nodejs好用工具介紹

10

精選文章

101 Express

bull 使用 NodeJS + Express從 GETPOST Request取值 [邀稿中]

bull link2 [邀稿中]

bull link3 [邀稿中]

81

Nodejs中文電子書

82 10 精選文章

11

參考資源

111 Nodejs書籍

bull Programming Nodejs April 2012 (est) Travis Swicegood Pragmatic Bookshelf

bull e Little Book on CoffeeScript January 2012 Alex MacCaw OrsquoReilly Media

bull Node for Front-End Developers January 2012 Garann Means OrsquoReilly Media

bull Building Hypermedia APIs with HTML5 and Node November 2011 MikeAmundsen OrsquoReilly Media

bull Node Web Development August 2011 David Herron PacktPub

bull CoffeeScript Accelerated JavaScript Development July 2011 Trevor BurnhamPragmatic Bookshelf

bull Getting Started with GEO CouchDB and Nodejs July 2011 Mick omp-son OrsquoReilly Media

bull What Is Node July 2011 Brett McLaughlin OrsquoReilly Media

bull Node Up and Running May 2011 Tom Hughes-Croucher Mike WilsonOrsquoReilly Media

83

Nodejs中文電子書

bull Hands-on Nodejs e Nodejs introduction and API reference May 2011 Pe-dro Teixeira LeanPub

bull e Node Beginner Book Manuel Kiessling

bull Mastering Nodejs visionmedia

112 Nodejs影音教學

bull e Node Sessions e Best of OSCON 2011 August 2011 OrsquoReilly Media(Video)

bull Tom Hughes-Croucher on Node March 2011 OrsquoReilly Media (Video)

113 Nodejs教學網站

bull Node Tuts

114 Nodejs課程

bull Introduction To Nodejs Van Nguyen CodeLesson

84 11 參考資源

Page 32: Nodejs Wiki

Nodejs中文電子書

sudo apt-get update

sudo apt-get install nodejs

32 Other Linux

Linux很適合作為 NodeJS的伺服器作業系統及開發環境安裝前請先確認以下套件已正確安裝

bull curl (wget)用來下載檔案的工具

bull git先進的版本控制工具

bull g++ GNU C++軟體編譯工具

bull make GNU軟體專案建置工具

安裝指令如下如設有權限問題請在指令前面加上 sudo

git clone httpsgithubcomjoyentnodegit

cd node

git checkout v067

configure

make

sudo make install

接著測試 nodeJS是否正常執行

node --version

出現版本訊息即表示安裝成功

33 Windows

nodeJS 在 v060 版本之後開始正式支援 windows native直接使用nodeexe 就可以執行程式支援性完全與 linux 相同更棒的部份就是不需經過編譯經過下載之後簡單設定完成立即開發 node程式

26 3 Nodejs安裝與設定

Nodejs中文電子書

下載 nodejs安裝檔案 lthttpnodejsorgdownloadgt

如此完成 windows native nodeexe安裝接著可以進入 command line執行測試在 command line輸入rdquonoderdquo會出現 nodejs版本訊息畫面表示安裝完成

33 Windows 27

Nodejs中文電子書

28 3 Nodejs安裝與設定

4

Nodejs基礎

前篇文章已經由介紹安裝至設定都有完整介紹nodeJS 內部除了javascript常用的函式 (function)物件 (object)之外也有許多不同的自訂物件nodeJS預設建立這些物件為核心物件是為了要讓開發流程更為這些資料在官方文件已經具有許多具體說明接下來將會介紹在開發 nodeJS程式時常見的物件特性與使用方法

41 nodejs http伺服器建立

在 lsquonodejs官方網站 lthttpnodejsorggtlsquo裡面有舉一個最簡單的 HTTP伺服器建立一開始初步就是建立一個伺服器平台讓 nodejs可以與瀏覽器互相行為每種語言一開始的程式建立都是以 Hello world開始最初也從 Hello world帶各位進入 nodejs的世界

輸入以下程式碼儲存檔案為 node basic http hello worldjs

var server

ip = rdquo127001rdquo

port = 1337

http = require(rsquohttprsquo)

server = httpcreateServer(function (req res)

29

Nodejs中文電子書

reswriteHead(200 rsquoContent-Typersquo rsquotextplainrsquo)

resend(rsquoHello Worldnrsquo)

)

serverlisten(port ip)

consolelog(rdquoServer running at httprdquo + ip + rdquordquo + port)

程式碼解講一開始需要有幾個基本的變數

bull ip 機器本身的 ip位置因為使用本地端因此設定為 127001

bull port 需要開通的阜號通常設定為 http port 80因範例不希望與基本 port相衝隨意設定為 1337

在 nodejs 的程式中有許多預設的模組可以使用因此需要使用require方法將模組引入在這邊我們需要使用 http這個模組因此將 http載入Http模組裡面內建有許多方法可以使用這邊採用 createServer創建一個基本的 http伺服器再將 http伺服器給予一個 server變數裡面的回呼函式 (call back function)可以載入 http伺服器的資料與回應方法 (requestresponse)在程式裡面就可以看到我們直接回應給瀏覽器端所需的 Header回應內容

reswriteHead(200 rsquoContent-Typersquo rsquotextplainrsquo)

resend(rsquoHello Worldnrsquo)

Http伺服器需要設定 port ip在最後需要設定 Http監聽需要使用到listen事件監聽所有 Http伺服器行為

httplisten(port ip)

所有事情都完成之後需要確認伺服器正確執行因此使用 console在javascript裡就有這個原生物件console所印出的資料都會顯示於 nodejs伺服器頁面這邊印出的資料並不會傳送到使用者頁面上之後許多除壞

(debug)都會用到 console物件

consolelog(rdquoServer running at httprdquo + ip + rdquordquo + port)

30 4 Nodejs基礎

Nodejs中文電子書

42 nodejs http路徑建立

前面已經介紹如何建立一個簡單的 http伺服器接下來本章節將會介紹如何處理伺服器路徑 (rout)問題在 http的協定下所有從瀏覽器發出的要求 (request)都需要經過處理路徑上的建立也是如此

路徑就是指伺服器 ip位置或者是網域名稱之後對於伺服器給予的要求修改剛才的 hello world檔案修改如下

server = httpcreateServer(function (req res)

consolelog(requrl)

reswriteHead(200 rsquoContent-Typersquo rsquotextplainrsquo)

resend(rsquohello worldnrsquo)

)

重新啟動 nodejs程式後在瀏覽器端測試一下路徑行為結果如下圖

當在瀏覽器輸入 http1270011337test 在伺服器端會收到兩個要求一個是我們輸入的test要求另外一個則是faviconicotest的路徑要求http伺服器本身需要經過程式設定才有辦法回應給瀏覽器端所需要的回應在伺服器中所有的路徑要求都是需要被解析才有辦法取得資料從

42 nodejs http路徑建立 31

Nodejs中文電子書

上面解說可以了解到在 nodejs當中所有的路徑都需要經過設定未經過設定的路由會讓瀏覽器無法取得任何資料導致錯誤頁面的發生底下將會解

說如何設定路由同時避免發生錯誤情形先前 nodejs程式需要增加一些修改才能讓使用者透過瀏覽器在不同路徑時有不同的結果根據剛才

的程式做如下的修改

var server

ip = rdquo127001rdquo

port = 1337

http = require(rsquohttprsquo)

url = require(rsquourlrsquo)

path

server = httpcreateServer(function (req res)

path = urlparse(requrl)

reswriteHead(200 rsquoContent-Typersquo rsquotextplainrsquo)

switch (pathpathname)

case rdquoindexrdquo

resend(rsquoI am indexnrsquo)

break

case rdquotestrdquo

resend(rsquothis is test pagenrsquo)

break

default

resend(rsquodefault pagenrsquo)

break

)

serverlisten(port ip)

consolelog(rdquoServer running at httprdquo + ip + rdquordquo + port)

程式做了片段的修改首先載入 url模組另外增加一個 path變數url模組就跟如同他的命名一般專門處理 url字串處理裡面提供了許多方法來解決路徑上的問題因為從瀏覽器發出的要求路徑可能會帶有多種需求

或者 GET參數組合等因此我們需要將路徑單純化取用路徑部分的資料即可例如使用者可能會送出 http1270011337testsend=1如果直接

32 4 Nodejs基礎

Nodejs中文電子書

信任 requrl就會收到結果為testsend=1所以需要透過 url模組的方法將路徑資料過濾

在這邊使用 urlparse的方法裡面帶入網址格式資料會回傳路徑資料為了後需方便使用將回傳的資料設定到 path變數當中在回傳的路徑資料裡面包含資訊如下圖

這邊只需要使用單純的路徑要求直接取用 pathpathname就可以達到我們的目的

最後要做路徑的判別在不同的路徑可以指定不同的輸出在範例中

有三個可能結果第一個從瀏覽器輸入index就會顯示 index結果test 就會呈現出 test頁面最後如果都不符合預期的輸入會直接顯示 default的頁面最後的預防可以讓瀏覽器不會出現非預期結果讓程式的可靠性提昇

底下為測試結果

42 nodejs http路徑建立 33

Nodejs中文電子書

43 nodejs檔案讀取

前面已經介紹如何使用路由(rount)做出不同的回應實際應用只有在瀏覽器只有輸出幾個文字資料總是不夠的在本章節中將介紹如何使

用檔案讀取輸出檔案資料讓使用者在前端瀏覽器也可以讀取到完整的

html css javascript檔案輸出

檔案管理最重要的部分就是 lsquoFile system lthttpnodejsorgdocslatestapifshtmlgtlsquo這個模組此模組可以針對檔案做管理監控讀取等行為裡面有許多預設的方法底下是檔案輸出的基本範例底下會有兩個檔案

第一個是靜態 html檔案

ltDOCTYPE htmlgt

lthtml xmlns=rdquohttpwwww3org1999xhtmlrdquo xmllang=rdquozh-CNrdquo lang=rdquozh-CNrdquogt

ltheadgt

ltmeta http-equiv=rdquoContent-Typerdquo content=rdquotexthtml charset=UTF-7rdquo gt

lttitlegtnodejs index html filelttitlegt

ltheadgt

ltbodygt

34 4 Nodejs基礎

Nodejs中文電子書

lth1gtnodejs index html filelth1gt

ltbodygt

lthtmlgt

另一個為 nodejs程式

var fs = require(rdquofsrdquo)

filename = rdquostaticindexhtmlrdquo

encode = rdquoutf8rdquo

fsreadFile(filename encode function(err file)

consolelog(file)

)

一開始直接載入 le system模組 載入名稱為 fs讀取檔案主要使

用的方法為 readFile裡面以三個參數路徑 ( le path) 編碼方式 (encoding)

回應函式 (callback)路徑必須要設定為靜態 html所在位置才能指定到正確的檔案靜態檔案的編碼方式也必須正確這邊使用靜態檔案的編

碼為 utf8如果編碼設定錯誤nodejs讀取出來檔案結果會使用 byte raw格式輸出如果錯誤編碼格式會導致輸出資料為 byte raw

回應函式中裡面會使用兩個變數error為錯誤資訊如果讀取的檔案不存在或者發生錯誤error數值會是 true如果成功讀取資料 error將會是 falsecontent則是檔案內容資料讀取後將會把資料全數丟到 content這個變數當中

最後程式的輸出結果畫面如下

43 nodejs檔案讀取 35

Nodejs中文電子書

44 nodejs http靜態檔案輸出

前面已經了解如何讀取本地端檔案接下來將配合 http伺服器路由讓每個路由都能夠輸出相對應的靜態 html檔案首先新增加幾個靜態 html檔案

ltDOCTYPE htmlgt

lthtml xmlns=rdquohttpwwww3org1999xhtmlrdquo xmllang=rdquozh-CNrdquo lang=rdquozh-CNrdquogt

ltheadgt

ltmeta http-equiv=rdquoContent-Typerdquo content=rdquotexthtml charset=UTF-7rdquo gt

lttitlegtnodejs index html filelttitlegt

ltheadgt

ltbodygt

lth1gtnodejs index html filelth1gt

ltbodygt

lthtmlgt

ltDOCTYPE htmlgt

lthtml xmlns=rdquohttpwwww3org1999xhtmlrdquo xmllang=rdquozh-CNrdquo lang=rdquozh-CNrdquogt

ltheadgt

ltmeta http-equiv=rdquoContent-Typerdquo content=rdquotexthtml charset=UTF-7rdquo gt

lttitlegtnodejs test html filelttitlegt

ltheadgt

ltbodygt

lth1gtnodejs test html filelth1gt

ltbodygt

lthtmlgt

ltDOCTYPE htmlgt

lthtml xmlns=rdquohttpwwww3org1999xhtmlrdquo xmllang=rdquozh-CNrdquo lang=rdquozh-CNrdquogt

ltheadgt

ltmeta http-equiv=rdquoContent-Typerdquo content=rdquotexthtml charset=UTF-7rdquo gt

lttitlegtnodejs static html filelttitlegt

ltheadgt

ltbodygt

lth1gtnodejs static html filelth1gt

ltbodygt

lthtmlgt

36 4 Nodejs基礎

Nodejs中文電子書

準備一個包含基本路由功能的 http伺服器

var server

ip = rdquo127001rdquo

port = 1337

http = require(rsquohttprsquo)

url = require(rsquourlrsquo)

server = httpcreateServer(function (req res)

var path = urlparse(requrl)

)

serverlisten(port ip)

consolelog(rdquoServer running at httprdquo + ip + rdquordquo + port)

加入 le system 模組使用 readFile 的功能將這一段程式放置於

createServer的回應函式中

fsreadFile(filePath encode function(err file)

)

readFile的回應函式裡面加入頁面輸出讓瀏覽器可以正確讀到檔案在這邊我們設定讀取的檔案為 html 靜態檔案所以 Content-type 設定為texthtml讀取到檔案的內容將會正確輸出成 html靜態檔案

fsreadFile(filePath encode function(err file)

reswriteHead(200 rsquoContent-Typersquo rsquotexthtmlrsquo)

reswrite(file)

resend()

)

到這邊為止基本的程式內容都已經完成剩下一些細節的調整首先

路徑上必須做調整目前的靜態檔案全部都放置於 static資料夾底下設定一個變數來記住資料夾位置

接著將瀏覽器發出要求路徑與資料夾組合讀取正確 html靜態檔案使用者有可能會輸入錯誤路徑所以在讀取檔案的時候要加入錯誤處理

同時回應 404伺服器無法正確回應的 http header格式

加入這些細節的修改一個基本的 http 靜態 html輸出伺服器就完成了完整程式碼如下

44 nodejs http靜態檔案輸出 37

Nodejs中文電子書

var server

ip = rdquo127001rdquo

port = 1337

http = require(rsquohttprsquo)

fs = require(rdquofsrdquo)

folderPath = rdquostaticrdquo

url = require(rsquourlrsquo)

path

filePath

encode = rdquoutf8rdquo

server = httpcreateServer(function (req res)

path = urlparse(requrl)

filePath = folderPath + pathpathname

fsreadFile(filePath encode function(err file)

if (err)

reswriteHead(404 rsquoContent-Typersquo rsquotextplainrsquo)

resend()

return

reswriteHead(200 rsquoContent-Typersquo rsquotextapplicationrsquo)

reswrite(file)

resend()

)

)

serverlisten(port ip)

consolelog(rdquoServer running at httprdquo + ip + rdquordquo + port)

45 nodejs http GET資料擷取

http伺服器中除了路由之外另一個最常使用的方法就是擷取 GET資料本單元將會介紹如何透過基本 http伺服器擷取瀏覽器傳來的要求擷取 GET資料

在 http協定中GET參數都是藉由 URL從瀏覽器發出要求送至伺服器

38 4 Nodejs基礎

Nodejs中文電子書

端基本的傳送網址格式可能如下

http127001testsend=1amptest=2

上面這段網址裡面的 GET參數就是 send而這個變數的數值就為 1如果想要在 http伺服器取得 GET資料需要在瀏覽器給予的要求 (request)做處理

首先需要載入 query string這個模組這個模組主要是用來將字串資料

過濾後轉換成javascript物件

qs = require(rsquoquerystringrsquo)

接著在第一階段利用 url模組過濾瀏覽器發出的 URL資料後將回應的物件裡面的 query這個變數是一個字串值資料過濾後如下

send=1amptest=2

透過 query string使用 parse這個方法將資料轉換成 javascript物件就表示 GET的資料已經被伺服器端正式擷取下來

path = urlparse(requrl)

parameter = qsparse(pathquery)

整個 nodejs http GET參數完整擷取程式碼如下

var server

ip = rdquo127001rdquo

port = 1337

http = require(rsquohttprsquo)

qs = require(rsquoquerystringrsquo)

url = require(rsquourlrsquo)

server = httpcreateServer(function (req res)

var path = urlparse(requrl)

parameter = qsparse(pathquery)

consoledir(parameter)

reswriteHead(200 rsquoContent-Typersquo rsquotextplainrsquo)

reswrite(rsquoBrowser test GET parameternrsquo)

resend()

)

45 nodejs http GET資料擷取 39

Nodejs中文電子書

serverlisten(port ip)

consolelog(rdquoServer running at httprdquo + ip + rdquordquo + port)

程式運作之後由瀏覽器輸入要求網址之後nodejs伺服器端回應資料為

Server running at http1270011337

send rsquo1rsquo test rsquo1rsquo

46 本章結語

前面所解說的部份一大部分主要是處理 http伺服器基本問題雖然在某些部分有牽扯到 http 伺服器基本運作原理主要還是希望可以藉由這些基本範例練習 nodejs練習回應函式與語法串接的特點習慣編寫javascript風格程式當然直接這樣開發 nodejs是非常辛苦的接下來在模組實戰開發的部份將會介紹特定的模組一步一步帶領各位從無到有進行

nodejs應用程式開發

40 4 Nodejs基礎

5

NPM套件管理工具

npm全名為 Node Package Manager是 Nodejs的套件(package)管理工具類似 Perl的 ppm或 PHP的 PEAR等安裝 npm後使用npm install

module name指令即可安裝新套件維護管理套件的工作會更加輕鬆

npm可以讓Nodejs的開發者直接利用擴充線上的套件庫(packagesregistry)加速軟體專案的開發npm提供很友善的搜尋功能可以快速找到安裝需要的套件當這些套件發行新版本時npm也可以協助開發者自動更新這些套件

npm不僅可用於安裝新的套件它也支援搜尋列出已安裝模組及更新的功能

51 安裝 NPM

Nodejs在 063版本開始內建 npm讀者安裝的版本若是此版本或更新的版本就可以略過以下安裝說明

若要檢查 npm是否正確安裝可以使用以下的指令

npm -v

41

Nodejs中文電子書

執行結果說明

若 npm正確安裝執行 npm -v將會看到類似 110-2的版本訊息

若讀者安裝的 Nodejs版本比較舊或是有興趣嘗試自己動手安裝 npm工具則可以參考以下的說明

安裝於Windows系統

Nodejs for Windows 於 062 版開始內建 npm使用 nodejsorg 官方提供的安裝程式不需要進一步的設定就可以立即使用 npm指令對於Windows的開發者來說大幅降低環境設定的問題與門檻

除了使用 Nodejs內建的 npm讀者也可以從 npm官方提供的以下網址

httpnpmjsorgdist

這是由 npm提供的 Fancy Windows Install版本請下載壓縮檔(例如npm-110-3zip)並將壓縮檔內容解壓縮至 Nodejs的安裝路徑(例如CProgram Filesnodejs)

解壓縮後在 Nodejs的安裝路徑下應該有以下的檔案及資料夾

bull npmcmd(檔案)

bull node modules(資料夾)

安裝於 Linux系統

Ubuntu Linux的使用者可以加入 NPM Unoffcial PPA這個 repository即可使用 apt-get完成 npm安裝

42 5 NPM套件管理工具

Nodejs中文電子書

Ubuntu Linux使用 apt-get安裝 npm

sudo apt-get install python-software-properties

sudo add-apt-repository ppagias-kay-leenpm

sudo apt-get update

sudo apt-get install npm

npm官方提供的安裝程式 installsh可以適用於大多數的 Linux系統使用這個安裝程式請先確認

1 系統已安裝 curl工具(請使用 curl --version查看版本訊息)

2 已安裝 Nodejs並且 PATH正確設置

3 Nodejs的版本必須大於 04x

以下為 npm提供的安裝指令

curl httpnpmjsorginstallsh | sh

安裝成功會看到如下訊息

installsh安裝成功的訊息

npm10105 homeUSERNAMElocalnodelibnode_modulesnpm

It worked

安裝於Mac OS X

建議採用與 Nodejs相同的方式進行 npm的安裝例如使用MacPorts安裝 Nodejs就同樣使用MacPorts安裝 npm這樣對日後的維護才會更方便容易

使用 MacPorts安裝 npm是本書比較建議的方式它可以讓 npm的安裝移除及更新工作自動化將會幫助開發者節省寶貴時間

51 安裝 NPM 43

Nodejs中文電子書

安裝MacPorts的提示

在MacPorts網站可以取得 OS X系統版本對應的安裝程式(例如 106或 107)httpwwwmacportsorg安裝過程會詢問系統管理者密碼使用預設的選項完成安裝即可安

裝MacPorts之後在終端機執行 port -v將會看到MacPorts的版本訊息

安裝 npm之前先更新MacPorts的套件清單以確保安裝的 npm是最新版本

sudo port -d selfupdate

接著安裝 npm

sudo port install npm

若讀者的 Nodejs並非使用MacPorts安裝則不建議使用MacPorts安裝npm因為 MacPorts會自動檢查並安裝相依套件而 npm相依 nodejs所以MacPorts也會一併將 nodejs套件安裝造成先前讀者使用其它方式安裝的 nodejs被覆蓋

讀者可以先使用 MacPorts安裝 curl(sudo port install curl)

再參考 Linux的 installsh安裝方式即可使用 npm官方提供的安裝程式

NPM安裝後測試

npm是指令列工具(command-line tool)使用時請先打開系統的文字終端機工具

測試 npm安裝與設定是否正確請輸入指令如下

npm -v

或是

npm --version

如果 npm已經正確安裝設定就會顯示版本訊息

44 5 NPM套件管理工具

Nodejs中文電子書

執行結果(範例)

110-2

52 使用 NPM安裝套件

npm目前擁有超過 6000種套件(packages)可以在 npm registry使用關鍵字搜尋套件

httpsearchnpmjsorg

舉例來說在關鍵字欄位輸入「coffee-script」下方的清單就會自動列出包含 coffee-script關鍵字的套件

接著我們回到終端機模式的操作npm的指令工具本身就可以完成套

件搜尋的任務

例如以下的指令同樣可以找出 coffee-script相關套件

npm search coffee-script

以下是搜尋結果的參考畫面

52 使用 NPM安裝套件 45

Nodejs中文電子書

找到需要的套件後(例如 express)即可使用以下指令安裝

npm install coffee-script

值得注意的一點是使用 npm install會將指定的套件安裝在工

作目錄(Working Directory)的 node modules資料夾下

以Windows為例如果執行 npm install的目錄位於

Cproject1

那麼 npm將會自動建立一個 node modules的子目錄(如果不存在)

Cproject1node modules

並且將下載的套件放置於這個子目錄例如

Cproject1node modulescoffee-script

這個設計讓專案可以個別管理相依的套件並且可以在專案佈署或發

行時將這些套件(位於 node modules)一併打包方便其它專案的使用者不必再重新下載套件

這個 npm install的預設安裝模式為 local(本地)只會變更當前專案的資料夾不會影響系統

另一種安裝模式稱為 global(全域)這種模式會將套件安裝到系統資

料夾也就是 npm安裝路徑的 node modules資料夾例如

CProgram Filesnodejsnode modules

46 5 NPM套件管理工具

Nodejs中文電子書

是否要使用全域安裝可以依照套件是否提供新指令來判斷舉例來說express 套件提供 express 這個指令而 coffee-script 則提供 coffee

指令

在 local 安 裝 模 式 中這 些 指 令 的 程 式 檔 案會 被 安 裝 到node modules的 bin這個隱藏資料夾下除非將bin的路徑加入 PATH環境變數否則要執行這些指令將會相當不便

為了方便指令的執行我們可以在 npm install 加上 -g 或 --

global參數啟用 global安裝模式例如

npm install -g coffee-script

npm install -g express

使用 global安裝模式需要注意執行權限的問題若權限不足可能會出現類似以下的錯誤訊息

npm ERR Error EACCES permission denied rsquorsquo

npm ERR

npm ERR Please try running this command again as rootAdministrator

要獲得足夠得執行權限請參考以下說明

bull Windows 7 或 2008 以上在「命令提示字元」的捷徑按右鍵選擇「以系統管理員身分執行」執行 npm指令時就會具有 Administrator身分

bull Mac OS X或 Linux系統可以使用 sudo指令例如

sudo npm install -g express

bull Linux系統可以使用 root權限登入或是以「sudo su -」切換成 root身分(使用 root權限操作系統相當危險因此並不建議使用這種方式)

若加上 -g參數使用 npm install -g coffee-script完成安裝

後就可以在終端機執行 coffee指令例如

coffee -v

52 使用 NPM安裝套件 47

Nodejs中文電子書

執行結果(範例)

CoffeeScript version 120

53 套件的更新及維護

除了前一節說明的 search 及 install 用法npm 還提供其他許多指令(commands)

使用 npm help可以查詢可用的指令

npm help

執行結果(部分)

where ltcommandgt is one of

adduser apihelp author bin bugs c cache completion

config deprecate docs edit explore faq find get

help help-search home i info init install la link

list ll ln login ls outdated owner pack prefix

prune publish r rb rebuild remove restart rm root

run-script s se search set show star start stop

submodule tag test un uninstall unlink unpublish

unstar up update version view whoami

使用 npm help command可以查詢指令的詳細用法例如

npm help list

接下來本節要介紹開發過程常用的 npm指令

使用 list可以列出已安裝套件

npm list

48 5 NPM套件管理工具

Nodejs中文電子書

執行結果(範例)

-- coffee-script120

- express256

- connect185

| -- formidable108

-- mime124

-- mkdirp007

-- qs041

檢視某個套件的詳細資訊例如

npm show express

升級所有套件(如果該套件已發佈更新版本)

npm update

升級指定的套件

npm update express

移除指定的套件

npm uninstall express

54 使用 packagejson

對於正式的 Nodejs專案可以建立一個命名為 packagejson的設

定檔(純文字格式)檔案內容參考範例如下

54 使用 packagejson 49

Nodejs中文電子書

packagejson(範例)

rdquonamerdquo rdquoapplication-namerdquo

rdquoversionrdquo rdquo001rdquo

rdquoprivaterdquo true

rdquodependenciesrdquo

rdquoexpressrdquo rdquo255rdquo

rdquocoffee-scriptrdquo rdquolatestrdquo

rdquomongooserdquo rdquogt= 253rdquo

其中 name與 version依照專案的需求設置

需要注意的是 dependencies的設定它用於指定專案相依的套件名

稱及版本

bull rdquoexpressrdquo rdquo255rdquo

代表此專案相依版本 255的 express套件

bull rdquocoffee-scriptrdquo rdquolatestrdquo

使用最新版的 coffee-script套件(每次更新都會檢查新版)

bull rdquomongooserdquo rdquogt= 253rdquo

使用版本大於 253的 mongoose套件

假設某個套件的新版可能造成專案無法正常運作就必須指定套件的

版本避免專案的程式碼來不及更新以相容新版套件通常在開發初期的

專案需要盡可能維持新套件的相容性(以取得套件的更新或修正)可以

用「gt=」設定最低相容的版本或是使用「latest」設定永遠保持最新

套件

50 5 NPM套件管理工具

6

Express介紹

在前面的 nodejs基礎當中介紹許多許多開設 http的使用方法及介紹以及許多基本的 nodejs基本應用

接下來要介紹一個套件稱為 express [Express](httpexpressjscom)這個套件主要幫忙解決許多 nodejs http server所需要的基本服務讓開發 httpservice變得更為容易不需要像之前需要透過層層模組(module)才有辦法開始編寫自己的程式

這個套件是由 TJ Holowaychuk 製作而成的套件裡面包含基本的路由處理 (route)http資料處理(GETPOSTPUT)另外還與樣板套件(jshtml template engine)搭配同時也可以處理許多複雜化的問題

61 Express安裝

安裝方式十分簡單只要透過之前介紹的 NPM就可以使用簡單的指令安裝指令如下

這邊建議需要將此套件安裝成為全域模組方便日後使用

51

Nodejs中文電子書

62 Express基本操作

express的使用也十分簡單先來建立一個基本的 hello world

var app = require(rsquoexpressrsquo)createServer()

port = 1337

applisten(port)

appget(rsquorsquo function(req res)

ressend(rsquohello worldrsquo)

)

consolelog(rsquostart express servernrsquo)

可以從上面的程式碼發現基本操作與 nodejs http的建立方式沒有太大差異主要差在當我們設定路由時可以直接透過 appget方式設定回應與接受方式

63 Express路由處理

Express對於 http服務上有許多包裝讓開發者使用及設定上更為方便例如有幾個路由設定那我們就統一藉由 appget來處理

Create http server

appget(rsquorsquo function(req res)

ressend(rsquohello worldrsquo)

)

appget(rsquotestrsquo function(req res)

ressend(rsquotest renderrsquo)

)

appget(rsquouserrsquo function(req res)

ressend(rsquouser pagersquo)

)

52 6 Express介紹

Nodejs中文電子書

如上面的程式碼所表示appget可以帶入兩個參數第一個是路徑名稱設定第二個為回應函式 (call back function)回應函式裡面就如同之前的 createServer方法裡面包含 requestresponse兩個物件可供使用使用者就可以透過瀏覽器輸入不同的 url切換到不同的頁面顯示不同的結果

路由設定上也有基本的配對方式讓使用者從瀏覽器輸入的網址可以

是一個變數只要符合型態就可以有對應的頁面產出例如

Create http server

appget(rsquouseridrsquo function(req res)

ressend(rsquouser rsquo + reqparamsid)

)

appget(rsquonumberrsquo function(req res)

ressend(rsquonumber rsquo + reqparamsnumber)

)

裡 面 使 用 到number 從 網 址 輸 入 之 後 就 可 以 直 接 使 用reqparamsnumber 取得所輸入的資料變成 url 參數使用當然前面也是可以加上路徑的設定userid在瀏覽器上路徑必須符合userxxx透

過 reqparamsid就可以取到 xxx這個字串值

另外express參數處理也提供了路由參數配對處理也可以透過正規表示法作為參數設定

var app = require(rsquoexpressrsquo)createServer()

port = 1337

applisten(port)

appget(^ip((d23)((d23))((d23))((d23))) function(req res)

ressend(reqparams)

)

上面程式碼可以發現後面路由設定的型態是正規表示法裡面設定

格式為ip之後必須要加上 ip型態才會符合資料格式同時取得 ip資料已經由正規表示法將資料做分群因此可以取得 ip的四個數字

此程式執行之後可以透過瀏覽器測試輸入網址為 localhost3000ip25525510010可以從頁面獲得資料

63 Express路由處理 53

Nodejs中文電子書

此章節全部範例程式碼如下

overview

author Caesar Chi

blog clonnblogspotcom

version 20120226

create server

var app = require(rsquoexpressrsquo)createServer()

port = 1337

applisten(port)

normal style

appget(rsquorsquo function(req res)

ressend(rsquohello worldrsquo)

)

appget(rsquotestrsquo function(req res)

ressend(rsquotest renderrsquo)

)

parameter style

appget(rsquouseridrsquo function(req res)

ressend(rsquouser rsquo + reqparamsid)

)

appget(rsquonumberrsquo function(req res)

ressend(rsquonumber rsquo + reqparamsnumber)

)

REGX style

appget(^ip((d23)((d23))((d23))) function(req res)

ressend(reqparams)

)

appget(rsquorsquo function(req res)

ressend(rsquoPage not foundrsquo 404)

54 6 Express介紹

Nodejs中文電子書

)

consolelog(rsquostart express servernrsquo)

64 Express middleware

Express 裡面有一個十分好用的應用概念稱為 middleware可以透過middleware做出複雜的效果同時上面也有介紹 next方法參數傳遞就是靠 middleware的概念來傳遞參數讓開發者可以明確的控制程式邏輯

create http server

appuse(expressbodyParser())

appuse(expressmethodOverride())

appuse(expresssession())

上面都是一種 middleware的使用方式透過 appuse方式裡面載入函式執行方法回應函式會包含三個基本參數responserequestnext其中next表示下一個 middleware執行函式同時會自動將預設三個參數繼續帶往下個函式執行底下有個實驗

上面的片段程式執行後開啟瀏覽器連結上 localhost1337會發現伺服器回應結果順序如下

first middle ware

second middle ware

execute middle ware

end middleware function

從上面的結果可以得知剛才設定的 middleware都生效了在 appuse設定的 middleware是所有 url皆會執行方法如果有指定特定方法就可以使用 appget的 middleware設定在 appget函式的第二個參數就可以帶入函式或者是匿名函式只要函式裡面最後會接受 request response next這三個參數同時也有正確指定 next函式的執行時機最後都會執行到最後一個方法當然開發者也可以評估程式邏輯要執行到哪一個階段讓邏輯

可以更為分明

64 Express middleware 55

Nodejs中文電子書

65 Express路由應用

在實際開發上可能會遇到需要使用參數等方式混和變數一起使用

express裡面提供了一個很棒的處理方法 appall這個方式可以先採用基本路由配對再將設定為每個不同的處理方式開發者可以透過這個方式簡

化自己的程式邏輯

overview

author

version 20120226

create server

var app = require(rsquoexpressrsquo)createServer()

port = 1337

users = [

name rsquoClonnrsquo

name rsquoChirsquo

]

applisten(port)

appall(rsquouseridoprsquo function(req res next)

requser = users[reqparamsid]

if (requser)

next()

else

next(new Error(rsquocannot find user rsquo + reqparamsid))

)

appget(rsquouseridrsquo function(req res)

ressend(rsquoviewing rsquo + requsername)

)

appget(rsquouserideditrsquo function(req res)

ressend(rsquoediting rsquo + requsername)

)

56 6 Express介紹

Nodejs中文電子書

appget(rsquouseriddeletersquo function(req res)

ressend(rsquodeleting rsquo + requsername)

)

appget(rsquorsquo function(req res)

ressend(rsquoPage not foundrsquo 404)

)

consolelog(rsquostart express servernrsquo)

內部宣告一組預設的使用者分別給予名稱設定藉由 appall這個方法可以先將路由雛形建立再接下來設定 appget的路徑格式只要符合格式就會分配進入對應的方法中像上面的程式當中如果使用者輸入路徑為user0除了執行 appall程式之後執行 next方法就會對應到路徑設定為userid的這個方法當中如果使用者輸入路徑為user0edit就會執行到useridedit的對應方法

66 Express GET應用範例

我們準備一個使用 GET方法傳送資料的表單

ltDOCTYPE htmlgt

lthtmlgt

ltheadgt

ltmeta http-equiv=rdquoContent-Typerdquo content=rdquotexthtml charset=utf8rdquogt

lttitlegtNodejs 菜鳥筆記 (1)lttitlegt

ltlink rel=rdquostylesheetrdquo href=rdquocssstylecssrdquo type=rdquotextcssrdquo media=rdquoallrdquo gt

ltheadgt

ltbodygt

lth1gtNodejs 菜鳥筆記-註冊lth1gt

ltform id=rdquosignuprdquo method=rdquoGETrdquo action=rdquohttplocalhost3000Signuprdquogt

ltlabelgt 使用者名稱ltlabelgtltinput type=rdquotextrdquo id=rdquousernamerdquo name=rdquousernamerdquo gtltbrgt

ltlabelgt 電子郵件ltlabelgtltinput type=rdquotextrdquo id=rdquoemailrdquo name=rdquoemailrdquo gtltbrgt

ltinput type=rdquosubmitrdquo value=rdquo 註冊我的帳號rdquo gtltbrgt

ltformgt

ltbodygt

lthtmlgt

66 Express GET應用範例 57

Nodejs中文電子書

這個表單沒有什麼特別的地方我們只需要看第 9 行form 使用的method是 GET然後 action是rdquohttplocalhost3000Signupldquo等一下我們要來撰寫Signup這個 URL Path的處理程式

處理 Signup行為

我們知道所謂的 GET方法會透過 URL來把表單的值給帶過去以上面的表單來說到時候 URL會以這樣的形式傳遞

httplocalhost3000Signupusername=xxxampemail=xxx

所以要能處理這樣的資料必須有以下功能

bull 解析 URL

bull 辨別動作是 Signup

bull 解析出 username和 email

一旦能取得 username和 email的值程式就能加以應用了

處理 Signup的程式碼雛形

load module

var url = require(rsquourlrsquo)

urlData = urlparse(requrltrue)

action = urlDatapathname

reswriteHead(200 rdquoContent-Typerdquordquotexthtml charset=utf-8rdquo)

if (action === rdquoSignuprdquo)

user = urlDataquery

resend(rdquolth1gtrdquo + userusername + rdquo 歡迎您的加入 lth1gtltpgt 我們已經將會員啟用信寄至rdquo + useremail + rdquoltpgtrdquo)

首先需要加載 url module它是用來協助我們解析 URL的模組接著使用 urlparse方法第一個傳入 url字串變數也就是 requrl另外第二個參數的用意是設為 ture則引進 querystring模組來協助處理預設是 false它影響到的是 urlDataquery設為 true會傳回物件不然就只是一般的字串urlparse會將字串內容整理成一個物件我們把它指定給 urlData

action變數作為記錄 pathname這是我們稍後要來判斷目前網頁的動作是什麼接著先將 html表頭資訊 (Header)準備好再來判斷路徑邏輯如

58 6 Express介紹

Nodejs中文電子書

果是 Signup這個動作就把 urlDataquery裡的資料指定給 user然後輸出userusername和 useremail把使用者從表單註冊的資料顯示於頁面中

最後進行程式測試啟動 nodejs主程式之後開啟瀏覽器就會看到表單填寫完畢按下送出就可以看到結果了

完整 nodejs程式碼如下

var http = require(rsquohttprsquo)

url = require(rsquourlrsquo)

fs = require(rdquofsrdquo)

server

server = httpcreateServer(function (reqres)

var urlData

encode = rdquoutf8rdquo

filePath = rdquoviewexpress_get_example_formhtmlrdquo

action

urlData = urlparse(requrltrue)

action = urlDatapathname

reswriteHead(200 rdquoContent-Typerdquordquotexthtml charset=utf-8rdquo)

if (action === rdquoSignuprdquo)

user = urlDataquery

resend(rdquolth1gtrdquo + userusername + rdquo 歡迎您的加入 lth1gtltpgt 我們已經將會員啟用信寄至rdquo + useremail + rdquoltpgtrdquo)

else

fsreadFile(filePath encode function(err file)

reswrite(file)

resend()

)

)

serverlisten(3000)

consolelog(rsquoServer 跑起來了現在時間是rsquo + new Date())

66 Express GET應用範例 59

Nodejs中文電子書

67 Express POST應用範例

一開始準備基本的 html表單傳送內容以 POST方式form的 action屬性設定為 POST其餘 html內容與前一個範例應用相同

ltDOCTYPE htmlgt

lthtmlgt

ltheadgt

ltmeta http-equiv=rdquoContent-Typerdquo content=rdquotexthtml charset=utf8rdquogt

lttitlegtNodejs 菜鳥筆記 (1)lttitlegt

ltlink rel=rdquostylesheetrdquo href=rdquocssstylecssrdquo type=rdquotextcssrdquo media=rdquoallrdquo gt

ltheadgt

ltbodygt

lth1gtNodejs 菜鳥筆記-註冊lth1gt

ltform id=rdquosignuprdquo method=rdquoPOSTrdquo action=rdquohttplocalhost3000Signuprdquogt

ltlabelgt 使用者名稱ltlabelgtltinput type=rdquotextrdquo id=rdquousernamerdquo name=rdquousernamerdquo gtltbrgt

ltlabelgt 電子郵件ltlabelgtltinput type=rdquotextrdquo id=rdquoemailrdquo name=rdquoemailrdquo gtltbrgt

ltinput type=rdquosubmitrdquo value=rdquo 註冊我的帳號rdquo gtltbrgt

ltformgt

ltbodygt

lthtmlgt

nodejs的程式處理邏輯與前面 GET範例類似部分程式碼如下

qs = require(rsquoquerystringrsquo)

if (action === rdquoSignuprdquo)

formData = rsquorsquo

reqon(rdquodatardquo function (data)

formData += data

)

reqon(rdquoendrdquo function ()

user = qsparse(formData)

resend(rdquolth1gtrdquo + userusername + rdquo 歡迎您的加入 lth1gtltpgt 我們已經將會員啟用信寄至rdquo + useremail + rdquoltpgtrdquo)

)

60 6 Express介紹

Nodejs中文電子書

主要加入了rsquoquerystringrsquo這個moduel方便我們等一下解析由表單 POST回來的資料另外加入一個 formData的變數用來搜集待等一下表單回傳的資料前面的 GET範例我們只從 req拿出 url的資料這次要在利用req身上的事件處理

JavaScript 在訂閱事件時使用 addEventListener而 nodejs 使用的則是on這邊加上了監聽 data的事件會在瀏覽器傳送資料到Web Server時被執行參數是它所接收到的資料型態是字串

接著再增加 end的事件當瀏覽器的請求事件結束時它就會動作

由於瀏覽器使用 POST在上傳資料時會將資料一塊塊地上傳因為我們在監聽 data事件時透過 formData變數將它累加起來lt不過由於我們

上傳的資料很少一次就結束不過如果日後需要傳的是資料比較大的檔

案這個累加動作就很重要

當資料傳完就進到 end 事件中會用到 qsparse 來解析 formDataformData的內容是字串內容是

username=wordsmithampemail=wordsmith40somewhere

而 qsparse可以幫我們把這個 querystring轉成物件的格式也就是

username=wordsmithampemail=wordsmith40somewhere

一旦轉成物件並指定給 user之後其他的事情就和 GET方法時操作的一樣寫 response的表頭將內容回傳並將 userusername和 useremail代入到內容中

修改完成後接著執行 nodejs程式啟動 web server開啟瀏覽器進入表單測試看看POST的方式能否順利運作

完整程式碼如下

var http = require(rsquohttprsquo)

url = require(rsquourlrsquo)

fs = require(rdquofsrdquo)

qs = require(rsquoquerystringrsquo)

server

server = httpcreateServer(function (reqres)

var urlData

67 Express POST應用範例 61

Nodejs中文電子書

encode = rdquoutf8rdquo

filePath = rdquoviewexpress_post_example_formhtmlrdquo

formData

action

urlData = urlparse(requrltrue)

action = urlDatapathname

reswriteHead(200 rdquoContent-Typerdquordquotexthtml charset=utf-8rdquo)

if (action === rdquoSignuprdquo)

formData = rsquorsquo

reqon(rdquodatardquo function (data)

formData += data

)

reqon(rdquoendrdquo function ()

user = qsparse(formData)

resend(rdquolth1gtrdquo + userusername + rdquo 歡迎您的加入 lth1gtltpgt 我們已經將會員啟用信寄至rdquo + useremail + rdquoltpgtrdquo)

)

else

fsreadFile(filePath encode function(err file)

reswrite(file)

resend()

)

)

serverlisten(3000)

consolelog(rsquoServer 跑起來了現在時間是rsquo + new Date())

62 6 Express介紹

Nodejs中文電子書

68 Express AJAX應用範例

在 Nodejs要使用 Ajax傳送資料並且與之互動在接受資料的部份沒有太大的差別client端不是用 GET就是用 POST來傳資料重點在處理完後用 JSON格式回傳當然 Ajax不見得只傳 JSON格式有時是回傳一段 HTML碼不過後者對伺服器來說基本上就和前兩篇沒有差別了所以我們還是以回傳 JSON做為這一回的主題

這一回其實大多數的工作都會落在前端 Ajax上面前端要負責發送與接收資料並在接收資料後撤掉原先發送資料的表單並將取得的資料

改成 HTML格式之後放上頁面

首先先準備 HTML靜態頁面資料

ltDOCTYPE htmlgt

lthtmlgt

ltheadgt

lttitlegtNodejs 菜鳥筆記 (3)lttitlegt

ltlink rel=rdquostylesheetrdquo href=rdquocssstylecssrdquo type=rdquotextcssrdquo media=rdquoallrdquo gt

ltscript src=rdquohttpsajaxgoogleapiscomajaxlibsjquery171jqueryminjsrdquogtltscriptgt

ltheadgt

ltbodygt

lth1gtNodejs 菜鳥筆記-註冊 (用 Ajax)lth1gt

ltform id=rdquosignuprdquo action=rdquoSignuprdquo method=rdquoPOSTrdquogt

ltlabelgt 使用者名稱ltlabelgtltinput type=rdquotextrdquo id=rdquousernamerdquo name=rdquousernamerdquo gtltbrgt

ltlabelgt 電子郵件ltlabelgtltinput type=rdquotextrdquo id=rdquoemailrdquo name=rdquoemailrdquo gtltbrgt

ltinput type=rdquosubmitrdquo value=rdquo 註冊我的帳號rdquo gtltbrgt

ltformgt

ltdiv id=rdquoresultrdquogt

ltdivgt

ltscript type=rdquotextjavascriptrdquogt

$(function()$(rsquosignup input[type=rdquosubmitrdquo]rsquo)click(function(e)

var user =

userusername = $(rdquousernamerdquo)val()useremail = $(rdquoemailrdquo)val()

$post(rdquoSignuprdquouserfunction(data)greet(data)

)

68 Express AJAX應用範例 63

Nodejs中文電子書

epreventDefault()

)

var greet = function(msg)

var resultNode = $(rsquoresultrsquo)greeting = $(rsquolth2gtHirsquo+msgusername+rsquo 你的會員 id 是rsquo+ msgid +rsquo 我們會將會員啟動信寄至rsquo+msgemail+rsquolth2gtrsquo)

$(rsquosignuprsquo)hide()resultNodehtml(rdquordquo)

resultNodeappend(greeting)

)

ltscriptgt

ltbodygt

lthtmlgt

HTML頁面上準備了一個表單用來傳送註冊資料接著直接引用了Google CDN來載入 jQuery用來幫我們處理 Ajax的工作這次要傳送和接收的工作很大的變動都在 HTML頁面上的 JavaScript當中我們要做的事有 (相關 jQuery處理這邊不多做贅述指提起主要功能解說)

bull 用 jQUery取得 submit按鈕綁定它的 click動作

bull 取得表單 username和 email的值存放在 user這個物件中

bull 用 jQuery的 $post方法將 user的資料傳到 Server

bull 一旦成功取得資料後透過 greet這個 function組成回報給 user的訊息

bull 清空原本給使用者填資料的表單

bull 將 Server回傳的 usernameemail和 id這 3個資料組成回應的訊息

bull 將訊息放到原本表單的位置

經過以上的處理後一個 Ajax的表單的基本功能已經完成

接著進行 nodejs主要程式的編輯部分程式碼如下

var fs = require(rdquofsrdquo)

64 6 Express介紹

Nodejs中文電子書

qs = require(rsquoquerystringrsquo)

if (action === rdquoSignuprdquo)

formData = rsquorsquo

reqon(rdquodatardquo function (data)

formData += data

)

reqon(rdquoendrdquo function ()

var msg

user = qsparse(formData)

userid = rdquo123456rdquo

msg = JSONstringify(user)

reswriteHead(200 rdquoContent-Typerdquordquoapplicationjson charset=utf-8rdquordquoContent-Lengthrdquomsglength)

resend(msg)

)

這裡的程式和前面 POST範例基本上大同小異差別在

bull 幫 user的資料加上 id隨意存放一些文字進去讓 Server回傳的資料多於 Client端傳上來的不然會覺得 Server都沒做事

bull 增加了 msg 這個變數存放將 user 物件 JSON 文字化的結果JSONstringify這個轉換函式是 V8引擎所提供的如果你好奇的話

bull 大重點來了我們要告訴 Client端這次回傳的資料格式是 JSON所在 Content-type和 Content-Length要提供給 Client

Server很輕鬆就完成任務了最後進行程式測試啟動 nodejs主程式之後開啟瀏覽器就會看到表單填寫完畢按下送出就可以看到結果了

最後 nodejs本篇範例程式碼如下

var http = require(rsquohttprsquo)

url = require(rsquourlrsquo)

fs = require(rdquofsrdquo)

qs = require(rsquoquerystringrsquo)

server

68 Express AJAX應用範例 65

Nodejs中文電子書

server = httpcreateServer(function (reqres)

var urlData

encode = rdquoutf8rdquo

filePath = rdquoviewexpress_ajax_example_formhtmlrdquo

formData

action

urlData = urlparse(requrltrue)

action = urlDatapathname

if (action === rdquoSignuprdquo)

formData = rsquorsquo

reqon(rdquodatardquo function (data)

formData += data

)

reqon(rdquoendrdquo function ()

var msg

user = qsparse(formData)

userid = rdquo123456rdquo

msg = JSONstringify(user)

reswriteHead(200 rdquoContent-TyperdquordquoapplicationjsonrdquordquoContent-Lengthrdquomsglength)

resend(msg)

)

else

fsreadFile(filePath encode function(err file)

reswriteHead(200 rdquoContent-Typerdquordquotexthtml charset=utf-8rdquo)

reswrite(file)

resend()

)

)

serverlisten(3000)

66 6 Express介紹

Nodejs中文電子書

consolelog(rsquoServer 跑起來了現在時間是rsquo + new Date())

69 原始資料提供

bull [NodeJS初學者筆記 (1)-用 GET傳送資料] (httpithelpithomecomtwquestion10087402)

bull [NodeJS初學者筆記 (2)-用 POST傳送資料] (httpithelpithomecomtwquestion10087489)

bull [NodeJS 初學者筆記 (3)-用 Ajax 傳送資料] (httpithelpithomecomtwquestion10087627)

69 原始資料提供 67

Nodejs中文電子書

68 6 Express介紹

7

CoffeeScript

bull Spine

bull Hem

bull Spineapp

Letrsquos Have a Cup of CoffeeScript

bull es5-shim ECMAScript 5 compatibility shims for legacy JavaScript engines

ESnext

node-iform - A middleware for node-validator httpsgithubcomguileennode-iform

69

Nodejs中文電子書

70 7 CoffeeScript

8

製作一個 Hubot的 Plurk Adapter

81 應用事項提醒

此應用範例以CoffeeScript編寫主要程式架構採用Github釋放的Hubot專案以下有幾個主要注意事項請讀者注意

bull Hubot 一開始的架構除了內建的兩個 Adapter 之外其餘都要以Nodejs的Module方式才能運作

bull Hubot的 binhubot裡面寫著 npm install所以不管你怎麼改原始碼也不能改變第一點的狀況

其實上述都在討論同一件事情NodeJS的模組而且模組不能設定相對路徑之類的來安裝一定要包裝成 npm相符和格式才有辦法正確執行

82 建立 Adapter

首先需要準備兩個檔案

bull packagejson

bull plurkcoffee

71

Nodejs中文電子書

有這兩個就足以變成 NodeJS的模組了在開始 Coding之前先來設定一下 packagejson相依性

rdquonamerdquo rdquohubot-plurkrdquo

rdquoversionrdquo rdquo011rdquo

rdquomainrdquo rdquoplurkrdquo

rdquodependenciesrdquo

rdquohubotrdquo rdquogt=205rdquo

rdquooauthrdquo rdquordquo

rdquocronrdquordquordquo

這邊會使用到NodeJS的 cron模組(Module)而登入 Plurk需要OAuth標準授權才行所以也需要加載 OAuth模組

注意Hubot在讀取非內建模組時會自動在前面加上 hubot-的前置

83 建立 Robot跟 API

本篇應用基本上程式碼大部分參考 Hubot - Twitter Adapter來製作主要差異只有在使用 OAuth這個模組Twitter有 Streaming可用而 Plurk則得用 Comet方式來達到即時讀取

plurkcoffee程式碼

Robot = require(rdquohubotrdquo)robot()

Adapter = require(rdquohubtordquo)adapter()

EventEmitter = require(rdquoeventsrdquo)EventEmitter

oauth = require(rdquooauthrdquo)

cronJob = require(rdquocronrdquo)CronJob

class Plurk exntends Adapter

class PlurkStreaming exnteds EventEmitter

72 8 製作一個 Hubot的 Plurk Adapter

Nodejs中文電子書

先弄個基本架構主要 Class皆參考 Twitter Adapter命名方式接著再將 Plurk這個 Class增加幾個Method基本上只要有 run send reply就夠了而 run用來做初始化的部分

class Plurk entends Adapter

send (plurk id strings⋯) -gt

reply (plurk id strings⋯) -gt

run -gt

看起來有點東西接著來處理主要的 Plurk API結合部分

class PlurkStreaming extends EventEmitter

consuctor (options) -gt

plurk (callback) -gt

觀察河道

getChannel -gt

取得 Comet 網址

reply (plurk id message) -gt

回噗

acceptFriends -gt

接受好友

get (path callback) -gt

GET 請求

post (path body callback)-gt

POST 請求(其實是裝飾)

request (method path body callback)-gt

主要的 OAuth 請求

comet (server callback)-gt

噗浪的 Comet 傳回是 JavaScript Callback 要另外處理後才會變成 JSON

然後把注意力集中到 constructor上先把建構子弄好

constructor (options) -gt

super()

if optionskey and optionssecret and optionstoken and optionstoken secret

key = optionskey

secret = optionssecret

token = optionstoken

token secret = optionstoken secret

83 建立 Robot跟 API 73

Nodejs中文電子書

建立 OAuth 連接

consumer = new oauthOAuth(

rdquohttpwwwplurkcomOAuthrequest tokenrdquo

rdquohttpwwwplurkcomOAuthaccess tokenrdquo

key

secret

rdquo10rdquo

rdquohttpwwwplurkcomOAuthauthorizerdquo

rdquoHMAC-SHA1rdquo

)

domain = rdquowwwplurkcomrdquo

初始化取得 Comet 網址

do getChannel

else

throw new Error(rdquo 參數不足需要 Key Secret Token Token Secretrdquo)

接著來處理 request這個 method

request (method path body callback) -gt

記錄一下這次的 Request

consolelog(rdquohttpdomainpathrdquo)

Callback 這邊先不丟進去要用另一種方式處理

request = consumerget(rdquohttpdomainpathrdquo token token secret null)

requeston rdquoresponserdquo (res) -gt

reson rdquodatardquo (chunk) -gt

parseResponse(chunk+rsquorsquo callback)

reson rdquoendrdquo (data) -gt

consolelog rdquoEnd Request pathrdquo

reson rdquoerrorrdquo (data) -gt

consolelog rdquoError rdquo + data

requestend()

處理資料

parseResponse = (data callback) -gt

if datalength gt 0

用 TryCatch 避免處理 JSON 出錯導致整個中斷

try

callback null JSONparse(data)

catch err

74 8 製作一個 Hubot的 Plurk Adapter

Nodejs中文電子書

consolelog(rdquoError Parse JSONrdquo + data err)

繼續執行

callback null data ||

大致上就是這樣上面程式的架構已經將整個 Hubot Plurk Adapter完成因為在測試時竟然因為噗浪 Lag而沒讀到完整的 Comet資料然後造成程式異常為了避免這個問題發生因此需要加上為了完美呈現需要再

加上 Comet的處理所以要使用到 EventEmitter的功能

comet (server callback) -gt

在 Callback 裡面會找不到自身所以設定區域變數

self =

記錄一下這次的 Request

consolelog(rdquo[Comet] serverrdquo)

Callback 這邊先不丟進去要用另一種方式處理

request = consumerget(rdquohttpdomainpathrdquo token token secret null)

requeston rdquoresponserdquo (res) -gt

reson rdquodatardquo (chunk) -gt

parseResponse(chunk+rsquorsquo callback)

reson rdquoendrdquo (data) -gt

consolelog rdquoEnd Request pathrdquo

請求結束發出事件通知可以進行下一次請求

selfemit rdquonextPlurkrdquo

reson rdquoerrorrdquo (data) -gt

consolelog rdquoError rdquo + data

requestend()

處理資料

parseResponse = (data callback) -gt

if datalength gt 0

用 trycatch 避免失敗中斷

try

去掉 JavaScript 的 Callback

data = datamatch(CometChannelscriptCallback((+))s)

jsonData = rdquordquo

if data

83 建立 Robot跟 API 75

Nodejs中文電子書

jsonData = JSONparse(data[1])

else

如果沒有任何 Match 嘗試直接 parse

jsonData = JSONparse(data)

catch err

consolelog(rdquo[Comet] Errorrdquo data err)

用 TryCatch 避免處理 JSON 出錯導致整個中斷

try

只傳入 json 的 data 部分

callback null jsonDatadata

catch err

consolelog(rdquo[Comet]Error Parse JSONrdquo + data err)

繼續執行

callback null data ||

後面的 get跟 post就簡單多了

get (path callback) -gt

request(rdquoGETrdquo path null callback)

post (path body callback) -gt

request(rdquoPOSTrdquo path body callback)

接著處理取的 Comet網址的 getChannel

getChannel -gt

self =

get rdquoAPPRealtimegetUserChannelrdquo (error data) -gt

if error

檢查是否有 comet server

if datacomet server

selfchannel = datacomet server

如果沒有 Channel Ready 就嘗試連接會失敗

selfemit(rsquochannel readyrsquo)

那麼先來處理 Plurk Adaper好處理的部份

send (plruk id strings⋯)-gt

跟 Reply 一樣直接交給 reply 做

reply plurk id strings⋯

76 8 製作一個 Hubot的 Plurk Adapter

Nodejs中文電子書

reply (plurk id strings⋯) -gt

stringsforEach (message) =gt

botreply(plruk id message)

接著把 run處理好就可以上線運作摟

run -gt

self =

options =

key processenvHUBOT PLURK KEY

secret processenvHUBOT PLURK SECRET

token processenvHUBOT PLURK TOKEN

token secret processenvHUBOT PLURK TOKEN SECRET

創建剛剛的 API

bot = new PlurkStreaming(options)

依照 Twitter 的 new RobotTextMessage 會沒有反應所以參考 hubot-minecraft 的方式

r = robotconstructor

處理噗浪河道訊息

doPlurk = (data)-gt

檢查是否為回噗

if dataresponse

datacontent raw = dataresponsecontent raw

datauser id = dataresponseuser id

確定有噗浪 ID 跟訊息

if dataplurk id and datacontent raw

selfreceive new rTextMessage(dataplurk id datacontent raw)

取得 Comet Server 完成開始第一次 Comet 連接

boton rdquochannel readyrdquo () -gt

botplurk selfdoPlurk

上一次 Comet 完成繼續 Polling

boton rdquonextPlurkrdquo ()-gt

botplurk selfdoPlurk

定時接受好友邀請

do botacceptFriends

bot = bot

83 建立 Robot跟 API 77

Nodejs中文電子書

終於完成 AdapterHubot專案裡面的 scripts資料夾內是互動部分不需要像 Adapter如此大費周章處理只新增檔案並且設計好對白之後就會回噗了我開發用的機器人在此大家可以去跟他玩玩[httpplurkcomelct9620 bot](httpplurkcomelct9620 bot)

84 原始資料提供

bull [製 作 一 個 Hubot 的 噗 浪 Adapter](http revo-skillfrosttw blog20120318create-a-hubot-plurk-adapter)

78 8 製作一個 Hubot的 Plurk Adapter

9

Nodejs好用工具介紹

91 logio

Real-time log monitoring in your browser

httplogioorg

nodejs (gt=0412 lt=0611)

npm config set unsafe-perm true

npm install -g --prefix=usrlocal logio

logio server start

httplocalhost8998

79

Nodejs中文電子書

80 9 Nodejs好用工具介紹

10

精選文章

101 Express

bull 使用 NodeJS + Express從 GETPOST Request取值 [邀稿中]

bull link2 [邀稿中]

bull link3 [邀稿中]

81

Nodejs中文電子書

82 10 精選文章

11

參考資源

111 Nodejs書籍

bull Programming Nodejs April 2012 (est) Travis Swicegood Pragmatic Bookshelf

bull e Little Book on CoffeeScript January 2012 Alex MacCaw OrsquoReilly Media

bull Node for Front-End Developers January 2012 Garann Means OrsquoReilly Media

bull Building Hypermedia APIs with HTML5 and Node November 2011 MikeAmundsen OrsquoReilly Media

bull Node Web Development August 2011 David Herron PacktPub

bull CoffeeScript Accelerated JavaScript Development July 2011 Trevor BurnhamPragmatic Bookshelf

bull Getting Started with GEO CouchDB and Nodejs July 2011 Mick omp-son OrsquoReilly Media

bull What Is Node July 2011 Brett McLaughlin OrsquoReilly Media

bull Node Up and Running May 2011 Tom Hughes-Croucher Mike WilsonOrsquoReilly Media

83

Nodejs中文電子書

bull Hands-on Nodejs e Nodejs introduction and API reference May 2011 Pe-dro Teixeira LeanPub

bull e Node Beginner Book Manuel Kiessling

bull Mastering Nodejs visionmedia

112 Nodejs影音教學

bull e Node Sessions e Best of OSCON 2011 August 2011 OrsquoReilly Media(Video)

bull Tom Hughes-Croucher on Node March 2011 OrsquoReilly Media (Video)

113 Nodejs教學網站

bull Node Tuts

114 Nodejs課程

bull Introduction To Nodejs Van Nguyen CodeLesson

84 11 參考資源

Page 33: Nodejs Wiki

Nodejs中文電子書

下載 nodejs安裝檔案 lthttpnodejsorgdownloadgt

如此完成 windows native nodeexe安裝接著可以進入 command line執行測試在 command line輸入rdquonoderdquo會出現 nodejs版本訊息畫面表示安裝完成

33 Windows 27

Nodejs中文電子書

28 3 Nodejs安裝與設定

4

Nodejs基礎

前篇文章已經由介紹安裝至設定都有完整介紹nodeJS 內部除了javascript常用的函式 (function)物件 (object)之外也有許多不同的自訂物件nodeJS預設建立這些物件為核心物件是為了要讓開發流程更為這些資料在官方文件已經具有許多具體說明接下來將會介紹在開發 nodeJS程式時常見的物件特性與使用方法

41 nodejs http伺服器建立

在 lsquonodejs官方網站 lthttpnodejsorggtlsquo裡面有舉一個最簡單的 HTTP伺服器建立一開始初步就是建立一個伺服器平台讓 nodejs可以與瀏覽器互相行為每種語言一開始的程式建立都是以 Hello world開始最初也從 Hello world帶各位進入 nodejs的世界

輸入以下程式碼儲存檔案為 node basic http hello worldjs

var server

ip = rdquo127001rdquo

port = 1337

http = require(rsquohttprsquo)

server = httpcreateServer(function (req res)

29

Nodejs中文電子書

reswriteHead(200 rsquoContent-Typersquo rsquotextplainrsquo)

resend(rsquoHello Worldnrsquo)

)

serverlisten(port ip)

consolelog(rdquoServer running at httprdquo + ip + rdquordquo + port)

程式碼解講一開始需要有幾個基本的變數

bull ip 機器本身的 ip位置因為使用本地端因此設定為 127001

bull port 需要開通的阜號通常設定為 http port 80因範例不希望與基本 port相衝隨意設定為 1337

在 nodejs 的程式中有許多預設的模組可以使用因此需要使用require方法將模組引入在這邊我們需要使用 http這個模組因此將 http載入Http模組裡面內建有許多方法可以使用這邊採用 createServer創建一個基本的 http伺服器再將 http伺服器給予一個 server變數裡面的回呼函式 (call back function)可以載入 http伺服器的資料與回應方法 (requestresponse)在程式裡面就可以看到我們直接回應給瀏覽器端所需的 Header回應內容

reswriteHead(200 rsquoContent-Typersquo rsquotextplainrsquo)

resend(rsquoHello Worldnrsquo)

Http伺服器需要設定 port ip在最後需要設定 Http監聽需要使用到listen事件監聽所有 Http伺服器行為

httplisten(port ip)

所有事情都完成之後需要確認伺服器正確執行因此使用 console在javascript裡就有這個原生物件console所印出的資料都會顯示於 nodejs伺服器頁面這邊印出的資料並不會傳送到使用者頁面上之後許多除壞

(debug)都會用到 console物件

consolelog(rdquoServer running at httprdquo + ip + rdquordquo + port)

30 4 Nodejs基礎

Nodejs中文電子書

42 nodejs http路徑建立

前面已經介紹如何建立一個簡單的 http伺服器接下來本章節將會介紹如何處理伺服器路徑 (rout)問題在 http的協定下所有從瀏覽器發出的要求 (request)都需要經過處理路徑上的建立也是如此

路徑就是指伺服器 ip位置或者是網域名稱之後對於伺服器給予的要求修改剛才的 hello world檔案修改如下

server = httpcreateServer(function (req res)

consolelog(requrl)

reswriteHead(200 rsquoContent-Typersquo rsquotextplainrsquo)

resend(rsquohello worldnrsquo)

)

重新啟動 nodejs程式後在瀏覽器端測試一下路徑行為結果如下圖

當在瀏覽器輸入 http1270011337test 在伺服器端會收到兩個要求一個是我們輸入的test要求另外一個則是faviconicotest的路徑要求http伺服器本身需要經過程式設定才有辦法回應給瀏覽器端所需要的回應在伺服器中所有的路徑要求都是需要被解析才有辦法取得資料從

42 nodejs http路徑建立 31

Nodejs中文電子書

上面解說可以了解到在 nodejs當中所有的路徑都需要經過設定未經過設定的路由會讓瀏覽器無法取得任何資料導致錯誤頁面的發生底下將會解

說如何設定路由同時避免發生錯誤情形先前 nodejs程式需要增加一些修改才能讓使用者透過瀏覽器在不同路徑時有不同的結果根據剛才

的程式做如下的修改

var server

ip = rdquo127001rdquo

port = 1337

http = require(rsquohttprsquo)

url = require(rsquourlrsquo)

path

server = httpcreateServer(function (req res)

path = urlparse(requrl)

reswriteHead(200 rsquoContent-Typersquo rsquotextplainrsquo)

switch (pathpathname)

case rdquoindexrdquo

resend(rsquoI am indexnrsquo)

break

case rdquotestrdquo

resend(rsquothis is test pagenrsquo)

break

default

resend(rsquodefault pagenrsquo)

break

)

serverlisten(port ip)

consolelog(rdquoServer running at httprdquo + ip + rdquordquo + port)

程式做了片段的修改首先載入 url模組另外增加一個 path變數url模組就跟如同他的命名一般專門處理 url字串處理裡面提供了許多方法來解決路徑上的問題因為從瀏覽器發出的要求路徑可能會帶有多種需求

或者 GET參數組合等因此我們需要將路徑單純化取用路徑部分的資料即可例如使用者可能會送出 http1270011337testsend=1如果直接

32 4 Nodejs基礎

Nodejs中文電子書

信任 requrl就會收到結果為testsend=1所以需要透過 url模組的方法將路徑資料過濾

在這邊使用 urlparse的方法裡面帶入網址格式資料會回傳路徑資料為了後需方便使用將回傳的資料設定到 path變數當中在回傳的路徑資料裡面包含資訊如下圖

這邊只需要使用單純的路徑要求直接取用 pathpathname就可以達到我們的目的

最後要做路徑的判別在不同的路徑可以指定不同的輸出在範例中

有三個可能結果第一個從瀏覽器輸入index就會顯示 index結果test 就會呈現出 test頁面最後如果都不符合預期的輸入會直接顯示 default的頁面最後的預防可以讓瀏覽器不會出現非預期結果讓程式的可靠性提昇

底下為測試結果

42 nodejs http路徑建立 33

Nodejs中文電子書

43 nodejs檔案讀取

前面已經介紹如何使用路由(rount)做出不同的回應實際應用只有在瀏覽器只有輸出幾個文字資料總是不夠的在本章節中將介紹如何使

用檔案讀取輸出檔案資料讓使用者在前端瀏覽器也可以讀取到完整的

html css javascript檔案輸出

檔案管理最重要的部分就是 lsquoFile system lthttpnodejsorgdocslatestapifshtmlgtlsquo這個模組此模組可以針對檔案做管理監控讀取等行為裡面有許多預設的方法底下是檔案輸出的基本範例底下會有兩個檔案

第一個是靜態 html檔案

ltDOCTYPE htmlgt

lthtml xmlns=rdquohttpwwww3org1999xhtmlrdquo xmllang=rdquozh-CNrdquo lang=rdquozh-CNrdquogt

ltheadgt

ltmeta http-equiv=rdquoContent-Typerdquo content=rdquotexthtml charset=UTF-7rdquo gt

lttitlegtnodejs index html filelttitlegt

ltheadgt

ltbodygt

34 4 Nodejs基礎

Nodejs中文電子書

lth1gtnodejs index html filelth1gt

ltbodygt

lthtmlgt

另一個為 nodejs程式

var fs = require(rdquofsrdquo)

filename = rdquostaticindexhtmlrdquo

encode = rdquoutf8rdquo

fsreadFile(filename encode function(err file)

consolelog(file)

)

一開始直接載入 le system模組 載入名稱為 fs讀取檔案主要使

用的方法為 readFile裡面以三個參數路徑 ( le path) 編碼方式 (encoding)

回應函式 (callback)路徑必須要設定為靜態 html所在位置才能指定到正確的檔案靜態檔案的編碼方式也必須正確這邊使用靜態檔案的編

碼為 utf8如果編碼設定錯誤nodejs讀取出來檔案結果會使用 byte raw格式輸出如果錯誤編碼格式會導致輸出資料為 byte raw

回應函式中裡面會使用兩個變數error為錯誤資訊如果讀取的檔案不存在或者發生錯誤error數值會是 true如果成功讀取資料 error將會是 falsecontent則是檔案內容資料讀取後將會把資料全數丟到 content這個變數當中

最後程式的輸出結果畫面如下

43 nodejs檔案讀取 35

Nodejs中文電子書

44 nodejs http靜態檔案輸出

前面已經了解如何讀取本地端檔案接下來將配合 http伺服器路由讓每個路由都能夠輸出相對應的靜態 html檔案首先新增加幾個靜態 html檔案

ltDOCTYPE htmlgt

lthtml xmlns=rdquohttpwwww3org1999xhtmlrdquo xmllang=rdquozh-CNrdquo lang=rdquozh-CNrdquogt

ltheadgt

ltmeta http-equiv=rdquoContent-Typerdquo content=rdquotexthtml charset=UTF-7rdquo gt

lttitlegtnodejs index html filelttitlegt

ltheadgt

ltbodygt

lth1gtnodejs index html filelth1gt

ltbodygt

lthtmlgt

ltDOCTYPE htmlgt

lthtml xmlns=rdquohttpwwww3org1999xhtmlrdquo xmllang=rdquozh-CNrdquo lang=rdquozh-CNrdquogt

ltheadgt

ltmeta http-equiv=rdquoContent-Typerdquo content=rdquotexthtml charset=UTF-7rdquo gt

lttitlegtnodejs test html filelttitlegt

ltheadgt

ltbodygt

lth1gtnodejs test html filelth1gt

ltbodygt

lthtmlgt

ltDOCTYPE htmlgt

lthtml xmlns=rdquohttpwwww3org1999xhtmlrdquo xmllang=rdquozh-CNrdquo lang=rdquozh-CNrdquogt

ltheadgt

ltmeta http-equiv=rdquoContent-Typerdquo content=rdquotexthtml charset=UTF-7rdquo gt

lttitlegtnodejs static html filelttitlegt

ltheadgt

ltbodygt

lth1gtnodejs static html filelth1gt

ltbodygt

lthtmlgt

36 4 Nodejs基礎

Nodejs中文電子書

準備一個包含基本路由功能的 http伺服器

var server

ip = rdquo127001rdquo

port = 1337

http = require(rsquohttprsquo)

url = require(rsquourlrsquo)

server = httpcreateServer(function (req res)

var path = urlparse(requrl)

)

serverlisten(port ip)

consolelog(rdquoServer running at httprdquo + ip + rdquordquo + port)

加入 le system 模組使用 readFile 的功能將這一段程式放置於

createServer的回應函式中

fsreadFile(filePath encode function(err file)

)

readFile的回應函式裡面加入頁面輸出讓瀏覽器可以正確讀到檔案在這邊我們設定讀取的檔案為 html 靜態檔案所以 Content-type 設定為texthtml讀取到檔案的內容將會正確輸出成 html靜態檔案

fsreadFile(filePath encode function(err file)

reswriteHead(200 rsquoContent-Typersquo rsquotexthtmlrsquo)

reswrite(file)

resend()

)

到這邊為止基本的程式內容都已經完成剩下一些細節的調整首先

路徑上必須做調整目前的靜態檔案全部都放置於 static資料夾底下設定一個變數來記住資料夾位置

接著將瀏覽器發出要求路徑與資料夾組合讀取正確 html靜態檔案使用者有可能會輸入錯誤路徑所以在讀取檔案的時候要加入錯誤處理

同時回應 404伺服器無法正確回應的 http header格式

加入這些細節的修改一個基本的 http 靜態 html輸出伺服器就完成了完整程式碼如下

44 nodejs http靜態檔案輸出 37

Nodejs中文電子書

var server

ip = rdquo127001rdquo

port = 1337

http = require(rsquohttprsquo)

fs = require(rdquofsrdquo)

folderPath = rdquostaticrdquo

url = require(rsquourlrsquo)

path

filePath

encode = rdquoutf8rdquo

server = httpcreateServer(function (req res)

path = urlparse(requrl)

filePath = folderPath + pathpathname

fsreadFile(filePath encode function(err file)

if (err)

reswriteHead(404 rsquoContent-Typersquo rsquotextplainrsquo)

resend()

return

reswriteHead(200 rsquoContent-Typersquo rsquotextapplicationrsquo)

reswrite(file)

resend()

)

)

serverlisten(port ip)

consolelog(rdquoServer running at httprdquo + ip + rdquordquo + port)

45 nodejs http GET資料擷取

http伺服器中除了路由之外另一個最常使用的方法就是擷取 GET資料本單元將會介紹如何透過基本 http伺服器擷取瀏覽器傳來的要求擷取 GET資料

在 http協定中GET參數都是藉由 URL從瀏覽器發出要求送至伺服器

38 4 Nodejs基礎

Nodejs中文電子書

端基本的傳送網址格式可能如下

http127001testsend=1amptest=2

上面這段網址裡面的 GET參數就是 send而這個變數的數值就為 1如果想要在 http伺服器取得 GET資料需要在瀏覽器給予的要求 (request)做處理

首先需要載入 query string這個模組這個模組主要是用來將字串資料

過濾後轉換成javascript物件

qs = require(rsquoquerystringrsquo)

接著在第一階段利用 url模組過濾瀏覽器發出的 URL資料後將回應的物件裡面的 query這個變數是一個字串值資料過濾後如下

send=1amptest=2

透過 query string使用 parse這個方法將資料轉換成 javascript物件就表示 GET的資料已經被伺服器端正式擷取下來

path = urlparse(requrl)

parameter = qsparse(pathquery)

整個 nodejs http GET參數完整擷取程式碼如下

var server

ip = rdquo127001rdquo

port = 1337

http = require(rsquohttprsquo)

qs = require(rsquoquerystringrsquo)

url = require(rsquourlrsquo)

server = httpcreateServer(function (req res)

var path = urlparse(requrl)

parameter = qsparse(pathquery)

consoledir(parameter)

reswriteHead(200 rsquoContent-Typersquo rsquotextplainrsquo)

reswrite(rsquoBrowser test GET parameternrsquo)

resend()

)

45 nodejs http GET資料擷取 39

Nodejs中文電子書

serverlisten(port ip)

consolelog(rdquoServer running at httprdquo + ip + rdquordquo + port)

程式運作之後由瀏覽器輸入要求網址之後nodejs伺服器端回應資料為

Server running at http1270011337

send rsquo1rsquo test rsquo1rsquo

46 本章結語

前面所解說的部份一大部分主要是處理 http伺服器基本問題雖然在某些部分有牽扯到 http 伺服器基本運作原理主要還是希望可以藉由這些基本範例練習 nodejs練習回應函式與語法串接的特點習慣編寫javascript風格程式當然直接這樣開發 nodejs是非常辛苦的接下來在模組實戰開發的部份將會介紹特定的模組一步一步帶領各位從無到有進行

nodejs應用程式開發

40 4 Nodejs基礎

5

NPM套件管理工具

npm全名為 Node Package Manager是 Nodejs的套件(package)管理工具類似 Perl的 ppm或 PHP的 PEAR等安裝 npm後使用npm install

module name指令即可安裝新套件維護管理套件的工作會更加輕鬆

npm可以讓Nodejs的開發者直接利用擴充線上的套件庫(packagesregistry)加速軟體專案的開發npm提供很友善的搜尋功能可以快速找到安裝需要的套件當這些套件發行新版本時npm也可以協助開發者自動更新這些套件

npm不僅可用於安裝新的套件它也支援搜尋列出已安裝模組及更新的功能

51 安裝 NPM

Nodejs在 063版本開始內建 npm讀者安裝的版本若是此版本或更新的版本就可以略過以下安裝說明

若要檢查 npm是否正確安裝可以使用以下的指令

npm -v

41

Nodejs中文電子書

執行結果說明

若 npm正確安裝執行 npm -v將會看到類似 110-2的版本訊息

若讀者安裝的 Nodejs版本比較舊或是有興趣嘗試自己動手安裝 npm工具則可以參考以下的說明

安裝於Windows系統

Nodejs for Windows 於 062 版開始內建 npm使用 nodejsorg 官方提供的安裝程式不需要進一步的設定就可以立即使用 npm指令對於Windows的開發者來說大幅降低環境設定的問題與門檻

除了使用 Nodejs內建的 npm讀者也可以從 npm官方提供的以下網址

httpnpmjsorgdist

這是由 npm提供的 Fancy Windows Install版本請下載壓縮檔(例如npm-110-3zip)並將壓縮檔內容解壓縮至 Nodejs的安裝路徑(例如CProgram Filesnodejs)

解壓縮後在 Nodejs的安裝路徑下應該有以下的檔案及資料夾

bull npmcmd(檔案)

bull node modules(資料夾)

安裝於 Linux系統

Ubuntu Linux的使用者可以加入 NPM Unoffcial PPA這個 repository即可使用 apt-get完成 npm安裝

42 5 NPM套件管理工具

Nodejs中文電子書

Ubuntu Linux使用 apt-get安裝 npm

sudo apt-get install python-software-properties

sudo add-apt-repository ppagias-kay-leenpm

sudo apt-get update

sudo apt-get install npm

npm官方提供的安裝程式 installsh可以適用於大多數的 Linux系統使用這個安裝程式請先確認

1 系統已安裝 curl工具(請使用 curl --version查看版本訊息)

2 已安裝 Nodejs並且 PATH正確設置

3 Nodejs的版本必須大於 04x

以下為 npm提供的安裝指令

curl httpnpmjsorginstallsh | sh

安裝成功會看到如下訊息

installsh安裝成功的訊息

npm10105 homeUSERNAMElocalnodelibnode_modulesnpm

It worked

安裝於Mac OS X

建議採用與 Nodejs相同的方式進行 npm的安裝例如使用MacPorts安裝 Nodejs就同樣使用MacPorts安裝 npm這樣對日後的維護才會更方便容易

使用 MacPorts安裝 npm是本書比較建議的方式它可以讓 npm的安裝移除及更新工作自動化將會幫助開發者節省寶貴時間

51 安裝 NPM 43

Nodejs中文電子書

安裝MacPorts的提示

在MacPorts網站可以取得 OS X系統版本對應的安裝程式(例如 106或 107)httpwwwmacportsorg安裝過程會詢問系統管理者密碼使用預設的選項完成安裝即可安

裝MacPorts之後在終端機執行 port -v將會看到MacPorts的版本訊息

安裝 npm之前先更新MacPorts的套件清單以確保安裝的 npm是最新版本

sudo port -d selfupdate

接著安裝 npm

sudo port install npm

若讀者的 Nodejs並非使用MacPorts安裝則不建議使用MacPorts安裝npm因為 MacPorts會自動檢查並安裝相依套件而 npm相依 nodejs所以MacPorts也會一併將 nodejs套件安裝造成先前讀者使用其它方式安裝的 nodejs被覆蓋

讀者可以先使用 MacPorts安裝 curl(sudo port install curl)

再參考 Linux的 installsh安裝方式即可使用 npm官方提供的安裝程式

NPM安裝後測試

npm是指令列工具(command-line tool)使用時請先打開系統的文字終端機工具

測試 npm安裝與設定是否正確請輸入指令如下

npm -v

或是

npm --version

如果 npm已經正確安裝設定就會顯示版本訊息

44 5 NPM套件管理工具

Nodejs中文電子書

執行結果(範例)

110-2

52 使用 NPM安裝套件

npm目前擁有超過 6000種套件(packages)可以在 npm registry使用關鍵字搜尋套件

httpsearchnpmjsorg

舉例來說在關鍵字欄位輸入「coffee-script」下方的清單就會自動列出包含 coffee-script關鍵字的套件

接著我們回到終端機模式的操作npm的指令工具本身就可以完成套

件搜尋的任務

例如以下的指令同樣可以找出 coffee-script相關套件

npm search coffee-script

以下是搜尋結果的參考畫面

52 使用 NPM安裝套件 45

Nodejs中文電子書

找到需要的套件後(例如 express)即可使用以下指令安裝

npm install coffee-script

值得注意的一點是使用 npm install會將指定的套件安裝在工

作目錄(Working Directory)的 node modules資料夾下

以Windows為例如果執行 npm install的目錄位於

Cproject1

那麼 npm將會自動建立一個 node modules的子目錄(如果不存在)

Cproject1node modules

並且將下載的套件放置於這個子目錄例如

Cproject1node modulescoffee-script

這個設計讓專案可以個別管理相依的套件並且可以在專案佈署或發

行時將這些套件(位於 node modules)一併打包方便其它專案的使用者不必再重新下載套件

這個 npm install的預設安裝模式為 local(本地)只會變更當前專案的資料夾不會影響系統

另一種安裝模式稱為 global(全域)這種模式會將套件安裝到系統資

料夾也就是 npm安裝路徑的 node modules資料夾例如

CProgram Filesnodejsnode modules

46 5 NPM套件管理工具

Nodejs中文電子書

是否要使用全域安裝可以依照套件是否提供新指令來判斷舉例來說express 套件提供 express 這個指令而 coffee-script 則提供 coffee

指令

在 local 安 裝 模 式 中這 些 指 令 的 程 式 檔 案會 被 安 裝 到node modules的 bin這個隱藏資料夾下除非將bin的路徑加入 PATH環境變數否則要執行這些指令將會相當不便

為了方便指令的執行我們可以在 npm install 加上 -g 或 --

global參數啟用 global安裝模式例如

npm install -g coffee-script

npm install -g express

使用 global安裝模式需要注意執行權限的問題若權限不足可能會出現類似以下的錯誤訊息

npm ERR Error EACCES permission denied rsquorsquo

npm ERR

npm ERR Please try running this command again as rootAdministrator

要獲得足夠得執行權限請參考以下說明

bull Windows 7 或 2008 以上在「命令提示字元」的捷徑按右鍵選擇「以系統管理員身分執行」執行 npm指令時就會具有 Administrator身分

bull Mac OS X或 Linux系統可以使用 sudo指令例如

sudo npm install -g express

bull Linux系統可以使用 root權限登入或是以「sudo su -」切換成 root身分(使用 root權限操作系統相當危險因此並不建議使用這種方式)

若加上 -g參數使用 npm install -g coffee-script完成安裝

後就可以在終端機執行 coffee指令例如

coffee -v

52 使用 NPM安裝套件 47

Nodejs中文電子書

執行結果(範例)

CoffeeScript version 120

53 套件的更新及維護

除了前一節說明的 search 及 install 用法npm 還提供其他許多指令(commands)

使用 npm help可以查詢可用的指令

npm help

執行結果(部分)

where ltcommandgt is one of

adduser apihelp author bin bugs c cache completion

config deprecate docs edit explore faq find get

help help-search home i info init install la link

list ll ln login ls outdated owner pack prefix

prune publish r rb rebuild remove restart rm root

run-script s se search set show star start stop

submodule tag test un uninstall unlink unpublish

unstar up update version view whoami

使用 npm help command可以查詢指令的詳細用法例如

npm help list

接下來本節要介紹開發過程常用的 npm指令

使用 list可以列出已安裝套件

npm list

48 5 NPM套件管理工具

Nodejs中文電子書

執行結果(範例)

-- coffee-script120

- express256

- connect185

| -- formidable108

-- mime124

-- mkdirp007

-- qs041

檢視某個套件的詳細資訊例如

npm show express

升級所有套件(如果該套件已發佈更新版本)

npm update

升級指定的套件

npm update express

移除指定的套件

npm uninstall express

54 使用 packagejson

對於正式的 Nodejs專案可以建立一個命名為 packagejson的設

定檔(純文字格式)檔案內容參考範例如下

54 使用 packagejson 49

Nodejs中文電子書

packagejson(範例)

rdquonamerdquo rdquoapplication-namerdquo

rdquoversionrdquo rdquo001rdquo

rdquoprivaterdquo true

rdquodependenciesrdquo

rdquoexpressrdquo rdquo255rdquo

rdquocoffee-scriptrdquo rdquolatestrdquo

rdquomongooserdquo rdquogt= 253rdquo

其中 name與 version依照專案的需求設置

需要注意的是 dependencies的設定它用於指定專案相依的套件名

稱及版本

bull rdquoexpressrdquo rdquo255rdquo

代表此專案相依版本 255的 express套件

bull rdquocoffee-scriptrdquo rdquolatestrdquo

使用最新版的 coffee-script套件(每次更新都會檢查新版)

bull rdquomongooserdquo rdquogt= 253rdquo

使用版本大於 253的 mongoose套件

假設某個套件的新版可能造成專案無法正常運作就必須指定套件的

版本避免專案的程式碼來不及更新以相容新版套件通常在開發初期的

專案需要盡可能維持新套件的相容性(以取得套件的更新或修正)可以

用「gt=」設定最低相容的版本或是使用「latest」設定永遠保持最新

套件

50 5 NPM套件管理工具

6

Express介紹

在前面的 nodejs基礎當中介紹許多許多開設 http的使用方法及介紹以及許多基本的 nodejs基本應用

接下來要介紹一個套件稱為 express [Express](httpexpressjscom)這個套件主要幫忙解決許多 nodejs http server所需要的基本服務讓開發 httpservice變得更為容易不需要像之前需要透過層層模組(module)才有辦法開始編寫自己的程式

這個套件是由 TJ Holowaychuk 製作而成的套件裡面包含基本的路由處理 (route)http資料處理(GETPOSTPUT)另外還與樣板套件(jshtml template engine)搭配同時也可以處理許多複雜化的問題

61 Express安裝

安裝方式十分簡單只要透過之前介紹的 NPM就可以使用簡單的指令安裝指令如下

這邊建議需要將此套件安裝成為全域模組方便日後使用

51

Nodejs中文電子書

62 Express基本操作

express的使用也十分簡單先來建立一個基本的 hello world

var app = require(rsquoexpressrsquo)createServer()

port = 1337

applisten(port)

appget(rsquorsquo function(req res)

ressend(rsquohello worldrsquo)

)

consolelog(rsquostart express servernrsquo)

可以從上面的程式碼發現基本操作與 nodejs http的建立方式沒有太大差異主要差在當我們設定路由時可以直接透過 appget方式設定回應與接受方式

63 Express路由處理

Express對於 http服務上有許多包裝讓開發者使用及設定上更為方便例如有幾個路由設定那我們就統一藉由 appget來處理

Create http server

appget(rsquorsquo function(req res)

ressend(rsquohello worldrsquo)

)

appget(rsquotestrsquo function(req res)

ressend(rsquotest renderrsquo)

)

appget(rsquouserrsquo function(req res)

ressend(rsquouser pagersquo)

)

52 6 Express介紹

Nodejs中文電子書

如上面的程式碼所表示appget可以帶入兩個參數第一個是路徑名稱設定第二個為回應函式 (call back function)回應函式裡面就如同之前的 createServer方法裡面包含 requestresponse兩個物件可供使用使用者就可以透過瀏覽器輸入不同的 url切換到不同的頁面顯示不同的結果

路由設定上也有基本的配對方式讓使用者從瀏覽器輸入的網址可以

是一個變數只要符合型態就可以有對應的頁面產出例如

Create http server

appget(rsquouseridrsquo function(req res)

ressend(rsquouser rsquo + reqparamsid)

)

appget(rsquonumberrsquo function(req res)

ressend(rsquonumber rsquo + reqparamsnumber)

)

裡 面 使 用 到number 從 網 址 輸 入 之 後 就 可 以 直 接 使 用reqparamsnumber 取得所輸入的資料變成 url 參數使用當然前面也是可以加上路徑的設定userid在瀏覽器上路徑必須符合userxxx透

過 reqparamsid就可以取到 xxx這個字串值

另外express參數處理也提供了路由參數配對處理也可以透過正規表示法作為參數設定

var app = require(rsquoexpressrsquo)createServer()

port = 1337

applisten(port)

appget(^ip((d23)((d23))((d23))((d23))) function(req res)

ressend(reqparams)

)

上面程式碼可以發現後面路由設定的型態是正規表示法裡面設定

格式為ip之後必須要加上 ip型態才會符合資料格式同時取得 ip資料已經由正規表示法將資料做分群因此可以取得 ip的四個數字

此程式執行之後可以透過瀏覽器測試輸入網址為 localhost3000ip25525510010可以從頁面獲得資料

63 Express路由處理 53

Nodejs中文電子書

此章節全部範例程式碼如下

overview

author Caesar Chi

blog clonnblogspotcom

version 20120226

create server

var app = require(rsquoexpressrsquo)createServer()

port = 1337

applisten(port)

normal style

appget(rsquorsquo function(req res)

ressend(rsquohello worldrsquo)

)

appget(rsquotestrsquo function(req res)

ressend(rsquotest renderrsquo)

)

parameter style

appget(rsquouseridrsquo function(req res)

ressend(rsquouser rsquo + reqparamsid)

)

appget(rsquonumberrsquo function(req res)

ressend(rsquonumber rsquo + reqparamsnumber)

)

REGX style

appget(^ip((d23)((d23))((d23))) function(req res)

ressend(reqparams)

)

appget(rsquorsquo function(req res)

ressend(rsquoPage not foundrsquo 404)

54 6 Express介紹

Nodejs中文電子書

)

consolelog(rsquostart express servernrsquo)

64 Express middleware

Express 裡面有一個十分好用的應用概念稱為 middleware可以透過middleware做出複雜的效果同時上面也有介紹 next方法參數傳遞就是靠 middleware的概念來傳遞參數讓開發者可以明確的控制程式邏輯

create http server

appuse(expressbodyParser())

appuse(expressmethodOverride())

appuse(expresssession())

上面都是一種 middleware的使用方式透過 appuse方式裡面載入函式執行方法回應函式會包含三個基本參數responserequestnext其中next表示下一個 middleware執行函式同時會自動將預設三個參數繼續帶往下個函式執行底下有個實驗

上面的片段程式執行後開啟瀏覽器連結上 localhost1337會發現伺服器回應結果順序如下

first middle ware

second middle ware

execute middle ware

end middleware function

從上面的結果可以得知剛才設定的 middleware都生效了在 appuse設定的 middleware是所有 url皆會執行方法如果有指定特定方法就可以使用 appget的 middleware設定在 appget函式的第二個參數就可以帶入函式或者是匿名函式只要函式裡面最後會接受 request response next這三個參數同時也有正確指定 next函式的執行時機最後都會執行到最後一個方法當然開發者也可以評估程式邏輯要執行到哪一個階段讓邏輯

可以更為分明

64 Express middleware 55

Nodejs中文電子書

65 Express路由應用

在實際開發上可能會遇到需要使用參數等方式混和變數一起使用

express裡面提供了一個很棒的處理方法 appall這個方式可以先採用基本路由配對再將設定為每個不同的處理方式開發者可以透過這個方式簡

化自己的程式邏輯

overview

author

version 20120226

create server

var app = require(rsquoexpressrsquo)createServer()

port = 1337

users = [

name rsquoClonnrsquo

name rsquoChirsquo

]

applisten(port)

appall(rsquouseridoprsquo function(req res next)

requser = users[reqparamsid]

if (requser)

next()

else

next(new Error(rsquocannot find user rsquo + reqparamsid))

)

appget(rsquouseridrsquo function(req res)

ressend(rsquoviewing rsquo + requsername)

)

appget(rsquouserideditrsquo function(req res)

ressend(rsquoediting rsquo + requsername)

)

56 6 Express介紹

Nodejs中文電子書

appget(rsquouseriddeletersquo function(req res)

ressend(rsquodeleting rsquo + requsername)

)

appget(rsquorsquo function(req res)

ressend(rsquoPage not foundrsquo 404)

)

consolelog(rsquostart express servernrsquo)

內部宣告一組預設的使用者分別給予名稱設定藉由 appall這個方法可以先將路由雛形建立再接下來設定 appget的路徑格式只要符合格式就會分配進入對應的方法中像上面的程式當中如果使用者輸入路徑為user0除了執行 appall程式之後執行 next方法就會對應到路徑設定為userid的這個方法當中如果使用者輸入路徑為user0edit就會執行到useridedit的對應方法

66 Express GET應用範例

我們準備一個使用 GET方法傳送資料的表單

ltDOCTYPE htmlgt

lthtmlgt

ltheadgt

ltmeta http-equiv=rdquoContent-Typerdquo content=rdquotexthtml charset=utf8rdquogt

lttitlegtNodejs 菜鳥筆記 (1)lttitlegt

ltlink rel=rdquostylesheetrdquo href=rdquocssstylecssrdquo type=rdquotextcssrdquo media=rdquoallrdquo gt

ltheadgt

ltbodygt

lth1gtNodejs 菜鳥筆記-註冊lth1gt

ltform id=rdquosignuprdquo method=rdquoGETrdquo action=rdquohttplocalhost3000Signuprdquogt

ltlabelgt 使用者名稱ltlabelgtltinput type=rdquotextrdquo id=rdquousernamerdquo name=rdquousernamerdquo gtltbrgt

ltlabelgt 電子郵件ltlabelgtltinput type=rdquotextrdquo id=rdquoemailrdquo name=rdquoemailrdquo gtltbrgt

ltinput type=rdquosubmitrdquo value=rdquo 註冊我的帳號rdquo gtltbrgt

ltformgt

ltbodygt

lthtmlgt

66 Express GET應用範例 57

Nodejs中文電子書

這個表單沒有什麼特別的地方我們只需要看第 9 行form 使用的method是 GET然後 action是rdquohttplocalhost3000Signupldquo等一下我們要來撰寫Signup這個 URL Path的處理程式

處理 Signup行為

我們知道所謂的 GET方法會透過 URL來把表單的值給帶過去以上面的表單來說到時候 URL會以這樣的形式傳遞

httplocalhost3000Signupusername=xxxampemail=xxx

所以要能處理這樣的資料必須有以下功能

bull 解析 URL

bull 辨別動作是 Signup

bull 解析出 username和 email

一旦能取得 username和 email的值程式就能加以應用了

處理 Signup的程式碼雛形

load module

var url = require(rsquourlrsquo)

urlData = urlparse(requrltrue)

action = urlDatapathname

reswriteHead(200 rdquoContent-Typerdquordquotexthtml charset=utf-8rdquo)

if (action === rdquoSignuprdquo)

user = urlDataquery

resend(rdquolth1gtrdquo + userusername + rdquo 歡迎您的加入 lth1gtltpgt 我們已經將會員啟用信寄至rdquo + useremail + rdquoltpgtrdquo)

首先需要加載 url module它是用來協助我們解析 URL的模組接著使用 urlparse方法第一個傳入 url字串變數也就是 requrl另外第二個參數的用意是設為 ture則引進 querystring模組來協助處理預設是 false它影響到的是 urlDataquery設為 true會傳回物件不然就只是一般的字串urlparse會將字串內容整理成一個物件我們把它指定給 urlData

action變數作為記錄 pathname這是我們稍後要來判斷目前網頁的動作是什麼接著先將 html表頭資訊 (Header)準備好再來判斷路徑邏輯如

58 6 Express介紹

Nodejs中文電子書

果是 Signup這個動作就把 urlDataquery裡的資料指定給 user然後輸出userusername和 useremail把使用者從表單註冊的資料顯示於頁面中

最後進行程式測試啟動 nodejs主程式之後開啟瀏覽器就會看到表單填寫完畢按下送出就可以看到結果了

完整 nodejs程式碼如下

var http = require(rsquohttprsquo)

url = require(rsquourlrsquo)

fs = require(rdquofsrdquo)

server

server = httpcreateServer(function (reqres)

var urlData

encode = rdquoutf8rdquo

filePath = rdquoviewexpress_get_example_formhtmlrdquo

action

urlData = urlparse(requrltrue)

action = urlDatapathname

reswriteHead(200 rdquoContent-Typerdquordquotexthtml charset=utf-8rdquo)

if (action === rdquoSignuprdquo)

user = urlDataquery

resend(rdquolth1gtrdquo + userusername + rdquo 歡迎您的加入 lth1gtltpgt 我們已經將會員啟用信寄至rdquo + useremail + rdquoltpgtrdquo)

else

fsreadFile(filePath encode function(err file)

reswrite(file)

resend()

)

)

serverlisten(3000)

consolelog(rsquoServer 跑起來了現在時間是rsquo + new Date())

66 Express GET應用範例 59

Nodejs中文電子書

67 Express POST應用範例

一開始準備基本的 html表單傳送內容以 POST方式form的 action屬性設定為 POST其餘 html內容與前一個範例應用相同

ltDOCTYPE htmlgt

lthtmlgt

ltheadgt

ltmeta http-equiv=rdquoContent-Typerdquo content=rdquotexthtml charset=utf8rdquogt

lttitlegtNodejs 菜鳥筆記 (1)lttitlegt

ltlink rel=rdquostylesheetrdquo href=rdquocssstylecssrdquo type=rdquotextcssrdquo media=rdquoallrdquo gt

ltheadgt

ltbodygt

lth1gtNodejs 菜鳥筆記-註冊lth1gt

ltform id=rdquosignuprdquo method=rdquoPOSTrdquo action=rdquohttplocalhost3000Signuprdquogt

ltlabelgt 使用者名稱ltlabelgtltinput type=rdquotextrdquo id=rdquousernamerdquo name=rdquousernamerdquo gtltbrgt

ltlabelgt 電子郵件ltlabelgtltinput type=rdquotextrdquo id=rdquoemailrdquo name=rdquoemailrdquo gtltbrgt

ltinput type=rdquosubmitrdquo value=rdquo 註冊我的帳號rdquo gtltbrgt

ltformgt

ltbodygt

lthtmlgt

nodejs的程式處理邏輯與前面 GET範例類似部分程式碼如下

qs = require(rsquoquerystringrsquo)

if (action === rdquoSignuprdquo)

formData = rsquorsquo

reqon(rdquodatardquo function (data)

formData += data

)

reqon(rdquoendrdquo function ()

user = qsparse(formData)

resend(rdquolth1gtrdquo + userusername + rdquo 歡迎您的加入 lth1gtltpgt 我們已經將會員啟用信寄至rdquo + useremail + rdquoltpgtrdquo)

)

60 6 Express介紹

Nodejs中文電子書

主要加入了rsquoquerystringrsquo這個moduel方便我們等一下解析由表單 POST回來的資料另外加入一個 formData的變數用來搜集待等一下表單回傳的資料前面的 GET範例我們只從 req拿出 url的資料這次要在利用req身上的事件處理

JavaScript 在訂閱事件時使用 addEventListener而 nodejs 使用的則是on這邊加上了監聽 data的事件會在瀏覽器傳送資料到Web Server時被執行參數是它所接收到的資料型態是字串

接著再增加 end的事件當瀏覽器的請求事件結束時它就會動作

由於瀏覽器使用 POST在上傳資料時會將資料一塊塊地上傳因為我們在監聽 data事件時透過 formData變數將它累加起來lt不過由於我們

上傳的資料很少一次就結束不過如果日後需要傳的是資料比較大的檔

案這個累加動作就很重要

當資料傳完就進到 end 事件中會用到 qsparse 來解析 formDataformData的內容是字串內容是

username=wordsmithampemail=wordsmith40somewhere

而 qsparse可以幫我們把這個 querystring轉成物件的格式也就是

username=wordsmithampemail=wordsmith40somewhere

一旦轉成物件並指定給 user之後其他的事情就和 GET方法時操作的一樣寫 response的表頭將內容回傳並將 userusername和 useremail代入到內容中

修改完成後接著執行 nodejs程式啟動 web server開啟瀏覽器進入表單測試看看POST的方式能否順利運作

完整程式碼如下

var http = require(rsquohttprsquo)

url = require(rsquourlrsquo)

fs = require(rdquofsrdquo)

qs = require(rsquoquerystringrsquo)

server

server = httpcreateServer(function (reqres)

var urlData

67 Express POST應用範例 61

Nodejs中文電子書

encode = rdquoutf8rdquo

filePath = rdquoviewexpress_post_example_formhtmlrdquo

formData

action

urlData = urlparse(requrltrue)

action = urlDatapathname

reswriteHead(200 rdquoContent-Typerdquordquotexthtml charset=utf-8rdquo)

if (action === rdquoSignuprdquo)

formData = rsquorsquo

reqon(rdquodatardquo function (data)

formData += data

)

reqon(rdquoendrdquo function ()

user = qsparse(formData)

resend(rdquolth1gtrdquo + userusername + rdquo 歡迎您的加入 lth1gtltpgt 我們已經將會員啟用信寄至rdquo + useremail + rdquoltpgtrdquo)

)

else

fsreadFile(filePath encode function(err file)

reswrite(file)

resend()

)

)

serverlisten(3000)

consolelog(rsquoServer 跑起來了現在時間是rsquo + new Date())

62 6 Express介紹

Nodejs中文電子書

68 Express AJAX應用範例

在 Nodejs要使用 Ajax傳送資料並且與之互動在接受資料的部份沒有太大的差別client端不是用 GET就是用 POST來傳資料重點在處理完後用 JSON格式回傳當然 Ajax不見得只傳 JSON格式有時是回傳一段 HTML碼不過後者對伺服器來說基本上就和前兩篇沒有差別了所以我們還是以回傳 JSON做為這一回的主題

這一回其實大多數的工作都會落在前端 Ajax上面前端要負責發送與接收資料並在接收資料後撤掉原先發送資料的表單並將取得的資料

改成 HTML格式之後放上頁面

首先先準備 HTML靜態頁面資料

ltDOCTYPE htmlgt

lthtmlgt

ltheadgt

lttitlegtNodejs 菜鳥筆記 (3)lttitlegt

ltlink rel=rdquostylesheetrdquo href=rdquocssstylecssrdquo type=rdquotextcssrdquo media=rdquoallrdquo gt

ltscript src=rdquohttpsajaxgoogleapiscomajaxlibsjquery171jqueryminjsrdquogtltscriptgt

ltheadgt

ltbodygt

lth1gtNodejs 菜鳥筆記-註冊 (用 Ajax)lth1gt

ltform id=rdquosignuprdquo action=rdquoSignuprdquo method=rdquoPOSTrdquogt

ltlabelgt 使用者名稱ltlabelgtltinput type=rdquotextrdquo id=rdquousernamerdquo name=rdquousernamerdquo gtltbrgt

ltlabelgt 電子郵件ltlabelgtltinput type=rdquotextrdquo id=rdquoemailrdquo name=rdquoemailrdquo gtltbrgt

ltinput type=rdquosubmitrdquo value=rdquo 註冊我的帳號rdquo gtltbrgt

ltformgt

ltdiv id=rdquoresultrdquogt

ltdivgt

ltscript type=rdquotextjavascriptrdquogt

$(function()$(rsquosignup input[type=rdquosubmitrdquo]rsquo)click(function(e)

var user =

userusername = $(rdquousernamerdquo)val()useremail = $(rdquoemailrdquo)val()

$post(rdquoSignuprdquouserfunction(data)greet(data)

)

68 Express AJAX應用範例 63

Nodejs中文電子書

epreventDefault()

)

var greet = function(msg)

var resultNode = $(rsquoresultrsquo)greeting = $(rsquolth2gtHirsquo+msgusername+rsquo 你的會員 id 是rsquo+ msgid +rsquo 我們會將會員啟動信寄至rsquo+msgemail+rsquolth2gtrsquo)

$(rsquosignuprsquo)hide()resultNodehtml(rdquordquo)

resultNodeappend(greeting)

)

ltscriptgt

ltbodygt

lthtmlgt

HTML頁面上準備了一個表單用來傳送註冊資料接著直接引用了Google CDN來載入 jQuery用來幫我們處理 Ajax的工作這次要傳送和接收的工作很大的變動都在 HTML頁面上的 JavaScript當中我們要做的事有 (相關 jQuery處理這邊不多做贅述指提起主要功能解說)

bull 用 jQUery取得 submit按鈕綁定它的 click動作

bull 取得表單 username和 email的值存放在 user這個物件中

bull 用 jQuery的 $post方法將 user的資料傳到 Server

bull 一旦成功取得資料後透過 greet這個 function組成回報給 user的訊息

bull 清空原本給使用者填資料的表單

bull 將 Server回傳的 usernameemail和 id這 3個資料組成回應的訊息

bull 將訊息放到原本表單的位置

經過以上的處理後一個 Ajax的表單的基本功能已經完成

接著進行 nodejs主要程式的編輯部分程式碼如下

var fs = require(rdquofsrdquo)

64 6 Express介紹

Nodejs中文電子書

qs = require(rsquoquerystringrsquo)

if (action === rdquoSignuprdquo)

formData = rsquorsquo

reqon(rdquodatardquo function (data)

formData += data

)

reqon(rdquoendrdquo function ()

var msg

user = qsparse(formData)

userid = rdquo123456rdquo

msg = JSONstringify(user)

reswriteHead(200 rdquoContent-Typerdquordquoapplicationjson charset=utf-8rdquordquoContent-Lengthrdquomsglength)

resend(msg)

)

這裡的程式和前面 POST範例基本上大同小異差別在

bull 幫 user的資料加上 id隨意存放一些文字進去讓 Server回傳的資料多於 Client端傳上來的不然會覺得 Server都沒做事

bull 增加了 msg 這個變數存放將 user 物件 JSON 文字化的結果JSONstringify這個轉換函式是 V8引擎所提供的如果你好奇的話

bull 大重點來了我們要告訴 Client端這次回傳的資料格式是 JSON所在 Content-type和 Content-Length要提供給 Client

Server很輕鬆就完成任務了最後進行程式測試啟動 nodejs主程式之後開啟瀏覽器就會看到表單填寫完畢按下送出就可以看到結果了

最後 nodejs本篇範例程式碼如下

var http = require(rsquohttprsquo)

url = require(rsquourlrsquo)

fs = require(rdquofsrdquo)

qs = require(rsquoquerystringrsquo)

server

68 Express AJAX應用範例 65

Nodejs中文電子書

server = httpcreateServer(function (reqres)

var urlData

encode = rdquoutf8rdquo

filePath = rdquoviewexpress_ajax_example_formhtmlrdquo

formData

action

urlData = urlparse(requrltrue)

action = urlDatapathname

if (action === rdquoSignuprdquo)

formData = rsquorsquo

reqon(rdquodatardquo function (data)

formData += data

)

reqon(rdquoendrdquo function ()

var msg

user = qsparse(formData)

userid = rdquo123456rdquo

msg = JSONstringify(user)

reswriteHead(200 rdquoContent-TyperdquordquoapplicationjsonrdquordquoContent-Lengthrdquomsglength)

resend(msg)

)

else

fsreadFile(filePath encode function(err file)

reswriteHead(200 rdquoContent-Typerdquordquotexthtml charset=utf-8rdquo)

reswrite(file)

resend()

)

)

serverlisten(3000)

66 6 Express介紹

Nodejs中文電子書

consolelog(rsquoServer 跑起來了現在時間是rsquo + new Date())

69 原始資料提供

bull [NodeJS初學者筆記 (1)-用 GET傳送資料] (httpithelpithomecomtwquestion10087402)

bull [NodeJS初學者筆記 (2)-用 POST傳送資料] (httpithelpithomecomtwquestion10087489)

bull [NodeJS 初學者筆記 (3)-用 Ajax 傳送資料] (httpithelpithomecomtwquestion10087627)

69 原始資料提供 67

Nodejs中文電子書

68 6 Express介紹

7

CoffeeScript

bull Spine

bull Hem

bull Spineapp

Letrsquos Have a Cup of CoffeeScript

bull es5-shim ECMAScript 5 compatibility shims for legacy JavaScript engines

ESnext

node-iform - A middleware for node-validator httpsgithubcomguileennode-iform

69

Nodejs中文電子書

70 7 CoffeeScript

8

製作一個 Hubot的 Plurk Adapter

81 應用事項提醒

此應用範例以CoffeeScript編寫主要程式架構採用Github釋放的Hubot專案以下有幾個主要注意事項請讀者注意

bull Hubot 一開始的架構除了內建的兩個 Adapter 之外其餘都要以Nodejs的Module方式才能運作

bull Hubot的 binhubot裡面寫著 npm install所以不管你怎麼改原始碼也不能改變第一點的狀況

其實上述都在討論同一件事情NodeJS的模組而且模組不能設定相對路徑之類的來安裝一定要包裝成 npm相符和格式才有辦法正確執行

82 建立 Adapter

首先需要準備兩個檔案

bull packagejson

bull plurkcoffee

71

Nodejs中文電子書

有這兩個就足以變成 NodeJS的模組了在開始 Coding之前先來設定一下 packagejson相依性

rdquonamerdquo rdquohubot-plurkrdquo

rdquoversionrdquo rdquo011rdquo

rdquomainrdquo rdquoplurkrdquo

rdquodependenciesrdquo

rdquohubotrdquo rdquogt=205rdquo

rdquooauthrdquo rdquordquo

rdquocronrdquordquordquo

這邊會使用到NodeJS的 cron模組(Module)而登入 Plurk需要OAuth標準授權才行所以也需要加載 OAuth模組

注意Hubot在讀取非內建模組時會自動在前面加上 hubot-的前置

83 建立 Robot跟 API

本篇應用基本上程式碼大部分參考 Hubot - Twitter Adapter來製作主要差異只有在使用 OAuth這個模組Twitter有 Streaming可用而 Plurk則得用 Comet方式來達到即時讀取

plurkcoffee程式碼

Robot = require(rdquohubotrdquo)robot()

Adapter = require(rdquohubtordquo)adapter()

EventEmitter = require(rdquoeventsrdquo)EventEmitter

oauth = require(rdquooauthrdquo)

cronJob = require(rdquocronrdquo)CronJob

class Plurk exntends Adapter

class PlurkStreaming exnteds EventEmitter

72 8 製作一個 Hubot的 Plurk Adapter

Nodejs中文電子書

先弄個基本架構主要 Class皆參考 Twitter Adapter命名方式接著再將 Plurk這個 Class增加幾個Method基本上只要有 run send reply就夠了而 run用來做初始化的部分

class Plurk entends Adapter

send (plurk id strings⋯) -gt

reply (plurk id strings⋯) -gt

run -gt

看起來有點東西接著來處理主要的 Plurk API結合部分

class PlurkStreaming extends EventEmitter

consuctor (options) -gt

plurk (callback) -gt

觀察河道

getChannel -gt

取得 Comet 網址

reply (plurk id message) -gt

回噗

acceptFriends -gt

接受好友

get (path callback) -gt

GET 請求

post (path body callback)-gt

POST 請求(其實是裝飾)

request (method path body callback)-gt

主要的 OAuth 請求

comet (server callback)-gt

噗浪的 Comet 傳回是 JavaScript Callback 要另外處理後才會變成 JSON

然後把注意力集中到 constructor上先把建構子弄好

constructor (options) -gt

super()

if optionskey and optionssecret and optionstoken and optionstoken secret

key = optionskey

secret = optionssecret

token = optionstoken

token secret = optionstoken secret

83 建立 Robot跟 API 73

Nodejs中文電子書

建立 OAuth 連接

consumer = new oauthOAuth(

rdquohttpwwwplurkcomOAuthrequest tokenrdquo

rdquohttpwwwplurkcomOAuthaccess tokenrdquo

key

secret

rdquo10rdquo

rdquohttpwwwplurkcomOAuthauthorizerdquo

rdquoHMAC-SHA1rdquo

)

domain = rdquowwwplurkcomrdquo

初始化取得 Comet 網址

do getChannel

else

throw new Error(rdquo 參數不足需要 Key Secret Token Token Secretrdquo)

接著來處理 request這個 method

request (method path body callback) -gt

記錄一下這次的 Request

consolelog(rdquohttpdomainpathrdquo)

Callback 這邊先不丟進去要用另一種方式處理

request = consumerget(rdquohttpdomainpathrdquo token token secret null)

requeston rdquoresponserdquo (res) -gt

reson rdquodatardquo (chunk) -gt

parseResponse(chunk+rsquorsquo callback)

reson rdquoendrdquo (data) -gt

consolelog rdquoEnd Request pathrdquo

reson rdquoerrorrdquo (data) -gt

consolelog rdquoError rdquo + data

requestend()

處理資料

parseResponse = (data callback) -gt

if datalength gt 0

用 TryCatch 避免處理 JSON 出錯導致整個中斷

try

callback null JSONparse(data)

catch err

74 8 製作一個 Hubot的 Plurk Adapter

Nodejs中文電子書

consolelog(rdquoError Parse JSONrdquo + data err)

繼續執行

callback null data ||

大致上就是這樣上面程式的架構已經將整個 Hubot Plurk Adapter完成因為在測試時竟然因為噗浪 Lag而沒讀到完整的 Comet資料然後造成程式異常為了避免這個問題發生因此需要加上為了完美呈現需要再

加上 Comet的處理所以要使用到 EventEmitter的功能

comet (server callback) -gt

在 Callback 裡面會找不到自身所以設定區域變數

self =

記錄一下這次的 Request

consolelog(rdquo[Comet] serverrdquo)

Callback 這邊先不丟進去要用另一種方式處理

request = consumerget(rdquohttpdomainpathrdquo token token secret null)

requeston rdquoresponserdquo (res) -gt

reson rdquodatardquo (chunk) -gt

parseResponse(chunk+rsquorsquo callback)

reson rdquoendrdquo (data) -gt

consolelog rdquoEnd Request pathrdquo

請求結束發出事件通知可以進行下一次請求

selfemit rdquonextPlurkrdquo

reson rdquoerrorrdquo (data) -gt

consolelog rdquoError rdquo + data

requestend()

處理資料

parseResponse = (data callback) -gt

if datalength gt 0

用 trycatch 避免失敗中斷

try

去掉 JavaScript 的 Callback

data = datamatch(CometChannelscriptCallback((+))s)

jsonData = rdquordquo

if data

83 建立 Robot跟 API 75

Nodejs中文電子書

jsonData = JSONparse(data[1])

else

如果沒有任何 Match 嘗試直接 parse

jsonData = JSONparse(data)

catch err

consolelog(rdquo[Comet] Errorrdquo data err)

用 TryCatch 避免處理 JSON 出錯導致整個中斷

try

只傳入 json 的 data 部分

callback null jsonDatadata

catch err

consolelog(rdquo[Comet]Error Parse JSONrdquo + data err)

繼續執行

callback null data ||

後面的 get跟 post就簡單多了

get (path callback) -gt

request(rdquoGETrdquo path null callback)

post (path body callback) -gt

request(rdquoPOSTrdquo path body callback)

接著處理取的 Comet網址的 getChannel

getChannel -gt

self =

get rdquoAPPRealtimegetUserChannelrdquo (error data) -gt

if error

檢查是否有 comet server

if datacomet server

selfchannel = datacomet server

如果沒有 Channel Ready 就嘗試連接會失敗

selfemit(rsquochannel readyrsquo)

那麼先來處理 Plurk Adaper好處理的部份

send (plruk id strings⋯)-gt

跟 Reply 一樣直接交給 reply 做

reply plurk id strings⋯

76 8 製作一個 Hubot的 Plurk Adapter

Nodejs中文電子書

reply (plurk id strings⋯) -gt

stringsforEach (message) =gt

botreply(plruk id message)

接著把 run處理好就可以上線運作摟

run -gt

self =

options =

key processenvHUBOT PLURK KEY

secret processenvHUBOT PLURK SECRET

token processenvHUBOT PLURK TOKEN

token secret processenvHUBOT PLURK TOKEN SECRET

創建剛剛的 API

bot = new PlurkStreaming(options)

依照 Twitter 的 new RobotTextMessage 會沒有反應所以參考 hubot-minecraft 的方式

r = robotconstructor

處理噗浪河道訊息

doPlurk = (data)-gt

檢查是否為回噗

if dataresponse

datacontent raw = dataresponsecontent raw

datauser id = dataresponseuser id

確定有噗浪 ID 跟訊息

if dataplurk id and datacontent raw

selfreceive new rTextMessage(dataplurk id datacontent raw)

取得 Comet Server 完成開始第一次 Comet 連接

boton rdquochannel readyrdquo () -gt

botplurk selfdoPlurk

上一次 Comet 完成繼續 Polling

boton rdquonextPlurkrdquo ()-gt

botplurk selfdoPlurk

定時接受好友邀請

do botacceptFriends

bot = bot

83 建立 Robot跟 API 77

Nodejs中文電子書

終於完成 AdapterHubot專案裡面的 scripts資料夾內是互動部分不需要像 Adapter如此大費周章處理只新增檔案並且設計好對白之後就會回噗了我開發用的機器人在此大家可以去跟他玩玩[httpplurkcomelct9620 bot](httpplurkcomelct9620 bot)

84 原始資料提供

bull [製 作 一 個 Hubot 的 噗 浪 Adapter](http revo-skillfrosttw blog20120318create-a-hubot-plurk-adapter)

78 8 製作一個 Hubot的 Plurk Adapter

9

Nodejs好用工具介紹

91 logio

Real-time log monitoring in your browser

httplogioorg

nodejs (gt=0412 lt=0611)

npm config set unsafe-perm true

npm install -g --prefix=usrlocal logio

logio server start

httplocalhost8998

79

Nodejs中文電子書

80 9 Nodejs好用工具介紹

10

精選文章

101 Express

bull 使用 NodeJS + Express從 GETPOST Request取值 [邀稿中]

bull link2 [邀稿中]

bull link3 [邀稿中]

81

Nodejs中文電子書

82 10 精選文章

11

參考資源

111 Nodejs書籍

bull Programming Nodejs April 2012 (est) Travis Swicegood Pragmatic Bookshelf

bull e Little Book on CoffeeScript January 2012 Alex MacCaw OrsquoReilly Media

bull Node for Front-End Developers January 2012 Garann Means OrsquoReilly Media

bull Building Hypermedia APIs with HTML5 and Node November 2011 MikeAmundsen OrsquoReilly Media

bull Node Web Development August 2011 David Herron PacktPub

bull CoffeeScript Accelerated JavaScript Development July 2011 Trevor BurnhamPragmatic Bookshelf

bull Getting Started with GEO CouchDB and Nodejs July 2011 Mick omp-son OrsquoReilly Media

bull What Is Node July 2011 Brett McLaughlin OrsquoReilly Media

bull Node Up and Running May 2011 Tom Hughes-Croucher Mike WilsonOrsquoReilly Media

83

Nodejs中文電子書

bull Hands-on Nodejs e Nodejs introduction and API reference May 2011 Pe-dro Teixeira LeanPub

bull e Node Beginner Book Manuel Kiessling

bull Mastering Nodejs visionmedia

112 Nodejs影音教學

bull e Node Sessions e Best of OSCON 2011 August 2011 OrsquoReilly Media(Video)

bull Tom Hughes-Croucher on Node March 2011 OrsquoReilly Media (Video)

113 Nodejs教學網站

bull Node Tuts

114 Nodejs課程

bull Introduction To Nodejs Van Nguyen CodeLesson

84 11 參考資源

Page 34: Nodejs Wiki

Nodejs中文電子書

28 3 Nodejs安裝與設定

4

Nodejs基礎

前篇文章已經由介紹安裝至設定都有完整介紹nodeJS 內部除了javascript常用的函式 (function)物件 (object)之外也有許多不同的自訂物件nodeJS預設建立這些物件為核心物件是為了要讓開發流程更為這些資料在官方文件已經具有許多具體說明接下來將會介紹在開發 nodeJS程式時常見的物件特性與使用方法

41 nodejs http伺服器建立

在 lsquonodejs官方網站 lthttpnodejsorggtlsquo裡面有舉一個最簡單的 HTTP伺服器建立一開始初步就是建立一個伺服器平台讓 nodejs可以與瀏覽器互相行為每種語言一開始的程式建立都是以 Hello world開始最初也從 Hello world帶各位進入 nodejs的世界

輸入以下程式碼儲存檔案為 node basic http hello worldjs

var server

ip = rdquo127001rdquo

port = 1337

http = require(rsquohttprsquo)

server = httpcreateServer(function (req res)

29

Nodejs中文電子書

reswriteHead(200 rsquoContent-Typersquo rsquotextplainrsquo)

resend(rsquoHello Worldnrsquo)

)

serverlisten(port ip)

consolelog(rdquoServer running at httprdquo + ip + rdquordquo + port)

程式碼解講一開始需要有幾個基本的變數

bull ip 機器本身的 ip位置因為使用本地端因此設定為 127001

bull port 需要開通的阜號通常設定為 http port 80因範例不希望與基本 port相衝隨意設定為 1337

在 nodejs 的程式中有許多預設的模組可以使用因此需要使用require方法將模組引入在這邊我們需要使用 http這個模組因此將 http載入Http模組裡面內建有許多方法可以使用這邊採用 createServer創建一個基本的 http伺服器再將 http伺服器給予一個 server變數裡面的回呼函式 (call back function)可以載入 http伺服器的資料與回應方法 (requestresponse)在程式裡面就可以看到我們直接回應給瀏覽器端所需的 Header回應內容

reswriteHead(200 rsquoContent-Typersquo rsquotextplainrsquo)

resend(rsquoHello Worldnrsquo)

Http伺服器需要設定 port ip在最後需要設定 Http監聽需要使用到listen事件監聽所有 Http伺服器行為

httplisten(port ip)

所有事情都完成之後需要確認伺服器正確執行因此使用 console在javascript裡就有這個原生物件console所印出的資料都會顯示於 nodejs伺服器頁面這邊印出的資料並不會傳送到使用者頁面上之後許多除壞

(debug)都會用到 console物件

consolelog(rdquoServer running at httprdquo + ip + rdquordquo + port)

30 4 Nodejs基礎

Nodejs中文電子書

42 nodejs http路徑建立

前面已經介紹如何建立一個簡單的 http伺服器接下來本章節將會介紹如何處理伺服器路徑 (rout)問題在 http的協定下所有從瀏覽器發出的要求 (request)都需要經過處理路徑上的建立也是如此

路徑就是指伺服器 ip位置或者是網域名稱之後對於伺服器給予的要求修改剛才的 hello world檔案修改如下

server = httpcreateServer(function (req res)

consolelog(requrl)

reswriteHead(200 rsquoContent-Typersquo rsquotextplainrsquo)

resend(rsquohello worldnrsquo)

)

重新啟動 nodejs程式後在瀏覽器端測試一下路徑行為結果如下圖

當在瀏覽器輸入 http1270011337test 在伺服器端會收到兩個要求一個是我們輸入的test要求另外一個則是faviconicotest的路徑要求http伺服器本身需要經過程式設定才有辦法回應給瀏覽器端所需要的回應在伺服器中所有的路徑要求都是需要被解析才有辦法取得資料從

42 nodejs http路徑建立 31

Nodejs中文電子書

上面解說可以了解到在 nodejs當中所有的路徑都需要經過設定未經過設定的路由會讓瀏覽器無法取得任何資料導致錯誤頁面的發生底下將會解

說如何設定路由同時避免發生錯誤情形先前 nodejs程式需要增加一些修改才能讓使用者透過瀏覽器在不同路徑時有不同的結果根據剛才

的程式做如下的修改

var server

ip = rdquo127001rdquo

port = 1337

http = require(rsquohttprsquo)

url = require(rsquourlrsquo)

path

server = httpcreateServer(function (req res)

path = urlparse(requrl)

reswriteHead(200 rsquoContent-Typersquo rsquotextplainrsquo)

switch (pathpathname)

case rdquoindexrdquo

resend(rsquoI am indexnrsquo)

break

case rdquotestrdquo

resend(rsquothis is test pagenrsquo)

break

default

resend(rsquodefault pagenrsquo)

break

)

serverlisten(port ip)

consolelog(rdquoServer running at httprdquo + ip + rdquordquo + port)

程式做了片段的修改首先載入 url模組另外增加一個 path變數url模組就跟如同他的命名一般專門處理 url字串處理裡面提供了許多方法來解決路徑上的問題因為從瀏覽器發出的要求路徑可能會帶有多種需求

或者 GET參數組合等因此我們需要將路徑單純化取用路徑部分的資料即可例如使用者可能會送出 http1270011337testsend=1如果直接

32 4 Nodejs基礎

Nodejs中文電子書

信任 requrl就會收到結果為testsend=1所以需要透過 url模組的方法將路徑資料過濾

在這邊使用 urlparse的方法裡面帶入網址格式資料會回傳路徑資料為了後需方便使用將回傳的資料設定到 path變數當中在回傳的路徑資料裡面包含資訊如下圖

這邊只需要使用單純的路徑要求直接取用 pathpathname就可以達到我們的目的

最後要做路徑的判別在不同的路徑可以指定不同的輸出在範例中

有三個可能結果第一個從瀏覽器輸入index就會顯示 index結果test 就會呈現出 test頁面最後如果都不符合預期的輸入會直接顯示 default的頁面最後的預防可以讓瀏覽器不會出現非預期結果讓程式的可靠性提昇

底下為測試結果

42 nodejs http路徑建立 33

Nodejs中文電子書

43 nodejs檔案讀取

前面已經介紹如何使用路由(rount)做出不同的回應實際應用只有在瀏覽器只有輸出幾個文字資料總是不夠的在本章節中將介紹如何使

用檔案讀取輸出檔案資料讓使用者在前端瀏覽器也可以讀取到完整的

html css javascript檔案輸出

檔案管理最重要的部分就是 lsquoFile system lthttpnodejsorgdocslatestapifshtmlgtlsquo這個模組此模組可以針對檔案做管理監控讀取等行為裡面有許多預設的方法底下是檔案輸出的基本範例底下會有兩個檔案

第一個是靜態 html檔案

ltDOCTYPE htmlgt

lthtml xmlns=rdquohttpwwww3org1999xhtmlrdquo xmllang=rdquozh-CNrdquo lang=rdquozh-CNrdquogt

ltheadgt

ltmeta http-equiv=rdquoContent-Typerdquo content=rdquotexthtml charset=UTF-7rdquo gt

lttitlegtnodejs index html filelttitlegt

ltheadgt

ltbodygt

34 4 Nodejs基礎

Nodejs中文電子書

lth1gtnodejs index html filelth1gt

ltbodygt

lthtmlgt

另一個為 nodejs程式

var fs = require(rdquofsrdquo)

filename = rdquostaticindexhtmlrdquo

encode = rdquoutf8rdquo

fsreadFile(filename encode function(err file)

consolelog(file)

)

一開始直接載入 le system模組 載入名稱為 fs讀取檔案主要使

用的方法為 readFile裡面以三個參數路徑 ( le path) 編碼方式 (encoding)

回應函式 (callback)路徑必須要設定為靜態 html所在位置才能指定到正確的檔案靜態檔案的編碼方式也必須正確這邊使用靜態檔案的編

碼為 utf8如果編碼設定錯誤nodejs讀取出來檔案結果會使用 byte raw格式輸出如果錯誤編碼格式會導致輸出資料為 byte raw

回應函式中裡面會使用兩個變數error為錯誤資訊如果讀取的檔案不存在或者發生錯誤error數值會是 true如果成功讀取資料 error將會是 falsecontent則是檔案內容資料讀取後將會把資料全數丟到 content這個變數當中

最後程式的輸出結果畫面如下

43 nodejs檔案讀取 35

Nodejs中文電子書

44 nodejs http靜態檔案輸出

前面已經了解如何讀取本地端檔案接下來將配合 http伺服器路由讓每個路由都能夠輸出相對應的靜態 html檔案首先新增加幾個靜態 html檔案

ltDOCTYPE htmlgt

lthtml xmlns=rdquohttpwwww3org1999xhtmlrdquo xmllang=rdquozh-CNrdquo lang=rdquozh-CNrdquogt

ltheadgt

ltmeta http-equiv=rdquoContent-Typerdquo content=rdquotexthtml charset=UTF-7rdquo gt

lttitlegtnodejs index html filelttitlegt

ltheadgt

ltbodygt

lth1gtnodejs index html filelth1gt

ltbodygt

lthtmlgt

ltDOCTYPE htmlgt

lthtml xmlns=rdquohttpwwww3org1999xhtmlrdquo xmllang=rdquozh-CNrdquo lang=rdquozh-CNrdquogt

ltheadgt

ltmeta http-equiv=rdquoContent-Typerdquo content=rdquotexthtml charset=UTF-7rdquo gt

lttitlegtnodejs test html filelttitlegt

ltheadgt

ltbodygt

lth1gtnodejs test html filelth1gt

ltbodygt

lthtmlgt

ltDOCTYPE htmlgt

lthtml xmlns=rdquohttpwwww3org1999xhtmlrdquo xmllang=rdquozh-CNrdquo lang=rdquozh-CNrdquogt

ltheadgt

ltmeta http-equiv=rdquoContent-Typerdquo content=rdquotexthtml charset=UTF-7rdquo gt

lttitlegtnodejs static html filelttitlegt

ltheadgt

ltbodygt

lth1gtnodejs static html filelth1gt

ltbodygt

lthtmlgt

36 4 Nodejs基礎

Nodejs中文電子書

準備一個包含基本路由功能的 http伺服器

var server

ip = rdquo127001rdquo

port = 1337

http = require(rsquohttprsquo)

url = require(rsquourlrsquo)

server = httpcreateServer(function (req res)

var path = urlparse(requrl)

)

serverlisten(port ip)

consolelog(rdquoServer running at httprdquo + ip + rdquordquo + port)

加入 le system 模組使用 readFile 的功能將這一段程式放置於

createServer的回應函式中

fsreadFile(filePath encode function(err file)

)

readFile的回應函式裡面加入頁面輸出讓瀏覽器可以正確讀到檔案在這邊我們設定讀取的檔案為 html 靜態檔案所以 Content-type 設定為texthtml讀取到檔案的內容將會正確輸出成 html靜態檔案

fsreadFile(filePath encode function(err file)

reswriteHead(200 rsquoContent-Typersquo rsquotexthtmlrsquo)

reswrite(file)

resend()

)

到這邊為止基本的程式內容都已經完成剩下一些細節的調整首先

路徑上必須做調整目前的靜態檔案全部都放置於 static資料夾底下設定一個變數來記住資料夾位置

接著將瀏覽器發出要求路徑與資料夾組合讀取正確 html靜態檔案使用者有可能會輸入錯誤路徑所以在讀取檔案的時候要加入錯誤處理

同時回應 404伺服器無法正確回應的 http header格式

加入這些細節的修改一個基本的 http 靜態 html輸出伺服器就完成了完整程式碼如下

44 nodejs http靜態檔案輸出 37

Nodejs中文電子書

var server

ip = rdquo127001rdquo

port = 1337

http = require(rsquohttprsquo)

fs = require(rdquofsrdquo)

folderPath = rdquostaticrdquo

url = require(rsquourlrsquo)

path

filePath

encode = rdquoutf8rdquo

server = httpcreateServer(function (req res)

path = urlparse(requrl)

filePath = folderPath + pathpathname

fsreadFile(filePath encode function(err file)

if (err)

reswriteHead(404 rsquoContent-Typersquo rsquotextplainrsquo)

resend()

return

reswriteHead(200 rsquoContent-Typersquo rsquotextapplicationrsquo)

reswrite(file)

resend()

)

)

serverlisten(port ip)

consolelog(rdquoServer running at httprdquo + ip + rdquordquo + port)

45 nodejs http GET資料擷取

http伺服器中除了路由之外另一個最常使用的方法就是擷取 GET資料本單元將會介紹如何透過基本 http伺服器擷取瀏覽器傳來的要求擷取 GET資料

在 http協定中GET參數都是藉由 URL從瀏覽器發出要求送至伺服器

38 4 Nodejs基礎

Nodejs中文電子書

端基本的傳送網址格式可能如下

http127001testsend=1amptest=2

上面這段網址裡面的 GET參數就是 send而這個變數的數值就為 1如果想要在 http伺服器取得 GET資料需要在瀏覽器給予的要求 (request)做處理

首先需要載入 query string這個模組這個模組主要是用來將字串資料

過濾後轉換成javascript物件

qs = require(rsquoquerystringrsquo)

接著在第一階段利用 url模組過濾瀏覽器發出的 URL資料後將回應的物件裡面的 query這個變數是一個字串值資料過濾後如下

send=1amptest=2

透過 query string使用 parse這個方法將資料轉換成 javascript物件就表示 GET的資料已經被伺服器端正式擷取下來

path = urlparse(requrl)

parameter = qsparse(pathquery)

整個 nodejs http GET參數完整擷取程式碼如下

var server

ip = rdquo127001rdquo

port = 1337

http = require(rsquohttprsquo)

qs = require(rsquoquerystringrsquo)

url = require(rsquourlrsquo)

server = httpcreateServer(function (req res)

var path = urlparse(requrl)

parameter = qsparse(pathquery)

consoledir(parameter)

reswriteHead(200 rsquoContent-Typersquo rsquotextplainrsquo)

reswrite(rsquoBrowser test GET parameternrsquo)

resend()

)

45 nodejs http GET資料擷取 39

Nodejs中文電子書

serverlisten(port ip)

consolelog(rdquoServer running at httprdquo + ip + rdquordquo + port)

程式運作之後由瀏覽器輸入要求網址之後nodejs伺服器端回應資料為

Server running at http1270011337

send rsquo1rsquo test rsquo1rsquo

46 本章結語

前面所解說的部份一大部分主要是處理 http伺服器基本問題雖然在某些部分有牽扯到 http 伺服器基本運作原理主要還是希望可以藉由這些基本範例練習 nodejs練習回應函式與語法串接的特點習慣編寫javascript風格程式當然直接這樣開發 nodejs是非常辛苦的接下來在模組實戰開發的部份將會介紹特定的模組一步一步帶領各位從無到有進行

nodejs應用程式開發

40 4 Nodejs基礎

5

NPM套件管理工具

npm全名為 Node Package Manager是 Nodejs的套件(package)管理工具類似 Perl的 ppm或 PHP的 PEAR等安裝 npm後使用npm install

module name指令即可安裝新套件維護管理套件的工作會更加輕鬆

npm可以讓Nodejs的開發者直接利用擴充線上的套件庫(packagesregistry)加速軟體專案的開發npm提供很友善的搜尋功能可以快速找到安裝需要的套件當這些套件發行新版本時npm也可以協助開發者自動更新這些套件

npm不僅可用於安裝新的套件它也支援搜尋列出已安裝模組及更新的功能

51 安裝 NPM

Nodejs在 063版本開始內建 npm讀者安裝的版本若是此版本或更新的版本就可以略過以下安裝說明

若要檢查 npm是否正確安裝可以使用以下的指令

npm -v

41

Nodejs中文電子書

執行結果說明

若 npm正確安裝執行 npm -v將會看到類似 110-2的版本訊息

若讀者安裝的 Nodejs版本比較舊或是有興趣嘗試自己動手安裝 npm工具則可以參考以下的說明

安裝於Windows系統

Nodejs for Windows 於 062 版開始內建 npm使用 nodejsorg 官方提供的安裝程式不需要進一步的設定就可以立即使用 npm指令對於Windows的開發者來說大幅降低環境設定的問題與門檻

除了使用 Nodejs內建的 npm讀者也可以從 npm官方提供的以下網址

httpnpmjsorgdist

這是由 npm提供的 Fancy Windows Install版本請下載壓縮檔(例如npm-110-3zip)並將壓縮檔內容解壓縮至 Nodejs的安裝路徑(例如CProgram Filesnodejs)

解壓縮後在 Nodejs的安裝路徑下應該有以下的檔案及資料夾

bull npmcmd(檔案)

bull node modules(資料夾)

安裝於 Linux系統

Ubuntu Linux的使用者可以加入 NPM Unoffcial PPA這個 repository即可使用 apt-get完成 npm安裝

42 5 NPM套件管理工具

Nodejs中文電子書

Ubuntu Linux使用 apt-get安裝 npm

sudo apt-get install python-software-properties

sudo add-apt-repository ppagias-kay-leenpm

sudo apt-get update

sudo apt-get install npm

npm官方提供的安裝程式 installsh可以適用於大多數的 Linux系統使用這個安裝程式請先確認

1 系統已安裝 curl工具(請使用 curl --version查看版本訊息)

2 已安裝 Nodejs並且 PATH正確設置

3 Nodejs的版本必須大於 04x

以下為 npm提供的安裝指令

curl httpnpmjsorginstallsh | sh

安裝成功會看到如下訊息

installsh安裝成功的訊息

npm10105 homeUSERNAMElocalnodelibnode_modulesnpm

It worked

安裝於Mac OS X

建議採用與 Nodejs相同的方式進行 npm的安裝例如使用MacPorts安裝 Nodejs就同樣使用MacPorts安裝 npm這樣對日後的維護才會更方便容易

使用 MacPorts安裝 npm是本書比較建議的方式它可以讓 npm的安裝移除及更新工作自動化將會幫助開發者節省寶貴時間

51 安裝 NPM 43

Nodejs中文電子書

安裝MacPorts的提示

在MacPorts網站可以取得 OS X系統版本對應的安裝程式(例如 106或 107)httpwwwmacportsorg安裝過程會詢問系統管理者密碼使用預設的選項完成安裝即可安

裝MacPorts之後在終端機執行 port -v將會看到MacPorts的版本訊息

安裝 npm之前先更新MacPorts的套件清單以確保安裝的 npm是最新版本

sudo port -d selfupdate

接著安裝 npm

sudo port install npm

若讀者的 Nodejs並非使用MacPorts安裝則不建議使用MacPorts安裝npm因為 MacPorts會自動檢查並安裝相依套件而 npm相依 nodejs所以MacPorts也會一併將 nodejs套件安裝造成先前讀者使用其它方式安裝的 nodejs被覆蓋

讀者可以先使用 MacPorts安裝 curl(sudo port install curl)

再參考 Linux的 installsh安裝方式即可使用 npm官方提供的安裝程式

NPM安裝後測試

npm是指令列工具(command-line tool)使用時請先打開系統的文字終端機工具

測試 npm安裝與設定是否正確請輸入指令如下

npm -v

或是

npm --version

如果 npm已經正確安裝設定就會顯示版本訊息

44 5 NPM套件管理工具

Nodejs中文電子書

執行結果(範例)

110-2

52 使用 NPM安裝套件

npm目前擁有超過 6000種套件(packages)可以在 npm registry使用關鍵字搜尋套件

httpsearchnpmjsorg

舉例來說在關鍵字欄位輸入「coffee-script」下方的清單就會自動列出包含 coffee-script關鍵字的套件

接著我們回到終端機模式的操作npm的指令工具本身就可以完成套

件搜尋的任務

例如以下的指令同樣可以找出 coffee-script相關套件

npm search coffee-script

以下是搜尋結果的參考畫面

52 使用 NPM安裝套件 45

Nodejs中文電子書

找到需要的套件後(例如 express)即可使用以下指令安裝

npm install coffee-script

值得注意的一點是使用 npm install會將指定的套件安裝在工

作目錄(Working Directory)的 node modules資料夾下

以Windows為例如果執行 npm install的目錄位於

Cproject1

那麼 npm將會自動建立一個 node modules的子目錄(如果不存在)

Cproject1node modules

並且將下載的套件放置於這個子目錄例如

Cproject1node modulescoffee-script

這個設計讓專案可以個別管理相依的套件並且可以在專案佈署或發

行時將這些套件(位於 node modules)一併打包方便其它專案的使用者不必再重新下載套件

這個 npm install的預設安裝模式為 local(本地)只會變更當前專案的資料夾不會影響系統

另一種安裝模式稱為 global(全域)這種模式會將套件安裝到系統資

料夾也就是 npm安裝路徑的 node modules資料夾例如

CProgram Filesnodejsnode modules

46 5 NPM套件管理工具

Nodejs中文電子書

是否要使用全域安裝可以依照套件是否提供新指令來判斷舉例來說express 套件提供 express 這個指令而 coffee-script 則提供 coffee

指令

在 local 安 裝 模 式 中這 些 指 令 的 程 式 檔 案會 被 安 裝 到node modules的 bin這個隱藏資料夾下除非將bin的路徑加入 PATH環境變數否則要執行這些指令將會相當不便

為了方便指令的執行我們可以在 npm install 加上 -g 或 --

global參數啟用 global安裝模式例如

npm install -g coffee-script

npm install -g express

使用 global安裝模式需要注意執行權限的問題若權限不足可能會出現類似以下的錯誤訊息

npm ERR Error EACCES permission denied rsquorsquo

npm ERR

npm ERR Please try running this command again as rootAdministrator

要獲得足夠得執行權限請參考以下說明

bull Windows 7 或 2008 以上在「命令提示字元」的捷徑按右鍵選擇「以系統管理員身分執行」執行 npm指令時就會具有 Administrator身分

bull Mac OS X或 Linux系統可以使用 sudo指令例如

sudo npm install -g express

bull Linux系統可以使用 root權限登入或是以「sudo su -」切換成 root身分(使用 root權限操作系統相當危險因此並不建議使用這種方式)

若加上 -g參數使用 npm install -g coffee-script完成安裝

後就可以在終端機執行 coffee指令例如

coffee -v

52 使用 NPM安裝套件 47

Nodejs中文電子書

執行結果(範例)

CoffeeScript version 120

53 套件的更新及維護

除了前一節說明的 search 及 install 用法npm 還提供其他許多指令(commands)

使用 npm help可以查詢可用的指令

npm help

執行結果(部分)

where ltcommandgt is one of

adduser apihelp author bin bugs c cache completion

config deprecate docs edit explore faq find get

help help-search home i info init install la link

list ll ln login ls outdated owner pack prefix

prune publish r rb rebuild remove restart rm root

run-script s se search set show star start stop

submodule tag test un uninstall unlink unpublish

unstar up update version view whoami

使用 npm help command可以查詢指令的詳細用法例如

npm help list

接下來本節要介紹開發過程常用的 npm指令

使用 list可以列出已安裝套件

npm list

48 5 NPM套件管理工具

Nodejs中文電子書

執行結果(範例)

-- coffee-script120

- express256

- connect185

| -- formidable108

-- mime124

-- mkdirp007

-- qs041

檢視某個套件的詳細資訊例如

npm show express

升級所有套件(如果該套件已發佈更新版本)

npm update

升級指定的套件

npm update express

移除指定的套件

npm uninstall express

54 使用 packagejson

對於正式的 Nodejs專案可以建立一個命名為 packagejson的設

定檔(純文字格式)檔案內容參考範例如下

54 使用 packagejson 49

Nodejs中文電子書

packagejson(範例)

rdquonamerdquo rdquoapplication-namerdquo

rdquoversionrdquo rdquo001rdquo

rdquoprivaterdquo true

rdquodependenciesrdquo

rdquoexpressrdquo rdquo255rdquo

rdquocoffee-scriptrdquo rdquolatestrdquo

rdquomongooserdquo rdquogt= 253rdquo

其中 name與 version依照專案的需求設置

需要注意的是 dependencies的設定它用於指定專案相依的套件名

稱及版本

bull rdquoexpressrdquo rdquo255rdquo

代表此專案相依版本 255的 express套件

bull rdquocoffee-scriptrdquo rdquolatestrdquo

使用最新版的 coffee-script套件(每次更新都會檢查新版)

bull rdquomongooserdquo rdquogt= 253rdquo

使用版本大於 253的 mongoose套件

假設某個套件的新版可能造成專案無法正常運作就必須指定套件的

版本避免專案的程式碼來不及更新以相容新版套件通常在開發初期的

專案需要盡可能維持新套件的相容性(以取得套件的更新或修正)可以

用「gt=」設定最低相容的版本或是使用「latest」設定永遠保持最新

套件

50 5 NPM套件管理工具

6

Express介紹

在前面的 nodejs基礎當中介紹許多許多開設 http的使用方法及介紹以及許多基本的 nodejs基本應用

接下來要介紹一個套件稱為 express [Express](httpexpressjscom)這個套件主要幫忙解決許多 nodejs http server所需要的基本服務讓開發 httpservice變得更為容易不需要像之前需要透過層層模組(module)才有辦法開始編寫自己的程式

這個套件是由 TJ Holowaychuk 製作而成的套件裡面包含基本的路由處理 (route)http資料處理(GETPOSTPUT)另外還與樣板套件(jshtml template engine)搭配同時也可以處理許多複雜化的問題

61 Express安裝

安裝方式十分簡單只要透過之前介紹的 NPM就可以使用簡單的指令安裝指令如下

這邊建議需要將此套件安裝成為全域模組方便日後使用

51

Nodejs中文電子書

62 Express基本操作

express的使用也十分簡單先來建立一個基本的 hello world

var app = require(rsquoexpressrsquo)createServer()

port = 1337

applisten(port)

appget(rsquorsquo function(req res)

ressend(rsquohello worldrsquo)

)

consolelog(rsquostart express servernrsquo)

可以從上面的程式碼發現基本操作與 nodejs http的建立方式沒有太大差異主要差在當我們設定路由時可以直接透過 appget方式設定回應與接受方式

63 Express路由處理

Express對於 http服務上有許多包裝讓開發者使用及設定上更為方便例如有幾個路由設定那我們就統一藉由 appget來處理

Create http server

appget(rsquorsquo function(req res)

ressend(rsquohello worldrsquo)

)

appget(rsquotestrsquo function(req res)

ressend(rsquotest renderrsquo)

)

appget(rsquouserrsquo function(req res)

ressend(rsquouser pagersquo)

)

52 6 Express介紹

Nodejs中文電子書

如上面的程式碼所表示appget可以帶入兩個參數第一個是路徑名稱設定第二個為回應函式 (call back function)回應函式裡面就如同之前的 createServer方法裡面包含 requestresponse兩個物件可供使用使用者就可以透過瀏覽器輸入不同的 url切換到不同的頁面顯示不同的結果

路由設定上也有基本的配對方式讓使用者從瀏覽器輸入的網址可以

是一個變數只要符合型態就可以有對應的頁面產出例如

Create http server

appget(rsquouseridrsquo function(req res)

ressend(rsquouser rsquo + reqparamsid)

)

appget(rsquonumberrsquo function(req res)

ressend(rsquonumber rsquo + reqparamsnumber)

)

裡 面 使 用 到number 從 網 址 輸 入 之 後 就 可 以 直 接 使 用reqparamsnumber 取得所輸入的資料變成 url 參數使用當然前面也是可以加上路徑的設定userid在瀏覽器上路徑必須符合userxxx透

過 reqparamsid就可以取到 xxx這個字串值

另外express參數處理也提供了路由參數配對處理也可以透過正規表示法作為參數設定

var app = require(rsquoexpressrsquo)createServer()

port = 1337

applisten(port)

appget(^ip((d23)((d23))((d23))((d23))) function(req res)

ressend(reqparams)

)

上面程式碼可以發現後面路由設定的型態是正規表示法裡面設定

格式為ip之後必須要加上 ip型態才會符合資料格式同時取得 ip資料已經由正規表示法將資料做分群因此可以取得 ip的四個數字

此程式執行之後可以透過瀏覽器測試輸入網址為 localhost3000ip25525510010可以從頁面獲得資料

63 Express路由處理 53

Nodejs中文電子書

此章節全部範例程式碼如下

overview

author Caesar Chi

blog clonnblogspotcom

version 20120226

create server

var app = require(rsquoexpressrsquo)createServer()

port = 1337

applisten(port)

normal style

appget(rsquorsquo function(req res)

ressend(rsquohello worldrsquo)

)

appget(rsquotestrsquo function(req res)

ressend(rsquotest renderrsquo)

)

parameter style

appget(rsquouseridrsquo function(req res)

ressend(rsquouser rsquo + reqparamsid)

)

appget(rsquonumberrsquo function(req res)

ressend(rsquonumber rsquo + reqparamsnumber)

)

REGX style

appget(^ip((d23)((d23))((d23))) function(req res)

ressend(reqparams)

)

appget(rsquorsquo function(req res)

ressend(rsquoPage not foundrsquo 404)

54 6 Express介紹

Nodejs中文電子書

)

consolelog(rsquostart express servernrsquo)

64 Express middleware

Express 裡面有一個十分好用的應用概念稱為 middleware可以透過middleware做出複雜的效果同時上面也有介紹 next方法參數傳遞就是靠 middleware的概念來傳遞參數讓開發者可以明確的控制程式邏輯

create http server

appuse(expressbodyParser())

appuse(expressmethodOverride())

appuse(expresssession())

上面都是一種 middleware的使用方式透過 appuse方式裡面載入函式執行方法回應函式會包含三個基本參數responserequestnext其中next表示下一個 middleware執行函式同時會自動將預設三個參數繼續帶往下個函式執行底下有個實驗

上面的片段程式執行後開啟瀏覽器連結上 localhost1337會發現伺服器回應結果順序如下

first middle ware

second middle ware

execute middle ware

end middleware function

從上面的結果可以得知剛才設定的 middleware都生效了在 appuse設定的 middleware是所有 url皆會執行方法如果有指定特定方法就可以使用 appget的 middleware設定在 appget函式的第二個參數就可以帶入函式或者是匿名函式只要函式裡面最後會接受 request response next這三個參數同時也有正確指定 next函式的執行時機最後都會執行到最後一個方法當然開發者也可以評估程式邏輯要執行到哪一個階段讓邏輯

可以更為分明

64 Express middleware 55

Nodejs中文電子書

65 Express路由應用

在實際開發上可能會遇到需要使用參數等方式混和變數一起使用

express裡面提供了一個很棒的處理方法 appall這個方式可以先採用基本路由配對再將設定為每個不同的處理方式開發者可以透過這個方式簡

化自己的程式邏輯

overview

author

version 20120226

create server

var app = require(rsquoexpressrsquo)createServer()

port = 1337

users = [

name rsquoClonnrsquo

name rsquoChirsquo

]

applisten(port)

appall(rsquouseridoprsquo function(req res next)

requser = users[reqparamsid]

if (requser)

next()

else

next(new Error(rsquocannot find user rsquo + reqparamsid))

)

appget(rsquouseridrsquo function(req res)

ressend(rsquoviewing rsquo + requsername)

)

appget(rsquouserideditrsquo function(req res)

ressend(rsquoediting rsquo + requsername)

)

56 6 Express介紹

Nodejs中文電子書

appget(rsquouseriddeletersquo function(req res)

ressend(rsquodeleting rsquo + requsername)

)

appget(rsquorsquo function(req res)

ressend(rsquoPage not foundrsquo 404)

)

consolelog(rsquostart express servernrsquo)

內部宣告一組預設的使用者分別給予名稱設定藉由 appall這個方法可以先將路由雛形建立再接下來設定 appget的路徑格式只要符合格式就會分配進入對應的方法中像上面的程式當中如果使用者輸入路徑為user0除了執行 appall程式之後執行 next方法就會對應到路徑設定為userid的這個方法當中如果使用者輸入路徑為user0edit就會執行到useridedit的對應方法

66 Express GET應用範例

我們準備一個使用 GET方法傳送資料的表單

ltDOCTYPE htmlgt

lthtmlgt

ltheadgt

ltmeta http-equiv=rdquoContent-Typerdquo content=rdquotexthtml charset=utf8rdquogt

lttitlegtNodejs 菜鳥筆記 (1)lttitlegt

ltlink rel=rdquostylesheetrdquo href=rdquocssstylecssrdquo type=rdquotextcssrdquo media=rdquoallrdquo gt

ltheadgt

ltbodygt

lth1gtNodejs 菜鳥筆記-註冊lth1gt

ltform id=rdquosignuprdquo method=rdquoGETrdquo action=rdquohttplocalhost3000Signuprdquogt

ltlabelgt 使用者名稱ltlabelgtltinput type=rdquotextrdquo id=rdquousernamerdquo name=rdquousernamerdquo gtltbrgt

ltlabelgt 電子郵件ltlabelgtltinput type=rdquotextrdquo id=rdquoemailrdquo name=rdquoemailrdquo gtltbrgt

ltinput type=rdquosubmitrdquo value=rdquo 註冊我的帳號rdquo gtltbrgt

ltformgt

ltbodygt

lthtmlgt

66 Express GET應用範例 57

Nodejs中文電子書

這個表單沒有什麼特別的地方我們只需要看第 9 行form 使用的method是 GET然後 action是rdquohttplocalhost3000Signupldquo等一下我們要來撰寫Signup這個 URL Path的處理程式

處理 Signup行為

我們知道所謂的 GET方法會透過 URL來把表單的值給帶過去以上面的表單來說到時候 URL會以這樣的形式傳遞

httplocalhost3000Signupusername=xxxampemail=xxx

所以要能處理這樣的資料必須有以下功能

bull 解析 URL

bull 辨別動作是 Signup

bull 解析出 username和 email

一旦能取得 username和 email的值程式就能加以應用了

處理 Signup的程式碼雛形

load module

var url = require(rsquourlrsquo)

urlData = urlparse(requrltrue)

action = urlDatapathname

reswriteHead(200 rdquoContent-Typerdquordquotexthtml charset=utf-8rdquo)

if (action === rdquoSignuprdquo)

user = urlDataquery

resend(rdquolth1gtrdquo + userusername + rdquo 歡迎您的加入 lth1gtltpgt 我們已經將會員啟用信寄至rdquo + useremail + rdquoltpgtrdquo)

首先需要加載 url module它是用來協助我們解析 URL的模組接著使用 urlparse方法第一個傳入 url字串變數也就是 requrl另外第二個參數的用意是設為 ture則引進 querystring模組來協助處理預設是 false它影響到的是 urlDataquery設為 true會傳回物件不然就只是一般的字串urlparse會將字串內容整理成一個物件我們把它指定給 urlData

action變數作為記錄 pathname這是我們稍後要來判斷目前網頁的動作是什麼接著先將 html表頭資訊 (Header)準備好再來判斷路徑邏輯如

58 6 Express介紹

Nodejs中文電子書

果是 Signup這個動作就把 urlDataquery裡的資料指定給 user然後輸出userusername和 useremail把使用者從表單註冊的資料顯示於頁面中

最後進行程式測試啟動 nodejs主程式之後開啟瀏覽器就會看到表單填寫完畢按下送出就可以看到結果了

完整 nodejs程式碼如下

var http = require(rsquohttprsquo)

url = require(rsquourlrsquo)

fs = require(rdquofsrdquo)

server

server = httpcreateServer(function (reqres)

var urlData

encode = rdquoutf8rdquo

filePath = rdquoviewexpress_get_example_formhtmlrdquo

action

urlData = urlparse(requrltrue)

action = urlDatapathname

reswriteHead(200 rdquoContent-Typerdquordquotexthtml charset=utf-8rdquo)

if (action === rdquoSignuprdquo)

user = urlDataquery

resend(rdquolth1gtrdquo + userusername + rdquo 歡迎您的加入 lth1gtltpgt 我們已經將會員啟用信寄至rdquo + useremail + rdquoltpgtrdquo)

else

fsreadFile(filePath encode function(err file)

reswrite(file)

resend()

)

)

serverlisten(3000)

consolelog(rsquoServer 跑起來了現在時間是rsquo + new Date())

66 Express GET應用範例 59

Nodejs中文電子書

67 Express POST應用範例

一開始準備基本的 html表單傳送內容以 POST方式form的 action屬性設定為 POST其餘 html內容與前一個範例應用相同

ltDOCTYPE htmlgt

lthtmlgt

ltheadgt

ltmeta http-equiv=rdquoContent-Typerdquo content=rdquotexthtml charset=utf8rdquogt

lttitlegtNodejs 菜鳥筆記 (1)lttitlegt

ltlink rel=rdquostylesheetrdquo href=rdquocssstylecssrdquo type=rdquotextcssrdquo media=rdquoallrdquo gt

ltheadgt

ltbodygt

lth1gtNodejs 菜鳥筆記-註冊lth1gt

ltform id=rdquosignuprdquo method=rdquoPOSTrdquo action=rdquohttplocalhost3000Signuprdquogt

ltlabelgt 使用者名稱ltlabelgtltinput type=rdquotextrdquo id=rdquousernamerdquo name=rdquousernamerdquo gtltbrgt

ltlabelgt 電子郵件ltlabelgtltinput type=rdquotextrdquo id=rdquoemailrdquo name=rdquoemailrdquo gtltbrgt

ltinput type=rdquosubmitrdquo value=rdquo 註冊我的帳號rdquo gtltbrgt

ltformgt

ltbodygt

lthtmlgt

nodejs的程式處理邏輯與前面 GET範例類似部分程式碼如下

qs = require(rsquoquerystringrsquo)

if (action === rdquoSignuprdquo)

formData = rsquorsquo

reqon(rdquodatardquo function (data)

formData += data

)

reqon(rdquoendrdquo function ()

user = qsparse(formData)

resend(rdquolth1gtrdquo + userusername + rdquo 歡迎您的加入 lth1gtltpgt 我們已經將會員啟用信寄至rdquo + useremail + rdquoltpgtrdquo)

)

60 6 Express介紹

Nodejs中文電子書

主要加入了rsquoquerystringrsquo這個moduel方便我們等一下解析由表單 POST回來的資料另外加入一個 formData的變數用來搜集待等一下表單回傳的資料前面的 GET範例我們只從 req拿出 url的資料這次要在利用req身上的事件處理

JavaScript 在訂閱事件時使用 addEventListener而 nodejs 使用的則是on這邊加上了監聽 data的事件會在瀏覽器傳送資料到Web Server時被執行參數是它所接收到的資料型態是字串

接著再增加 end的事件當瀏覽器的請求事件結束時它就會動作

由於瀏覽器使用 POST在上傳資料時會將資料一塊塊地上傳因為我們在監聽 data事件時透過 formData變數將它累加起來lt不過由於我們

上傳的資料很少一次就結束不過如果日後需要傳的是資料比較大的檔

案這個累加動作就很重要

當資料傳完就進到 end 事件中會用到 qsparse 來解析 formDataformData的內容是字串內容是

username=wordsmithampemail=wordsmith40somewhere

而 qsparse可以幫我們把這個 querystring轉成物件的格式也就是

username=wordsmithampemail=wordsmith40somewhere

一旦轉成物件並指定給 user之後其他的事情就和 GET方法時操作的一樣寫 response的表頭將內容回傳並將 userusername和 useremail代入到內容中

修改完成後接著執行 nodejs程式啟動 web server開啟瀏覽器進入表單測試看看POST的方式能否順利運作

完整程式碼如下

var http = require(rsquohttprsquo)

url = require(rsquourlrsquo)

fs = require(rdquofsrdquo)

qs = require(rsquoquerystringrsquo)

server

server = httpcreateServer(function (reqres)

var urlData

67 Express POST應用範例 61

Nodejs中文電子書

encode = rdquoutf8rdquo

filePath = rdquoviewexpress_post_example_formhtmlrdquo

formData

action

urlData = urlparse(requrltrue)

action = urlDatapathname

reswriteHead(200 rdquoContent-Typerdquordquotexthtml charset=utf-8rdquo)

if (action === rdquoSignuprdquo)

formData = rsquorsquo

reqon(rdquodatardquo function (data)

formData += data

)

reqon(rdquoendrdquo function ()

user = qsparse(formData)

resend(rdquolth1gtrdquo + userusername + rdquo 歡迎您的加入 lth1gtltpgt 我們已經將會員啟用信寄至rdquo + useremail + rdquoltpgtrdquo)

)

else

fsreadFile(filePath encode function(err file)

reswrite(file)

resend()

)

)

serverlisten(3000)

consolelog(rsquoServer 跑起來了現在時間是rsquo + new Date())

62 6 Express介紹

Nodejs中文電子書

68 Express AJAX應用範例

在 Nodejs要使用 Ajax傳送資料並且與之互動在接受資料的部份沒有太大的差別client端不是用 GET就是用 POST來傳資料重點在處理完後用 JSON格式回傳當然 Ajax不見得只傳 JSON格式有時是回傳一段 HTML碼不過後者對伺服器來說基本上就和前兩篇沒有差別了所以我們還是以回傳 JSON做為這一回的主題

這一回其實大多數的工作都會落在前端 Ajax上面前端要負責發送與接收資料並在接收資料後撤掉原先發送資料的表單並將取得的資料

改成 HTML格式之後放上頁面

首先先準備 HTML靜態頁面資料

ltDOCTYPE htmlgt

lthtmlgt

ltheadgt

lttitlegtNodejs 菜鳥筆記 (3)lttitlegt

ltlink rel=rdquostylesheetrdquo href=rdquocssstylecssrdquo type=rdquotextcssrdquo media=rdquoallrdquo gt

ltscript src=rdquohttpsajaxgoogleapiscomajaxlibsjquery171jqueryminjsrdquogtltscriptgt

ltheadgt

ltbodygt

lth1gtNodejs 菜鳥筆記-註冊 (用 Ajax)lth1gt

ltform id=rdquosignuprdquo action=rdquoSignuprdquo method=rdquoPOSTrdquogt

ltlabelgt 使用者名稱ltlabelgtltinput type=rdquotextrdquo id=rdquousernamerdquo name=rdquousernamerdquo gtltbrgt

ltlabelgt 電子郵件ltlabelgtltinput type=rdquotextrdquo id=rdquoemailrdquo name=rdquoemailrdquo gtltbrgt

ltinput type=rdquosubmitrdquo value=rdquo 註冊我的帳號rdquo gtltbrgt

ltformgt

ltdiv id=rdquoresultrdquogt

ltdivgt

ltscript type=rdquotextjavascriptrdquogt

$(function()$(rsquosignup input[type=rdquosubmitrdquo]rsquo)click(function(e)

var user =

userusername = $(rdquousernamerdquo)val()useremail = $(rdquoemailrdquo)val()

$post(rdquoSignuprdquouserfunction(data)greet(data)

)

68 Express AJAX應用範例 63

Nodejs中文電子書

epreventDefault()

)

var greet = function(msg)

var resultNode = $(rsquoresultrsquo)greeting = $(rsquolth2gtHirsquo+msgusername+rsquo 你的會員 id 是rsquo+ msgid +rsquo 我們會將會員啟動信寄至rsquo+msgemail+rsquolth2gtrsquo)

$(rsquosignuprsquo)hide()resultNodehtml(rdquordquo)

resultNodeappend(greeting)

)

ltscriptgt

ltbodygt

lthtmlgt

HTML頁面上準備了一個表單用來傳送註冊資料接著直接引用了Google CDN來載入 jQuery用來幫我們處理 Ajax的工作這次要傳送和接收的工作很大的變動都在 HTML頁面上的 JavaScript當中我們要做的事有 (相關 jQuery處理這邊不多做贅述指提起主要功能解說)

bull 用 jQUery取得 submit按鈕綁定它的 click動作

bull 取得表單 username和 email的值存放在 user這個物件中

bull 用 jQuery的 $post方法將 user的資料傳到 Server

bull 一旦成功取得資料後透過 greet這個 function組成回報給 user的訊息

bull 清空原本給使用者填資料的表單

bull 將 Server回傳的 usernameemail和 id這 3個資料組成回應的訊息

bull 將訊息放到原本表單的位置

經過以上的處理後一個 Ajax的表單的基本功能已經完成

接著進行 nodejs主要程式的編輯部分程式碼如下

var fs = require(rdquofsrdquo)

64 6 Express介紹

Nodejs中文電子書

qs = require(rsquoquerystringrsquo)

if (action === rdquoSignuprdquo)

formData = rsquorsquo

reqon(rdquodatardquo function (data)

formData += data

)

reqon(rdquoendrdquo function ()

var msg

user = qsparse(formData)

userid = rdquo123456rdquo

msg = JSONstringify(user)

reswriteHead(200 rdquoContent-Typerdquordquoapplicationjson charset=utf-8rdquordquoContent-Lengthrdquomsglength)

resend(msg)

)

這裡的程式和前面 POST範例基本上大同小異差別在

bull 幫 user的資料加上 id隨意存放一些文字進去讓 Server回傳的資料多於 Client端傳上來的不然會覺得 Server都沒做事

bull 增加了 msg 這個變數存放將 user 物件 JSON 文字化的結果JSONstringify這個轉換函式是 V8引擎所提供的如果你好奇的話

bull 大重點來了我們要告訴 Client端這次回傳的資料格式是 JSON所在 Content-type和 Content-Length要提供給 Client

Server很輕鬆就完成任務了最後進行程式測試啟動 nodejs主程式之後開啟瀏覽器就會看到表單填寫完畢按下送出就可以看到結果了

最後 nodejs本篇範例程式碼如下

var http = require(rsquohttprsquo)

url = require(rsquourlrsquo)

fs = require(rdquofsrdquo)

qs = require(rsquoquerystringrsquo)

server

68 Express AJAX應用範例 65

Nodejs中文電子書

server = httpcreateServer(function (reqres)

var urlData

encode = rdquoutf8rdquo

filePath = rdquoviewexpress_ajax_example_formhtmlrdquo

formData

action

urlData = urlparse(requrltrue)

action = urlDatapathname

if (action === rdquoSignuprdquo)

formData = rsquorsquo

reqon(rdquodatardquo function (data)

formData += data

)

reqon(rdquoendrdquo function ()

var msg

user = qsparse(formData)

userid = rdquo123456rdquo

msg = JSONstringify(user)

reswriteHead(200 rdquoContent-TyperdquordquoapplicationjsonrdquordquoContent-Lengthrdquomsglength)

resend(msg)

)

else

fsreadFile(filePath encode function(err file)

reswriteHead(200 rdquoContent-Typerdquordquotexthtml charset=utf-8rdquo)

reswrite(file)

resend()

)

)

serverlisten(3000)

66 6 Express介紹

Nodejs中文電子書

consolelog(rsquoServer 跑起來了現在時間是rsquo + new Date())

69 原始資料提供

bull [NodeJS初學者筆記 (1)-用 GET傳送資料] (httpithelpithomecomtwquestion10087402)

bull [NodeJS初學者筆記 (2)-用 POST傳送資料] (httpithelpithomecomtwquestion10087489)

bull [NodeJS 初學者筆記 (3)-用 Ajax 傳送資料] (httpithelpithomecomtwquestion10087627)

69 原始資料提供 67

Nodejs中文電子書

68 6 Express介紹

7

CoffeeScript

bull Spine

bull Hem

bull Spineapp

Letrsquos Have a Cup of CoffeeScript

bull es5-shim ECMAScript 5 compatibility shims for legacy JavaScript engines

ESnext

node-iform - A middleware for node-validator httpsgithubcomguileennode-iform

69

Nodejs中文電子書

70 7 CoffeeScript

8

製作一個 Hubot的 Plurk Adapter

81 應用事項提醒

此應用範例以CoffeeScript編寫主要程式架構採用Github釋放的Hubot專案以下有幾個主要注意事項請讀者注意

bull Hubot 一開始的架構除了內建的兩個 Adapter 之外其餘都要以Nodejs的Module方式才能運作

bull Hubot的 binhubot裡面寫著 npm install所以不管你怎麼改原始碼也不能改變第一點的狀況

其實上述都在討論同一件事情NodeJS的模組而且模組不能設定相對路徑之類的來安裝一定要包裝成 npm相符和格式才有辦法正確執行

82 建立 Adapter

首先需要準備兩個檔案

bull packagejson

bull plurkcoffee

71

Nodejs中文電子書

有這兩個就足以變成 NodeJS的模組了在開始 Coding之前先來設定一下 packagejson相依性

rdquonamerdquo rdquohubot-plurkrdquo

rdquoversionrdquo rdquo011rdquo

rdquomainrdquo rdquoplurkrdquo

rdquodependenciesrdquo

rdquohubotrdquo rdquogt=205rdquo

rdquooauthrdquo rdquordquo

rdquocronrdquordquordquo

這邊會使用到NodeJS的 cron模組(Module)而登入 Plurk需要OAuth標準授權才行所以也需要加載 OAuth模組

注意Hubot在讀取非內建模組時會自動在前面加上 hubot-的前置

83 建立 Robot跟 API

本篇應用基本上程式碼大部分參考 Hubot - Twitter Adapter來製作主要差異只有在使用 OAuth這個模組Twitter有 Streaming可用而 Plurk則得用 Comet方式來達到即時讀取

plurkcoffee程式碼

Robot = require(rdquohubotrdquo)robot()

Adapter = require(rdquohubtordquo)adapter()

EventEmitter = require(rdquoeventsrdquo)EventEmitter

oauth = require(rdquooauthrdquo)

cronJob = require(rdquocronrdquo)CronJob

class Plurk exntends Adapter

class PlurkStreaming exnteds EventEmitter

72 8 製作一個 Hubot的 Plurk Adapter

Nodejs中文電子書

先弄個基本架構主要 Class皆參考 Twitter Adapter命名方式接著再將 Plurk這個 Class增加幾個Method基本上只要有 run send reply就夠了而 run用來做初始化的部分

class Plurk entends Adapter

send (plurk id strings⋯) -gt

reply (plurk id strings⋯) -gt

run -gt

看起來有點東西接著來處理主要的 Plurk API結合部分

class PlurkStreaming extends EventEmitter

consuctor (options) -gt

plurk (callback) -gt

觀察河道

getChannel -gt

取得 Comet 網址

reply (plurk id message) -gt

回噗

acceptFriends -gt

接受好友

get (path callback) -gt

GET 請求

post (path body callback)-gt

POST 請求(其實是裝飾)

request (method path body callback)-gt

主要的 OAuth 請求

comet (server callback)-gt

噗浪的 Comet 傳回是 JavaScript Callback 要另外處理後才會變成 JSON

然後把注意力集中到 constructor上先把建構子弄好

constructor (options) -gt

super()

if optionskey and optionssecret and optionstoken and optionstoken secret

key = optionskey

secret = optionssecret

token = optionstoken

token secret = optionstoken secret

83 建立 Robot跟 API 73

Nodejs中文電子書

建立 OAuth 連接

consumer = new oauthOAuth(

rdquohttpwwwplurkcomOAuthrequest tokenrdquo

rdquohttpwwwplurkcomOAuthaccess tokenrdquo

key

secret

rdquo10rdquo

rdquohttpwwwplurkcomOAuthauthorizerdquo

rdquoHMAC-SHA1rdquo

)

domain = rdquowwwplurkcomrdquo

初始化取得 Comet 網址

do getChannel

else

throw new Error(rdquo 參數不足需要 Key Secret Token Token Secretrdquo)

接著來處理 request這個 method

request (method path body callback) -gt

記錄一下這次的 Request

consolelog(rdquohttpdomainpathrdquo)

Callback 這邊先不丟進去要用另一種方式處理

request = consumerget(rdquohttpdomainpathrdquo token token secret null)

requeston rdquoresponserdquo (res) -gt

reson rdquodatardquo (chunk) -gt

parseResponse(chunk+rsquorsquo callback)

reson rdquoendrdquo (data) -gt

consolelog rdquoEnd Request pathrdquo

reson rdquoerrorrdquo (data) -gt

consolelog rdquoError rdquo + data

requestend()

處理資料

parseResponse = (data callback) -gt

if datalength gt 0

用 TryCatch 避免處理 JSON 出錯導致整個中斷

try

callback null JSONparse(data)

catch err

74 8 製作一個 Hubot的 Plurk Adapter

Nodejs中文電子書

consolelog(rdquoError Parse JSONrdquo + data err)

繼續執行

callback null data ||

大致上就是這樣上面程式的架構已經將整個 Hubot Plurk Adapter完成因為在測試時竟然因為噗浪 Lag而沒讀到完整的 Comet資料然後造成程式異常為了避免這個問題發生因此需要加上為了完美呈現需要再

加上 Comet的處理所以要使用到 EventEmitter的功能

comet (server callback) -gt

在 Callback 裡面會找不到自身所以設定區域變數

self =

記錄一下這次的 Request

consolelog(rdquo[Comet] serverrdquo)

Callback 這邊先不丟進去要用另一種方式處理

request = consumerget(rdquohttpdomainpathrdquo token token secret null)

requeston rdquoresponserdquo (res) -gt

reson rdquodatardquo (chunk) -gt

parseResponse(chunk+rsquorsquo callback)

reson rdquoendrdquo (data) -gt

consolelog rdquoEnd Request pathrdquo

請求結束發出事件通知可以進行下一次請求

selfemit rdquonextPlurkrdquo

reson rdquoerrorrdquo (data) -gt

consolelog rdquoError rdquo + data

requestend()

處理資料

parseResponse = (data callback) -gt

if datalength gt 0

用 trycatch 避免失敗中斷

try

去掉 JavaScript 的 Callback

data = datamatch(CometChannelscriptCallback((+))s)

jsonData = rdquordquo

if data

83 建立 Robot跟 API 75

Nodejs中文電子書

jsonData = JSONparse(data[1])

else

如果沒有任何 Match 嘗試直接 parse

jsonData = JSONparse(data)

catch err

consolelog(rdquo[Comet] Errorrdquo data err)

用 TryCatch 避免處理 JSON 出錯導致整個中斷

try

只傳入 json 的 data 部分

callback null jsonDatadata

catch err

consolelog(rdquo[Comet]Error Parse JSONrdquo + data err)

繼續執行

callback null data ||

後面的 get跟 post就簡單多了

get (path callback) -gt

request(rdquoGETrdquo path null callback)

post (path body callback) -gt

request(rdquoPOSTrdquo path body callback)

接著處理取的 Comet網址的 getChannel

getChannel -gt

self =

get rdquoAPPRealtimegetUserChannelrdquo (error data) -gt

if error

檢查是否有 comet server

if datacomet server

selfchannel = datacomet server

如果沒有 Channel Ready 就嘗試連接會失敗

selfemit(rsquochannel readyrsquo)

那麼先來處理 Plurk Adaper好處理的部份

send (plruk id strings⋯)-gt

跟 Reply 一樣直接交給 reply 做

reply plurk id strings⋯

76 8 製作一個 Hubot的 Plurk Adapter

Nodejs中文電子書

reply (plurk id strings⋯) -gt

stringsforEach (message) =gt

botreply(plruk id message)

接著把 run處理好就可以上線運作摟

run -gt

self =

options =

key processenvHUBOT PLURK KEY

secret processenvHUBOT PLURK SECRET

token processenvHUBOT PLURK TOKEN

token secret processenvHUBOT PLURK TOKEN SECRET

創建剛剛的 API

bot = new PlurkStreaming(options)

依照 Twitter 的 new RobotTextMessage 會沒有反應所以參考 hubot-minecraft 的方式

r = robotconstructor

處理噗浪河道訊息

doPlurk = (data)-gt

檢查是否為回噗

if dataresponse

datacontent raw = dataresponsecontent raw

datauser id = dataresponseuser id

確定有噗浪 ID 跟訊息

if dataplurk id and datacontent raw

selfreceive new rTextMessage(dataplurk id datacontent raw)

取得 Comet Server 完成開始第一次 Comet 連接

boton rdquochannel readyrdquo () -gt

botplurk selfdoPlurk

上一次 Comet 完成繼續 Polling

boton rdquonextPlurkrdquo ()-gt

botplurk selfdoPlurk

定時接受好友邀請

do botacceptFriends

bot = bot

83 建立 Robot跟 API 77

Nodejs中文電子書

終於完成 AdapterHubot專案裡面的 scripts資料夾內是互動部分不需要像 Adapter如此大費周章處理只新增檔案並且設計好對白之後就會回噗了我開發用的機器人在此大家可以去跟他玩玩[httpplurkcomelct9620 bot](httpplurkcomelct9620 bot)

84 原始資料提供

bull [製 作 一 個 Hubot 的 噗 浪 Adapter](http revo-skillfrosttw blog20120318create-a-hubot-plurk-adapter)

78 8 製作一個 Hubot的 Plurk Adapter

9

Nodejs好用工具介紹

91 logio

Real-time log monitoring in your browser

httplogioorg

nodejs (gt=0412 lt=0611)

npm config set unsafe-perm true

npm install -g --prefix=usrlocal logio

logio server start

httplocalhost8998

79

Nodejs中文電子書

80 9 Nodejs好用工具介紹

10

精選文章

101 Express

bull 使用 NodeJS + Express從 GETPOST Request取值 [邀稿中]

bull link2 [邀稿中]

bull link3 [邀稿中]

81

Nodejs中文電子書

82 10 精選文章

11

參考資源

111 Nodejs書籍

bull Programming Nodejs April 2012 (est) Travis Swicegood Pragmatic Bookshelf

bull e Little Book on CoffeeScript January 2012 Alex MacCaw OrsquoReilly Media

bull Node for Front-End Developers January 2012 Garann Means OrsquoReilly Media

bull Building Hypermedia APIs with HTML5 and Node November 2011 MikeAmundsen OrsquoReilly Media

bull Node Web Development August 2011 David Herron PacktPub

bull CoffeeScript Accelerated JavaScript Development July 2011 Trevor BurnhamPragmatic Bookshelf

bull Getting Started with GEO CouchDB and Nodejs July 2011 Mick omp-son OrsquoReilly Media

bull What Is Node July 2011 Brett McLaughlin OrsquoReilly Media

bull Node Up and Running May 2011 Tom Hughes-Croucher Mike WilsonOrsquoReilly Media

83

Nodejs中文電子書

bull Hands-on Nodejs e Nodejs introduction and API reference May 2011 Pe-dro Teixeira LeanPub

bull e Node Beginner Book Manuel Kiessling

bull Mastering Nodejs visionmedia

112 Nodejs影音教學

bull e Node Sessions e Best of OSCON 2011 August 2011 OrsquoReilly Media(Video)

bull Tom Hughes-Croucher on Node March 2011 OrsquoReilly Media (Video)

113 Nodejs教學網站

bull Node Tuts

114 Nodejs課程

bull Introduction To Nodejs Van Nguyen CodeLesson

84 11 參考資源

Page 35: Nodejs Wiki

4

Nodejs基礎

前篇文章已經由介紹安裝至設定都有完整介紹nodeJS 內部除了javascript常用的函式 (function)物件 (object)之外也有許多不同的自訂物件nodeJS預設建立這些物件為核心物件是為了要讓開發流程更為這些資料在官方文件已經具有許多具體說明接下來將會介紹在開發 nodeJS程式時常見的物件特性與使用方法

41 nodejs http伺服器建立

在 lsquonodejs官方網站 lthttpnodejsorggtlsquo裡面有舉一個最簡單的 HTTP伺服器建立一開始初步就是建立一個伺服器平台讓 nodejs可以與瀏覽器互相行為每種語言一開始的程式建立都是以 Hello world開始最初也從 Hello world帶各位進入 nodejs的世界

輸入以下程式碼儲存檔案為 node basic http hello worldjs

var server

ip = rdquo127001rdquo

port = 1337

http = require(rsquohttprsquo)

server = httpcreateServer(function (req res)

29

Nodejs中文電子書

reswriteHead(200 rsquoContent-Typersquo rsquotextplainrsquo)

resend(rsquoHello Worldnrsquo)

)

serverlisten(port ip)

consolelog(rdquoServer running at httprdquo + ip + rdquordquo + port)

程式碼解講一開始需要有幾個基本的變數

bull ip 機器本身的 ip位置因為使用本地端因此設定為 127001

bull port 需要開通的阜號通常設定為 http port 80因範例不希望與基本 port相衝隨意設定為 1337

在 nodejs 的程式中有許多預設的模組可以使用因此需要使用require方法將模組引入在這邊我們需要使用 http這個模組因此將 http載入Http模組裡面內建有許多方法可以使用這邊採用 createServer創建一個基本的 http伺服器再將 http伺服器給予一個 server變數裡面的回呼函式 (call back function)可以載入 http伺服器的資料與回應方法 (requestresponse)在程式裡面就可以看到我們直接回應給瀏覽器端所需的 Header回應內容

reswriteHead(200 rsquoContent-Typersquo rsquotextplainrsquo)

resend(rsquoHello Worldnrsquo)

Http伺服器需要設定 port ip在最後需要設定 Http監聽需要使用到listen事件監聽所有 Http伺服器行為

httplisten(port ip)

所有事情都完成之後需要確認伺服器正確執行因此使用 console在javascript裡就有這個原生物件console所印出的資料都會顯示於 nodejs伺服器頁面這邊印出的資料並不會傳送到使用者頁面上之後許多除壞

(debug)都會用到 console物件

consolelog(rdquoServer running at httprdquo + ip + rdquordquo + port)

30 4 Nodejs基礎

Nodejs中文電子書

42 nodejs http路徑建立

前面已經介紹如何建立一個簡單的 http伺服器接下來本章節將會介紹如何處理伺服器路徑 (rout)問題在 http的協定下所有從瀏覽器發出的要求 (request)都需要經過處理路徑上的建立也是如此

路徑就是指伺服器 ip位置或者是網域名稱之後對於伺服器給予的要求修改剛才的 hello world檔案修改如下

server = httpcreateServer(function (req res)

consolelog(requrl)

reswriteHead(200 rsquoContent-Typersquo rsquotextplainrsquo)

resend(rsquohello worldnrsquo)

)

重新啟動 nodejs程式後在瀏覽器端測試一下路徑行為結果如下圖

當在瀏覽器輸入 http1270011337test 在伺服器端會收到兩個要求一個是我們輸入的test要求另外一個則是faviconicotest的路徑要求http伺服器本身需要經過程式設定才有辦法回應給瀏覽器端所需要的回應在伺服器中所有的路徑要求都是需要被解析才有辦法取得資料從

42 nodejs http路徑建立 31

Nodejs中文電子書

上面解說可以了解到在 nodejs當中所有的路徑都需要經過設定未經過設定的路由會讓瀏覽器無法取得任何資料導致錯誤頁面的發生底下將會解

說如何設定路由同時避免發生錯誤情形先前 nodejs程式需要增加一些修改才能讓使用者透過瀏覽器在不同路徑時有不同的結果根據剛才

的程式做如下的修改

var server

ip = rdquo127001rdquo

port = 1337

http = require(rsquohttprsquo)

url = require(rsquourlrsquo)

path

server = httpcreateServer(function (req res)

path = urlparse(requrl)

reswriteHead(200 rsquoContent-Typersquo rsquotextplainrsquo)

switch (pathpathname)

case rdquoindexrdquo

resend(rsquoI am indexnrsquo)

break

case rdquotestrdquo

resend(rsquothis is test pagenrsquo)

break

default

resend(rsquodefault pagenrsquo)

break

)

serverlisten(port ip)

consolelog(rdquoServer running at httprdquo + ip + rdquordquo + port)

程式做了片段的修改首先載入 url模組另外增加一個 path變數url模組就跟如同他的命名一般專門處理 url字串處理裡面提供了許多方法來解決路徑上的問題因為從瀏覽器發出的要求路徑可能會帶有多種需求

或者 GET參數組合等因此我們需要將路徑單純化取用路徑部分的資料即可例如使用者可能會送出 http1270011337testsend=1如果直接

32 4 Nodejs基礎

Nodejs中文電子書

信任 requrl就會收到結果為testsend=1所以需要透過 url模組的方法將路徑資料過濾

在這邊使用 urlparse的方法裡面帶入網址格式資料會回傳路徑資料為了後需方便使用將回傳的資料設定到 path變數當中在回傳的路徑資料裡面包含資訊如下圖

這邊只需要使用單純的路徑要求直接取用 pathpathname就可以達到我們的目的

最後要做路徑的判別在不同的路徑可以指定不同的輸出在範例中

有三個可能結果第一個從瀏覽器輸入index就會顯示 index結果test 就會呈現出 test頁面最後如果都不符合預期的輸入會直接顯示 default的頁面最後的預防可以讓瀏覽器不會出現非預期結果讓程式的可靠性提昇

底下為測試結果

42 nodejs http路徑建立 33

Nodejs中文電子書

43 nodejs檔案讀取

前面已經介紹如何使用路由(rount)做出不同的回應實際應用只有在瀏覽器只有輸出幾個文字資料總是不夠的在本章節中將介紹如何使

用檔案讀取輸出檔案資料讓使用者在前端瀏覽器也可以讀取到完整的

html css javascript檔案輸出

檔案管理最重要的部分就是 lsquoFile system lthttpnodejsorgdocslatestapifshtmlgtlsquo這個模組此模組可以針對檔案做管理監控讀取等行為裡面有許多預設的方法底下是檔案輸出的基本範例底下會有兩個檔案

第一個是靜態 html檔案

ltDOCTYPE htmlgt

lthtml xmlns=rdquohttpwwww3org1999xhtmlrdquo xmllang=rdquozh-CNrdquo lang=rdquozh-CNrdquogt

ltheadgt

ltmeta http-equiv=rdquoContent-Typerdquo content=rdquotexthtml charset=UTF-7rdquo gt

lttitlegtnodejs index html filelttitlegt

ltheadgt

ltbodygt

34 4 Nodejs基礎

Nodejs中文電子書

lth1gtnodejs index html filelth1gt

ltbodygt

lthtmlgt

另一個為 nodejs程式

var fs = require(rdquofsrdquo)

filename = rdquostaticindexhtmlrdquo

encode = rdquoutf8rdquo

fsreadFile(filename encode function(err file)

consolelog(file)

)

一開始直接載入 le system模組 載入名稱為 fs讀取檔案主要使

用的方法為 readFile裡面以三個參數路徑 ( le path) 編碼方式 (encoding)

回應函式 (callback)路徑必須要設定為靜態 html所在位置才能指定到正確的檔案靜態檔案的編碼方式也必須正確這邊使用靜態檔案的編

碼為 utf8如果編碼設定錯誤nodejs讀取出來檔案結果會使用 byte raw格式輸出如果錯誤編碼格式會導致輸出資料為 byte raw

回應函式中裡面會使用兩個變數error為錯誤資訊如果讀取的檔案不存在或者發生錯誤error數值會是 true如果成功讀取資料 error將會是 falsecontent則是檔案內容資料讀取後將會把資料全數丟到 content這個變數當中

最後程式的輸出結果畫面如下

43 nodejs檔案讀取 35

Nodejs中文電子書

44 nodejs http靜態檔案輸出

前面已經了解如何讀取本地端檔案接下來將配合 http伺服器路由讓每個路由都能夠輸出相對應的靜態 html檔案首先新增加幾個靜態 html檔案

ltDOCTYPE htmlgt

lthtml xmlns=rdquohttpwwww3org1999xhtmlrdquo xmllang=rdquozh-CNrdquo lang=rdquozh-CNrdquogt

ltheadgt

ltmeta http-equiv=rdquoContent-Typerdquo content=rdquotexthtml charset=UTF-7rdquo gt

lttitlegtnodejs index html filelttitlegt

ltheadgt

ltbodygt

lth1gtnodejs index html filelth1gt

ltbodygt

lthtmlgt

ltDOCTYPE htmlgt

lthtml xmlns=rdquohttpwwww3org1999xhtmlrdquo xmllang=rdquozh-CNrdquo lang=rdquozh-CNrdquogt

ltheadgt

ltmeta http-equiv=rdquoContent-Typerdquo content=rdquotexthtml charset=UTF-7rdquo gt

lttitlegtnodejs test html filelttitlegt

ltheadgt

ltbodygt

lth1gtnodejs test html filelth1gt

ltbodygt

lthtmlgt

ltDOCTYPE htmlgt

lthtml xmlns=rdquohttpwwww3org1999xhtmlrdquo xmllang=rdquozh-CNrdquo lang=rdquozh-CNrdquogt

ltheadgt

ltmeta http-equiv=rdquoContent-Typerdquo content=rdquotexthtml charset=UTF-7rdquo gt

lttitlegtnodejs static html filelttitlegt

ltheadgt

ltbodygt

lth1gtnodejs static html filelth1gt

ltbodygt

lthtmlgt

36 4 Nodejs基礎

Nodejs中文電子書

準備一個包含基本路由功能的 http伺服器

var server

ip = rdquo127001rdquo

port = 1337

http = require(rsquohttprsquo)

url = require(rsquourlrsquo)

server = httpcreateServer(function (req res)

var path = urlparse(requrl)

)

serverlisten(port ip)

consolelog(rdquoServer running at httprdquo + ip + rdquordquo + port)

加入 le system 模組使用 readFile 的功能將這一段程式放置於

createServer的回應函式中

fsreadFile(filePath encode function(err file)

)

readFile的回應函式裡面加入頁面輸出讓瀏覽器可以正確讀到檔案在這邊我們設定讀取的檔案為 html 靜態檔案所以 Content-type 設定為texthtml讀取到檔案的內容將會正確輸出成 html靜態檔案

fsreadFile(filePath encode function(err file)

reswriteHead(200 rsquoContent-Typersquo rsquotexthtmlrsquo)

reswrite(file)

resend()

)

到這邊為止基本的程式內容都已經完成剩下一些細節的調整首先

路徑上必須做調整目前的靜態檔案全部都放置於 static資料夾底下設定一個變數來記住資料夾位置

接著將瀏覽器發出要求路徑與資料夾組合讀取正確 html靜態檔案使用者有可能會輸入錯誤路徑所以在讀取檔案的時候要加入錯誤處理

同時回應 404伺服器無法正確回應的 http header格式

加入這些細節的修改一個基本的 http 靜態 html輸出伺服器就完成了完整程式碼如下

44 nodejs http靜態檔案輸出 37

Nodejs中文電子書

var server

ip = rdquo127001rdquo

port = 1337

http = require(rsquohttprsquo)

fs = require(rdquofsrdquo)

folderPath = rdquostaticrdquo

url = require(rsquourlrsquo)

path

filePath

encode = rdquoutf8rdquo

server = httpcreateServer(function (req res)

path = urlparse(requrl)

filePath = folderPath + pathpathname

fsreadFile(filePath encode function(err file)

if (err)

reswriteHead(404 rsquoContent-Typersquo rsquotextplainrsquo)

resend()

return

reswriteHead(200 rsquoContent-Typersquo rsquotextapplicationrsquo)

reswrite(file)

resend()

)

)

serverlisten(port ip)

consolelog(rdquoServer running at httprdquo + ip + rdquordquo + port)

45 nodejs http GET資料擷取

http伺服器中除了路由之外另一個最常使用的方法就是擷取 GET資料本單元將會介紹如何透過基本 http伺服器擷取瀏覽器傳來的要求擷取 GET資料

在 http協定中GET參數都是藉由 URL從瀏覽器發出要求送至伺服器

38 4 Nodejs基礎

Nodejs中文電子書

端基本的傳送網址格式可能如下

http127001testsend=1amptest=2

上面這段網址裡面的 GET參數就是 send而這個變數的數值就為 1如果想要在 http伺服器取得 GET資料需要在瀏覽器給予的要求 (request)做處理

首先需要載入 query string這個模組這個模組主要是用來將字串資料

過濾後轉換成javascript物件

qs = require(rsquoquerystringrsquo)

接著在第一階段利用 url模組過濾瀏覽器發出的 URL資料後將回應的物件裡面的 query這個變數是一個字串值資料過濾後如下

send=1amptest=2

透過 query string使用 parse這個方法將資料轉換成 javascript物件就表示 GET的資料已經被伺服器端正式擷取下來

path = urlparse(requrl)

parameter = qsparse(pathquery)

整個 nodejs http GET參數完整擷取程式碼如下

var server

ip = rdquo127001rdquo

port = 1337

http = require(rsquohttprsquo)

qs = require(rsquoquerystringrsquo)

url = require(rsquourlrsquo)

server = httpcreateServer(function (req res)

var path = urlparse(requrl)

parameter = qsparse(pathquery)

consoledir(parameter)

reswriteHead(200 rsquoContent-Typersquo rsquotextplainrsquo)

reswrite(rsquoBrowser test GET parameternrsquo)

resend()

)

45 nodejs http GET資料擷取 39

Nodejs中文電子書

serverlisten(port ip)

consolelog(rdquoServer running at httprdquo + ip + rdquordquo + port)

程式運作之後由瀏覽器輸入要求網址之後nodejs伺服器端回應資料為

Server running at http1270011337

send rsquo1rsquo test rsquo1rsquo

46 本章結語

前面所解說的部份一大部分主要是處理 http伺服器基本問題雖然在某些部分有牽扯到 http 伺服器基本運作原理主要還是希望可以藉由這些基本範例練習 nodejs練習回應函式與語法串接的特點習慣編寫javascript風格程式當然直接這樣開發 nodejs是非常辛苦的接下來在模組實戰開發的部份將會介紹特定的模組一步一步帶領各位從無到有進行

nodejs應用程式開發

40 4 Nodejs基礎

5

NPM套件管理工具

npm全名為 Node Package Manager是 Nodejs的套件(package)管理工具類似 Perl的 ppm或 PHP的 PEAR等安裝 npm後使用npm install

module name指令即可安裝新套件維護管理套件的工作會更加輕鬆

npm可以讓Nodejs的開發者直接利用擴充線上的套件庫(packagesregistry)加速軟體專案的開發npm提供很友善的搜尋功能可以快速找到安裝需要的套件當這些套件發行新版本時npm也可以協助開發者自動更新這些套件

npm不僅可用於安裝新的套件它也支援搜尋列出已安裝模組及更新的功能

51 安裝 NPM

Nodejs在 063版本開始內建 npm讀者安裝的版本若是此版本或更新的版本就可以略過以下安裝說明

若要檢查 npm是否正確安裝可以使用以下的指令

npm -v

41

Nodejs中文電子書

執行結果說明

若 npm正確安裝執行 npm -v將會看到類似 110-2的版本訊息

若讀者安裝的 Nodejs版本比較舊或是有興趣嘗試自己動手安裝 npm工具則可以參考以下的說明

安裝於Windows系統

Nodejs for Windows 於 062 版開始內建 npm使用 nodejsorg 官方提供的安裝程式不需要進一步的設定就可以立即使用 npm指令對於Windows的開發者來說大幅降低環境設定的問題與門檻

除了使用 Nodejs內建的 npm讀者也可以從 npm官方提供的以下網址

httpnpmjsorgdist

這是由 npm提供的 Fancy Windows Install版本請下載壓縮檔(例如npm-110-3zip)並將壓縮檔內容解壓縮至 Nodejs的安裝路徑(例如CProgram Filesnodejs)

解壓縮後在 Nodejs的安裝路徑下應該有以下的檔案及資料夾

bull npmcmd(檔案)

bull node modules(資料夾)

安裝於 Linux系統

Ubuntu Linux的使用者可以加入 NPM Unoffcial PPA這個 repository即可使用 apt-get完成 npm安裝

42 5 NPM套件管理工具

Nodejs中文電子書

Ubuntu Linux使用 apt-get安裝 npm

sudo apt-get install python-software-properties

sudo add-apt-repository ppagias-kay-leenpm

sudo apt-get update

sudo apt-get install npm

npm官方提供的安裝程式 installsh可以適用於大多數的 Linux系統使用這個安裝程式請先確認

1 系統已安裝 curl工具(請使用 curl --version查看版本訊息)

2 已安裝 Nodejs並且 PATH正確設置

3 Nodejs的版本必須大於 04x

以下為 npm提供的安裝指令

curl httpnpmjsorginstallsh | sh

安裝成功會看到如下訊息

installsh安裝成功的訊息

npm10105 homeUSERNAMElocalnodelibnode_modulesnpm

It worked

安裝於Mac OS X

建議採用與 Nodejs相同的方式進行 npm的安裝例如使用MacPorts安裝 Nodejs就同樣使用MacPorts安裝 npm這樣對日後的維護才會更方便容易

使用 MacPorts安裝 npm是本書比較建議的方式它可以讓 npm的安裝移除及更新工作自動化將會幫助開發者節省寶貴時間

51 安裝 NPM 43

Nodejs中文電子書

安裝MacPorts的提示

在MacPorts網站可以取得 OS X系統版本對應的安裝程式(例如 106或 107)httpwwwmacportsorg安裝過程會詢問系統管理者密碼使用預設的選項完成安裝即可安

裝MacPorts之後在終端機執行 port -v將會看到MacPorts的版本訊息

安裝 npm之前先更新MacPorts的套件清單以確保安裝的 npm是最新版本

sudo port -d selfupdate

接著安裝 npm

sudo port install npm

若讀者的 Nodejs並非使用MacPorts安裝則不建議使用MacPorts安裝npm因為 MacPorts會自動檢查並安裝相依套件而 npm相依 nodejs所以MacPorts也會一併將 nodejs套件安裝造成先前讀者使用其它方式安裝的 nodejs被覆蓋

讀者可以先使用 MacPorts安裝 curl(sudo port install curl)

再參考 Linux的 installsh安裝方式即可使用 npm官方提供的安裝程式

NPM安裝後測試

npm是指令列工具(command-line tool)使用時請先打開系統的文字終端機工具

測試 npm安裝與設定是否正確請輸入指令如下

npm -v

或是

npm --version

如果 npm已經正確安裝設定就會顯示版本訊息

44 5 NPM套件管理工具

Nodejs中文電子書

執行結果(範例)

110-2

52 使用 NPM安裝套件

npm目前擁有超過 6000種套件(packages)可以在 npm registry使用關鍵字搜尋套件

httpsearchnpmjsorg

舉例來說在關鍵字欄位輸入「coffee-script」下方的清單就會自動列出包含 coffee-script關鍵字的套件

接著我們回到終端機模式的操作npm的指令工具本身就可以完成套

件搜尋的任務

例如以下的指令同樣可以找出 coffee-script相關套件

npm search coffee-script

以下是搜尋結果的參考畫面

52 使用 NPM安裝套件 45

Nodejs中文電子書

找到需要的套件後(例如 express)即可使用以下指令安裝

npm install coffee-script

值得注意的一點是使用 npm install會將指定的套件安裝在工

作目錄(Working Directory)的 node modules資料夾下

以Windows為例如果執行 npm install的目錄位於

Cproject1

那麼 npm將會自動建立一個 node modules的子目錄(如果不存在)

Cproject1node modules

並且將下載的套件放置於這個子目錄例如

Cproject1node modulescoffee-script

這個設計讓專案可以個別管理相依的套件並且可以在專案佈署或發

行時將這些套件(位於 node modules)一併打包方便其它專案的使用者不必再重新下載套件

這個 npm install的預設安裝模式為 local(本地)只會變更當前專案的資料夾不會影響系統

另一種安裝模式稱為 global(全域)這種模式會將套件安裝到系統資

料夾也就是 npm安裝路徑的 node modules資料夾例如

CProgram Filesnodejsnode modules

46 5 NPM套件管理工具

Nodejs中文電子書

是否要使用全域安裝可以依照套件是否提供新指令來判斷舉例來說express 套件提供 express 這個指令而 coffee-script 則提供 coffee

指令

在 local 安 裝 模 式 中這 些 指 令 的 程 式 檔 案會 被 安 裝 到node modules的 bin這個隱藏資料夾下除非將bin的路徑加入 PATH環境變數否則要執行這些指令將會相當不便

為了方便指令的執行我們可以在 npm install 加上 -g 或 --

global參數啟用 global安裝模式例如

npm install -g coffee-script

npm install -g express

使用 global安裝模式需要注意執行權限的問題若權限不足可能會出現類似以下的錯誤訊息

npm ERR Error EACCES permission denied rsquorsquo

npm ERR

npm ERR Please try running this command again as rootAdministrator

要獲得足夠得執行權限請參考以下說明

bull Windows 7 或 2008 以上在「命令提示字元」的捷徑按右鍵選擇「以系統管理員身分執行」執行 npm指令時就會具有 Administrator身分

bull Mac OS X或 Linux系統可以使用 sudo指令例如

sudo npm install -g express

bull Linux系統可以使用 root權限登入或是以「sudo su -」切換成 root身分(使用 root權限操作系統相當危險因此並不建議使用這種方式)

若加上 -g參數使用 npm install -g coffee-script完成安裝

後就可以在終端機執行 coffee指令例如

coffee -v

52 使用 NPM安裝套件 47

Nodejs中文電子書

執行結果(範例)

CoffeeScript version 120

53 套件的更新及維護

除了前一節說明的 search 及 install 用法npm 還提供其他許多指令(commands)

使用 npm help可以查詢可用的指令

npm help

執行結果(部分)

where ltcommandgt is one of

adduser apihelp author bin bugs c cache completion

config deprecate docs edit explore faq find get

help help-search home i info init install la link

list ll ln login ls outdated owner pack prefix

prune publish r rb rebuild remove restart rm root

run-script s se search set show star start stop

submodule tag test un uninstall unlink unpublish

unstar up update version view whoami

使用 npm help command可以查詢指令的詳細用法例如

npm help list

接下來本節要介紹開發過程常用的 npm指令

使用 list可以列出已安裝套件

npm list

48 5 NPM套件管理工具

Nodejs中文電子書

執行結果(範例)

-- coffee-script120

- express256

- connect185

| -- formidable108

-- mime124

-- mkdirp007

-- qs041

檢視某個套件的詳細資訊例如

npm show express

升級所有套件(如果該套件已發佈更新版本)

npm update

升級指定的套件

npm update express

移除指定的套件

npm uninstall express

54 使用 packagejson

對於正式的 Nodejs專案可以建立一個命名為 packagejson的設

定檔(純文字格式)檔案內容參考範例如下

54 使用 packagejson 49

Nodejs中文電子書

packagejson(範例)

rdquonamerdquo rdquoapplication-namerdquo

rdquoversionrdquo rdquo001rdquo

rdquoprivaterdquo true

rdquodependenciesrdquo

rdquoexpressrdquo rdquo255rdquo

rdquocoffee-scriptrdquo rdquolatestrdquo

rdquomongooserdquo rdquogt= 253rdquo

其中 name與 version依照專案的需求設置

需要注意的是 dependencies的設定它用於指定專案相依的套件名

稱及版本

bull rdquoexpressrdquo rdquo255rdquo

代表此專案相依版本 255的 express套件

bull rdquocoffee-scriptrdquo rdquolatestrdquo

使用最新版的 coffee-script套件(每次更新都會檢查新版)

bull rdquomongooserdquo rdquogt= 253rdquo

使用版本大於 253的 mongoose套件

假設某個套件的新版可能造成專案無法正常運作就必須指定套件的

版本避免專案的程式碼來不及更新以相容新版套件通常在開發初期的

專案需要盡可能維持新套件的相容性(以取得套件的更新或修正)可以

用「gt=」設定最低相容的版本或是使用「latest」設定永遠保持最新

套件

50 5 NPM套件管理工具

6

Express介紹

在前面的 nodejs基礎當中介紹許多許多開設 http的使用方法及介紹以及許多基本的 nodejs基本應用

接下來要介紹一個套件稱為 express [Express](httpexpressjscom)這個套件主要幫忙解決許多 nodejs http server所需要的基本服務讓開發 httpservice變得更為容易不需要像之前需要透過層層模組(module)才有辦法開始編寫自己的程式

這個套件是由 TJ Holowaychuk 製作而成的套件裡面包含基本的路由處理 (route)http資料處理(GETPOSTPUT)另外還與樣板套件(jshtml template engine)搭配同時也可以處理許多複雜化的問題

61 Express安裝

安裝方式十分簡單只要透過之前介紹的 NPM就可以使用簡單的指令安裝指令如下

這邊建議需要將此套件安裝成為全域模組方便日後使用

51

Nodejs中文電子書

62 Express基本操作

express的使用也十分簡單先來建立一個基本的 hello world

var app = require(rsquoexpressrsquo)createServer()

port = 1337

applisten(port)

appget(rsquorsquo function(req res)

ressend(rsquohello worldrsquo)

)

consolelog(rsquostart express servernrsquo)

可以從上面的程式碼發現基本操作與 nodejs http的建立方式沒有太大差異主要差在當我們設定路由時可以直接透過 appget方式設定回應與接受方式

63 Express路由處理

Express對於 http服務上有許多包裝讓開發者使用及設定上更為方便例如有幾個路由設定那我們就統一藉由 appget來處理

Create http server

appget(rsquorsquo function(req res)

ressend(rsquohello worldrsquo)

)

appget(rsquotestrsquo function(req res)

ressend(rsquotest renderrsquo)

)

appget(rsquouserrsquo function(req res)

ressend(rsquouser pagersquo)

)

52 6 Express介紹

Nodejs中文電子書

如上面的程式碼所表示appget可以帶入兩個參數第一個是路徑名稱設定第二個為回應函式 (call back function)回應函式裡面就如同之前的 createServer方法裡面包含 requestresponse兩個物件可供使用使用者就可以透過瀏覽器輸入不同的 url切換到不同的頁面顯示不同的結果

路由設定上也有基本的配對方式讓使用者從瀏覽器輸入的網址可以

是一個變數只要符合型態就可以有對應的頁面產出例如

Create http server

appget(rsquouseridrsquo function(req res)

ressend(rsquouser rsquo + reqparamsid)

)

appget(rsquonumberrsquo function(req res)

ressend(rsquonumber rsquo + reqparamsnumber)

)

裡 面 使 用 到number 從 網 址 輸 入 之 後 就 可 以 直 接 使 用reqparamsnumber 取得所輸入的資料變成 url 參數使用當然前面也是可以加上路徑的設定userid在瀏覽器上路徑必須符合userxxx透

過 reqparamsid就可以取到 xxx這個字串值

另外express參數處理也提供了路由參數配對處理也可以透過正規表示法作為參數設定

var app = require(rsquoexpressrsquo)createServer()

port = 1337

applisten(port)

appget(^ip((d23)((d23))((d23))((d23))) function(req res)

ressend(reqparams)

)

上面程式碼可以發現後面路由設定的型態是正規表示法裡面設定

格式為ip之後必須要加上 ip型態才會符合資料格式同時取得 ip資料已經由正規表示法將資料做分群因此可以取得 ip的四個數字

此程式執行之後可以透過瀏覽器測試輸入網址為 localhost3000ip25525510010可以從頁面獲得資料

63 Express路由處理 53

Nodejs中文電子書

此章節全部範例程式碼如下

overview

author Caesar Chi

blog clonnblogspotcom

version 20120226

create server

var app = require(rsquoexpressrsquo)createServer()

port = 1337

applisten(port)

normal style

appget(rsquorsquo function(req res)

ressend(rsquohello worldrsquo)

)

appget(rsquotestrsquo function(req res)

ressend(rsquotest renderrsquo)

)

parameter style

appget(rsquouseridrsquo function(req res)

ressend(rsquouser rsquo + reqparamsid)

)

appget(rsquonumberrsquo function(req res)

ressend(rsquonumber rsquo + reqparamsnumber)

)

REGX style

appget(^ip((d23)((d23))((d23))) function(req res)

ressend(reqparams)

)

appget(rsquorsquo function(req res)

ressend(rsquoPage not foundrsquo 404)

54 6 Express介紹

Nodejs中文電子書

)

consolelog(rsquostart express servernrsquo)

64 Express middleware

Express 裡面有一個十分好用的應用概念稱為 middleware可以透過middleware做出複雜的效果同時上面也有介紹 next方法參數傳遞就是靠 middleware的概念來傳遞參數讓開發者可以明確的控制程式邏輯

create http server

appuse(expressbodyParser())

appuse(expressmethodOverride())

appuse(expresssession())

上面都是一種 middleware的使用方式透過 appuse方式裡面載入函式執行方法回應函式會包含三個基本參數responserequestnext其中next表示下一個 middleware執行函式同時會自動將預設三個參數繼續帶往下個函式執行底下有個實驗

上面的片段程式執行後開啟瀏覽器連結上 localhost1337會發現伺服器回應結果順序如下

first middle ware

second middle ware

execute middle ware

end middleware function

從上面的結果可以得知剛才設定的 middleware都生效了在 appuse設定的 middleware是所有 url皆會執行方法如果有指定特定方法就可以使用 appget的 middleware設定在 appget函式的第二個參數就可以帶入函式或者是匿名函式只要函式裡面最後會接受 request response next這三個參數同時也有正確指定 next函式的執行時機最後都會執行到最後一個方法當然開發者也可以評估程式邏輯要執行到哪一個階段讓邏輯

可以更為分明

64 Express middleware 55

Nodejs中文電子書

65 Express路由應用

在實際開發上可能會遇到需要使用參數等方式混和變數一起使用

express裡面提供了一個很棒的處理方法 appall這個方式可以先採用基本路由配對再將設定為每個不同的處理方式開發者可以透過這個方式簡

化自己的程式邏輯

overview

author

version 20120226

create server

var app = require(rsquoexpressrsquo)createServer()

port = 1337

users = [

name rsquoClonnrsquo

name rsquoChirsquo

]

applisten(port)

appall(rsquouseridoprsquo function(req res next)

requser = users[reqparamsid]

if (requser)

next()

else

next(new Error(rsquocannot find user rsquo + reqparamsid))

)

appget(rsquouseridrsquo function(req res)

ressend(rsquoviewing rsquo + requsername)

)

appget(rsquouserideditrsquo function(req res)

ressend(rsquoediting rsquo + requsername)

)

56 6 Express介紹

Nodejs中文電子書

appget(rsquouseriddeletersquo function(req res)

ressend(rsquodeleting rsquo + requsername)

)

appget(rsquorsquo function(req res)

ressend(rsquoPage not foundrsquo 404)

)

consolelog(rsquostart express servernrsquo)

內部宣告一組預設的使用者分別給予名稱設定藉由 appall這個方法可以先將路由雛形建立再接下來設定 appget的路徑格式只要符合格式就會分配進入對應的方法中像上面的程式當中如果使用者輸入路徑為user0除了執行 appall程式之後執行 next方法就會對應到路徑設定為userid的這個方法當中如果使用者輸入路徑為user0edit就會執行到useridedit的對應方法

66 Express GET應用範例

我們準備一個使用 GET方法傳送資料的表單

ltDOCTYPE htmlgt

lthtmlgt

ltheadgt

ltmeta http-equiv=rdquoContent-Typerdquo content=rdquotexthtml charset=utf8rdquogt

lttitlegtNodejs 菜鳥筆記 (1)lttitlegt

ltlink rel=rdquostylesheetrdquo href=rdquocssstylecssrdquo type=rdquotextcssrdquo media=rdquoallrdquo gt

ltheadgt

ltbodygt

lth1gtNodejs 菜鳥筆記-註冊lth1gt

ltform id=rdquosignuprdquo method=rdquoGETrdquo action=rdquohttplocalhost3000Signuprdquogt

ltlabelgt 使用者名稱ltlabelgtltinput type=rdquotextrdquo id=rdquousernamerdquo name=rdquousernamerdquo gtltbrgt

ltlabelgt 電子郵件ltlabelgtltinput type=rdquotextrdquo id=rdquoemailrdquo name=rdquoemailrdquo gtltbrgt

ltinput type=rdquosubmitrdquo value=rdquo 註冊我的帳號rdquo gtltbrgt

ltformgt

ltbodygt

lthtmlgt

66 Express GET應用範例 57

Nodejs中文電子書

這個表單沒有什麼特別的地方我們只需要看第 9 行form 使用的method是 GET然後 action是rdquohttplocalhost3000Signupldquo等一下我們要來撰寫Signup這個 URL Path的處理程式

處理 Signup行為

我們知道所謂的 GET方法會透過 URL來把表單的值給帶過去以上面的表單來說到時候 URL會以這樣的形式傳遞

httplocalhost3000Signupusername=xxxampemail=xxx

所以要能處理這樣的資料必須有以下功能

bull 解析 URL

bull 辨別動作是 Signup

bull 解析出 username和 email

一旦能取得 username和 email的值程式就能加以應用了

處理 Signup的程式碼雛形

load module

var url = require(rsquourlrsquo)

urlData = urlparse(requrltrue)

action = urlDatapathname

reswriteHead(200 rdquoContent-Typerdquordquotexthtml charset=utf-8rdquo)

if (action === rdquoSignuprdquo)

user = urlDataquery

resend(rdquolth1gtrdquo + userusername + rdquo 歡迎您的加入 lth1gtltpgt 我們已經將會員啟用信寄至rdquo + useremail + rdquoltpgtrdquo)

首先需要加載 url module它是用來協助我們解析 URL的模組接著使用 urlparse方法第一個傳入 url字串變數也就是 requrl另外第二個參數的用意是設為 ture則引進 querystring模組來協助處理預設是 false它影響到的是 urlDataquery設為 true會傳回物件不然就只是一般的字串urlparse會將字串內容整理成一個物件我們把它指定給 urlData

action變數作為記錄 pathname這是我們稍後要來判斷目前網頁的動作是什麼接著先將 html表頭資訊 (Header)準備好再來判斷路徑邏輯如

58 6 Express介紹

Nodejs中文電子書

果是 Signup這個動作就把 urlDataquery裡的資料指定給 user然後輸出userusername和 useremail把使用者從表單註冊的資料顯示於頁面中

最後進行程式測試啟動 nodejs主程式之後開啟瀏覽器就會看到表單填寫完畢按下送出就可以看到結果了

完整 nodejs程式碼如下

var http = require(rsquohttprsquo)

url = require(rsquourlrsquo)

fs = require(rdquofsrdquo)

server

server = httpcreateServer(function (reqres)

var urlData

encode = rdquoutf8rdquo

filePath = rdquoviewexpress_get_example_formhtmlrdquo

action

urlData = urlparse(requrltrue)

action = urlDatapathname

reswriteHead(200 rdquoContent-Typerdquordquotexthtml charset=utf-8rdquo)

if (action === rdquoSignuprdquo)

user = urlDataquery

resend(rdquolth1gtrdquo + userusername + rdquo 歡迎您的加入 lth1gtltpgt 我們已經將會員啟用信寄至rdquo + useremail + rdquoltpgtrdquo)

else

fsreadFile(filePath encode function(err file)

reswrite(file)

resend()

)

)

serverlisten(3000)

consolelog(rsquoServer 跑起來了現在時間是rsquo + new Date())

66 Express GET應用範例 59

Nodejs中文電子書

67 Express POST應用範例

一開始準備基本的 html表單傳送內容以 POST方式form的 action屬性設定為 POST其餘 html內容與前一個範例應用相同

ltDOCTYPE htmlgt

lthtmlgt

ltheadgt

ltmeta http-equiv=rdquoContent-Typerdquo content=rdquotexthtml charset=utf8rdquogt

lttitlegtNodejs 菜鳥筆記 (1)lttitlegt

ltlink rel=rdquostylesheetrdquo href=rdquocssstylecssrdquo type=rdquotextcssrdquo media=rdquoallrdquo gt

ltheadgt

ltbodygt

lth1gtNodejs 菜鳥筆記-註冊lth1gt

ltform id=rdquosignuprdquo method=rdquoPOSTrdquo action=rdquohttplocalhost3000Signuprdquogt

ltlabelgt 使用者名稱ltlabelgtltinput type=rdquotextrdquo id=rdquousernamerdquo name=rdquousernamerdquo gtltbrgt

ltlabelgt 電子郵件ltlabelgtltinput type=rdquotextrdquo id=rdquoemailrdquo name=rdquoemailrdquo gtltbrgt

ltinput type=rdquosubmitrdquo value=rdquo 註冊我的帳號rdquo gtltbrgt

ltformgt

ltbodygt

lthtmlgt

nodejs的程式處理邏輯與前面 GET範例類似部分程式碼如下

qs = require(rsquoquerystringrsquo)

if (action === rdquoSignuprdquo)

formData = rsquorsquo

reqon(rdquodatardquo function (data)

formData += data

)

reqon(rdquoendrdquo function ()

user = qsparse(formData)

resend(rdquolth1gtrdquo + userusername + rdquo 歡迎您的加入 lth1gtltpgt 我們已經將會員啟用信寄至rdquo + useremail + rdquoltpgtrdquo)

)

60 6 Express介紹

Nodejs中文電子書

主要加入了rsquoquerystringrsquo這個moduel方便我們等一下解析由表單 POST回來的資料另外加入一個 formData的變數用來搜集待等一下表單回傳的資料前面的 GET範例我們只從 req拿出 url的資料這次要在利用req身上的事件處理

JavaScript 在訂閱事件時使用 addEventListener而 nodejs 使用的則是on這邊加上了監聽 data的事件會在瀏覽器傳送資料到Web Server時被執行參數是它所接收到的資料型態是字串

接著再增加 end的事件當瀏覽器的請求事件結束時它就會動作

由於瀏覽器使用 POST在上傳資料時會將資料一塊塊地上傳因為我們在監聽 data事件時透過 formData變數將它累加起來lt不過由於我們

上傳的資料很少一次就結束不過如果日後需要傳的是資料比較大的檔

案這個累加動作就很重要

當資料傳完就進到 end 事件中會用到 qsparse 來解析 formDataformData的內容是字串內容是

username=wordsmithampemail=wordsmith40somewhere

而 qsparse可以幫我們把這個 querystring轉成物件的格式也就是

username=wordsmithampemail=wordsmith40somewhere

一旦轉成物件並指定給 user之後其他的事情就和 GET方法時操作的一樣寫 response的表頭將內容回傳並將 userusername和 useremail代入到內容中

修改完成後接著執行 nodejs程式啟動 web server開啟瀏覽器進入表單測試看看POST的方式能否順利運作

完整程式碼如下

var http = require(rsquohttprsquo)

url = require(rsquourlrsquo)

fs = require(rdquofsrdquo)

qs = require(rsquoquerystringrsquo)

server

server = httpcreateServer(function (reqres)

var urlData

67 Express POST應用範例 61

Nodejs中文電子書

encode = rdquoutf8rdquo

filePath = rdquoviewexpress_post_example_formhtmlrdquo

formData

action

urlData = urlparse(requrltrue)

action = urlDatapathname

reswriteHead(200 rdquoContent-Typerdquordquotexthtml charset=utf-8rdquo)

if (action === rdquoSignuprdquo)

formData = rsquorsquo

reqon(rdquodatardquo function (data)

formData += data

)

reqon(rdquoendrdquo function ()

user = qsparse(formData)

resend(rdquolth1gtrdquo + userusername + rdquo 歡迎您的加入 lth1gtltpgt 我們已經將會員啟用信寄至rdquo + useremail + rdquoltpgtrdquo)

)

else

fsreadFile(filePath encode function(err file)

reswrite(file)

resend()

)

)

serverlisten(3000)

consolelog(rsquoServer 跑起來了現在時間是rsquo + new Date())

62 6 Express介紹

Nodejs中文電子書

68 Express AJAX應用範例

在 Nodejs要使用 Ajax傳送資料並且與之互動在接受資料的部份沒有太大的差別client端不是用 GET就是用 POST來傳資料重點在處理完後用 JSON格式回傳當然 Ajax不見得只傳 JSON格式有時是回傳一段 HTML碼不過後者對伺服器來說基本上就和前兩篇沒有差別了所以我們還是以回傳 JSON做為這一回的主題

這一回其實大多數的工作都會落在前端 Ajax上面前端要負責發送與接收資料並在接收資料後撤掉原先發送資料的表單並將取得的資料

改成 HTML格式之後放上頁面

首先先準備 HTML靜態頁面資料

ltDOCTYPE htmlgt

lthtmlgt

ltheadgt

lttitlegtNodejs 菜鳥筆記 (3)lttitlegt

ltlink rel=rdquostylesheetrdquo href=rdquocssstylecssrdquo type=rdquotextcssrdquo media=rdquoallrdquo gt

ltscript src=rdquohttpsajaxgoogleapiscomajaxlibsjquery171jqueryminjsrdquogtltscriptgt

ltheadgt

ltbodygt

lth1gtNodejs 菜鳥筆記-註冊 (用 Ajax)lth1gt

ltform id=rdquosignuprdquo action=rdquoSignuprdquo method=rdquoPOSTrdquogt

ltlabelgt 使用者名稱ltlabelgtltinput type=rdquotextrdquo id=rdquousernamerdquo name=rdquousernamerdquo gtltbrgt

ltlabelgt 電子郵件ltlabelgtltinput type=rdquotextrdquo id=rdquoemailrdquo name=rdquoemailrdquo gtltbrgt

ltinput type=rdquosubmitrdquo value=rdquo 註冊我的帳號rdquo gtltbrgt

ltformgt

ltdiv id=rdquoresultrdquogt

ltdivgt

ltscript type=rdquotextjavascriptrdquogt

$(function()$(rsquosignup input[type=rdquosubmitrdquo]rsquo)click(function(e)

var user =

userusername = $(rdquousernamerdquo)val()useremail = $(rdquoemailrdquo)val()

$post(rdquoSignuprdquouserfunction(data)greet(data)

)

68 Express AJAX應用範例 63

Nodejs中文電子書

epreventDefault()

)

var greet = function(msg)

var resultNode = $(rsquoresultrsquo)greeting = $(rsquolth2gtHirsquo+msgusername+rsquo 你的會員 id 是rsquo+ msgid +rsquo 我們會將會員啟動信寄至rsquo+msgemail+rsquolth2gtrsquo)

$(rsquosignuprsquo)hide()resultNodehtml(rdquordquo)

resultNodeappend(greeting)

)

ltscriptgt

ltbodygt

lthtmlgt

HTML頁面上準備了一個表單用來傳送註冊資料接著直接引用了Google CDN來載入 jQuery用來幫我們處理 Ajax的工作這次要傳送和接收的工作很大的變動都在 HTML頁面上的 JavaScript當中我們要做的事有 (相關 jQuery處理這邊不多做贅述指提起主要功能解說)

bull 用 jQUery取得 submit按鈕綁定它的 click動作

bull 取得表單 username和 email的值存放在 user這個物件中

bull 用 jQuery的 $post方法將 user的資料傳到 Server

bull 一旦成功取得資料後透過 greet這個 function組成回報給 user的訊息

bull 清空原本給使用者填資料的表單

bull 將 Server回傳的 usernameemail和 id這 3個資料組成回應的訊息

bull 將訊息放到原本表單的位置

經過以上的處理後一個 Ajax的表單的基本功能已經完成

接著進行 nodejs主要程式的編輯部分程式碼如下

var fs = require(rdquofsrdquo)

64 6 Express介紹

Nodejs中文電子書

qs = require(rsquoquerystringrsquo)

if (action === rdquoSignuprdquo)

formData = rsquorsquo

reqon(rdquodatardquo function (data)

formData += data

)

reqon(rdquoendrdquo function ()

var msg

user = qsparse(formData)

userid = rdquo123456rdquo

msg = JSONstringify(user)

reswriteHead(200 rdquoContent-Typerdquordquoapplicationjson charset=utf-8rdquordquoContent-Lengthrdquomsglength)

resend(msg)

)

這裡的程式和前面 POST範例基本上大同小異差別在

bull 幫 user的資料加上 id隨意存放一些文字進去讓 Server回傳的資料多於 Client端傳上來的不然會覺得 Server都沒做事

bull 增加了 msg 這個變數存放將 user 物件 JSON 文字化的結果JSONstringify這個轉換函式是 V8引擎所提供的如果你好奇的話

bull 大重點來了我們要告訴 Client端這次回傳的資料格式是 JSON所在 Content-type和 Content-Length要提供給 Client

Server很輕鬆就完成任務了最後進行程式測試啟動 nodejs主程式之後開啟瀏覽器就會看到表單填寫完畢按下送出就可以看到結果了

最後 nodejs本篇範例程式碼如下

var http = require(rsquohttprsquo)

url = require(rsquourlrsquo)

fs = require(rdquofsrdquo)

qs = require(rsquoquerystringrsquo)

server

68 Express AJAX應用範例 65

Nodejs中文電子書

server = httpcreateServer(function (reqres)

var urlData

encode = rdquoutf8rdquo

filePath = rdquoviewexpress_ajax_example_formhtmlrdquo

formData

action

urlData = urlparse(requrltrue)

action = urlDatapathname

if (action === rdquoSignuprdquo)

formData = rsquorsquo

reqon(rdquodatardquo function (data)

formData += data

)

reqon(rdquoendrdquo function ()

var msg

user = qsparse(formData)

userid = rdquo123456rdquo

msg = JSONstringify(user)

reswriteHead(200 rdquoContent-TyperdquordquoapplicationjsonrdquordquoContent-Lengthrdquomsglength)

resend(msg)

)

else

fsreadFile(filePath encode function(err file)

reswriteHead(200 rdquoContent-Typerdquordquotexthtml charset=utf-8rdquo)

reswrite(file)

resend()

)

)

serverlisten(3000)

66 6 Express介紹

Nodejs中文電子書

consolelog(rsquoServer 跑起來了現在時間是rsquo + new Date())

69 原始資料提供

bull [NodeJS初學者筆記 (1)-用 GET傳送資料] (httpithelpithomecomtwquestion10087402)

bull [NodeJS初學者筆記 (2)-用 POST傳送資料] (httpithelpithomecomtwquestion10087489)

bull [NodeJS 初學者筆記 (3)-用 Ajax 傳送資料] (httpithelpithomecomtwquestion10087627)

69 原始資料提供 67

Nodejs中文電子書

68 6 Express介紹

7

CoffeeScript

bull Spine

bull Hem

bull Spineapp

Letrsquos Have a Cup of CoffeeScript

bull es5-shim ECMAScript 5 compatibility shims for legacy JavaScript engines

ESnext

node-iform - A middleware for node-validator httpsgithubcomguileennode-iform

69

Nodejs中文電子書

70 7 CoffeeScript

8

製作一個 Hubot的 Plurk Adapter

81 應用事項提醒

此應用範例以CoffeeScript編寫主要程式架構採用Github釋放的Hubot專案以下有幾個主要注意事項請讀者注意

bull Hubot 一開始的架構除了內建的兩個 Adapter 之外其餘都要以Nodejs的Module方式才能運作

bull Hubot的 binhubot裡面寫著 npm install所以不管你怎麼改原始碼也不能改變第一點的狀況

其實上述都在討論同一件事情NodeJS的模組而且模組不能設定相對路徑之類的來安裝一定要包裝成 npm相符和格式才有辦法正確執行

82 建立 Adapter

首先需要準備兩個檔案

bull packagejson

bull plurkcoffee

71

Nodejs中文電子書

有這兩個就足以變成 NodeJS的模組了在開始 Coding之前先來設定一下 packagejson相依性

rdquonamerdquo rdquohubot-plurkrdquo

rdquoversionrdquo rdquo011rdquo

rdquomainrdquo rdquoplurkrdquo

rdquodependenciesrdquo

rdquohubotrdquo rdquogt=205rdquo

rdquooauthrdquo rdquordquo

rdquocronrdquordquordquo

這邊會使用到NodeJS的 cron模組(Module)而登入 Plurk需要OAuth標準授權才行所以也需要加載 OAuth模組

注意Hubot在讀取非內建模組時會自動在前面加上 hubot-的前置

83 建立 Robot跟 API

本篇應用基本上程式碼大部分參考 Hubot - Twitter Adapter來製作主要差異只有在使用 OAuth這個模組Twitter有 Streaming可用而 Plurk則得用 Comet方式來達到即時讀取

plurkcoffee程式碼

Robot = require(rdquohubotrdquo)robot()

Adapter = require(rdquohubtordquo)adapter()

EventEmitter = require(rdquoeventsrdquo)EventEmitter

oauth = require(rdquooauthrdquo)

cronJob = require(rdquocronrdquo)CronJob

class Plurk exntends Adapter

class PlurkStreaming exnteds EventEmitter

72 8 製作一個 Hubot的 Plurk Adapter

Nodejs中文電子書

先弄個基本架構主要 Class皆參考 Twitter Adapter命名方式接著再將 Plurk這個 Class增加幾個Method基本上只要有 run send reply就夠了而 run用來做初始化的部分

class Plurk entends Adapter

send (plurk id strings⋯) -gt

reply (plurk id strings⋯) -gt

run -gt

看起來有點東西接著來處理主要的 Plurk API結合部分

class PlurkStreaming extends EventEmitter

consuctor (options) -gt

plurk (callback) -gt

觀察河道

getChannel -gt

取得 Comet 網址

reply (plurk id message) -gt

回噗

acceptFriends -gt

接受好友

get (path callback) -gt

GET 請求

post (path body callback)-gt

POST 請求(其實是裝飾)

request (method path body callback)-gt

主要的 OAuth 請求

comet (server callback)-gt

噗浪的 Comet 傳回是 JavaScript Callback 要另外處理後才會變成 JSON

然後把注意力集中到 constructor上先把建構子弄好

constructor (options) -gt

super()

if optionskey and optionssecret and optionstoken and optionstoken secret

key = optionskey

secret = optionssecret

token = optionstoken

token secret = optionstoken secret

83 建立 Robot跟 API 73

Nodejs中文電子書

建立 OAuth 連接

consumer = new oauthOAuth(

rdquohttpwwwplurkcomOAuthrequest tokenrdquo

rdquohttpwwwplurkcomOAuthaccess tokenrdquo

key

secret

rdquo10rdquo

rdquohttpwwwplurkcomOAuthauthorizerdquo

rdquoHMAC-SHA1rdquo

)

domain = rdquowwwplurkcomrdquo

初始化取得 Comet 網址

do getChannel

else

throw new Error(rdquo 參數不足需要 Key Secret Token Token Secretrdquo)

接著來處理 request這個 method

request (method path body callback) -gt

記錄一下這次的 Request

consolelog(rdquohttpdomainpathrdquo)

Callback 這邊先不丟進去要用另一種方式處理

request = consumerget(rdquohttpdomainpathrdquo token token secret null)

requeston rdquoresponserdquo (res) -gt

reson rdquodatardquo (chunk) -gt

parseResponse(chunk+rsquorsquo callback)

reson rdquoendrdquo (data) -gt

consolelog rdquoEnd Request pathrdquo

reson rdquoerrorrdquo (data) -gt

consolelog rdquoError rdquo + data

requestend()

處理資料

parseResponse = (data callback) -gt

if datalength gt 0

用 TryCatch 避免處理 JSON 出錯導致整個中斷

try

callback null JSONparse(data)

catch err

74 8 製作一個 Hubot的 Plurk Adapter

Nodejs中文電子書

consolelog(rdquoError Parse JSONrdquo + data err)

繼續執行

callback null data ||

大致上就是這樣上面程式的架構已經將整個 Hubot Plurk Adapter完成因為在測試時竟然因為噗浪 Lag而沒讀到完整的 Comet資料然後造成程式異常為了避免這個問題發生因此需要加上為了完美呈現需要再

加上 Comet的處理所以要使用到 EventEmitter的功能

comet (server callback) -gt

在 Callback 裡面會找不到自身所以設定區域變數

self =

記錄一下這次的 Request

consolelog(rdquo[Comet] serverrdquo)

Callback 這邊先不丟進去要用另一種方式處理

request = consumerget(rdquohttpdomainpathrdquo token token secret null)

requeston rdquoresponserdquo (res) -gt

reson rdquodatardquo (chunk) -gt

parseResponse(chunk+rsquorsquo callback)

reson rdquoendrdquo (data) -gt

consolelog rdquoEnd Request pathrdquo

請求結束發出事件通知可以進行下一次請求

selfemit rdquonextPlurkrdquo

reson rdquoerrorrdquo (data) -gt

consolelog rdquoError rdquo + data

requestend()

處理資料

parseResponse = (data callback) -gt

if datalength gt 0

用 trycatch 避免失敗中斷

try

去掉 JavaScript 的 Callback

data = datamatch(CometChannelscriptCallback((+))s)

jsonData = rdquordquo

if data

83 建立 Robot跟 API 75

Nodejs中文電子書

jsonData = JSONparse(data[1])

else

如果沒有任何 Match 嘗試直接 parse

jsonData = JSONparse(data)

catch err

consolelog(rdquo[Comet] Errorrdquo data err)

用 TryCatch 避免處理 JSON 出錯導致整個中斷

try

只傳入 json 的 data 部分

callback null jsonDatadata

catch err

consolelog(rdquo[Comet]Error Parse JSONrdquo + data err)

繼續執行

callback null data ||

後面的 get跟 post就簡單多了

get (path callback) -gt

request(rdquoGETrdquo path null callback)

post (path body callback) -gt

request(rdquoPOSTrdquo path body callback)

接著處理取的 Comet網址的 getChannel

getChannel -gt

self =

get rdquoAPPRealtimegetUserChannelrdquo (error data) -gt

if error

檢查是否有 comet server

if datacomet server

selfchannel = datacomet server

如果沒有 Channel Ready 就嘗試連接會失敗

selfemit(rsquochannel readyrsquo)

那麼先來處理 Plurk Adaper好處理的部份

send (plruk id strings⋯)-gt

跟 Reply 一樣直接交給 reply 做

reply plurk id strings⋯

76 8 製作一個 Hubot的 Plurk Adapter

Nodejs中文電子書

reply (plurk id strings⋯) -gt

stringsforEach (message) =gt

botreply(plruk id message)

接著把 run處理好就可以上線運作摟

run -gt

self =

options =

key processenvHUBOT PLURK KEY

secret processenvHUBOT PLURK SECRET

token processenvHUBOT PLURK TOKEN

token secret processenvHUBOT PLURK TOKEN SECRET

創建剛剛的 API

bot = new PlurkStreaming(options)

依照 Twitter 的 new RobotTextMessage 會沒有反應所以參考 hubot-minecraft 的方式

r = robotconstructor

處理噗浪河道訊息

doPlurk = (data)-gt

檢查是否為回噗

if dataresponse

datacontent raw = dataresponsecontent raw

datauser id = dataresponseuser id

確定有噗浪 ID 跟訊息

if dataplurk id and datacontent raw

selfreceive new rTextMessage(dataplurk id datacontent raw)

取得 Comet Server 完成開始第一次 Comet 連接

boton rdquochannel readyrdquo () -gt

botplurk selfdoPlurk

上一次 Comet 完成繼續 Polling

boton rdquonextPlurkrdquo ()-gt

botplurk selfdoPlurk

定時接受好友邀請

do botacceptFriends

bot = bot

83 建立 Robot跟 API 77

Nodejs中文電子書

終於完成 AdapterHubot專案裡面的 scripts資料夾內是互動部分不需要像 Adapter如此大費周章處理只新增檔案並且設計好對白之後就會回噗了我開發用的機器人在此大家可以去跟他玩玩[httpplurkcomelct9620 bot](httpplurkcomelct9620 bot)

84 原始資料提供

bull [製 作 一 個 Hubot 的 噗 浪 Adapter](http revo-skillfrosttw blog20120318create-a-hubot-plurk-adapter)

78 8 製作一個 Hubot的 Plurk Adapter

9

Nodejs好用工具介紹

91 logio

Real-time log monitoring in your browser

httplogioorg

nodejs (gt=0412 lt=0611)

npm config set unsafe-perm true

npm install -g --prefix=usrlocal logio

logio server start

httplocalhost8998

79

Nodejs中文電子書

80 9 Nodejs好用工具介紹

10

精選文章

101 Express

bull 使用 NodeJS + Express從 GETPOST Request取值 [邀稿中]

bull link2 [邀稿中]

bull link3 [邀稿中]

81

Nodejs中文電子書

82 10 精選文章

11

參考資源

111 Nodejs書籍

bull Programming Nodejs April 2012 (est) Travis Swicegood Pragmatic Bookshelf

bull e Little Book on CoffeeScript January 2012 Alex MacCaw OrsquoReilly Media

bull Node for Front-End Developers January 2012 Garann Means OrsquoReilly Media

bull Building Hypermedia APIs with HTML5 and Node November 2011 MikeAmundsen OrsquoReilly Media

bull Node Web Development August 2011 David Herron PacktPub

bull CoffeeScript Accelerated JavaScript Development July 2011 Trevor BurnhamPragmatic Bookshelf

bull Getting Started with GEO CouchDB and Nodejs July 2011 Mick omp-son OrsquoReilly Media

bull What Is Node July 2011 Brett McLaughlin OrsquoReilly Media

bull Node Up and Running May 2011 Tom Hughes-Croucher Mike WilsonOrsquoReilly Media

83

Nodejs中文電子書

bull Hands-on Nodejs e Nodejs introduction and API reference May 2011 Pe-dro Teixeira LeanPub

bull e Node Beginner Book Manuel Kiessling

bull Mastering Nodejs visionmedia

112 Nodejs影音教學

bull e Node Sessions e Best of OSCON 2011 August 2011 OrsquoReilly Media(Video)

bull Tom Hughes-Croucher on Node March 2011 OrsquoReilly Media (Video)

113 Nodejs教學網站

bull Node Tuts

114 Nodejs課程

bull Introduction To Nodejs Van Nguyen CodeLesson

84 11 參考資源

Page 36: Nodejs Wiki

Nodejs中文電子書

reswriteHead(200 rsquoContent-Typersquo rsquotextplainrsquo)

resend(rsquoHello Worldnrsquo)

)

serverlisten(port ip)

consolelog(rdquoServer running at httprdquo + ip + rdquordquo + port)

程式碼解講一開始需要有幾個基本的變數

bull ip 機器本身的 ip位置因為使用本地端因此設定為 127001

bull port 需要開通的阜號通常設定為 http port 80因範例不希望與基本 port相衝隨意設定為 1337

在 nodejs 的程式中有許多預設的模組可以使用因此需要使用require方法將模組引入在這邊我們需要使用 http這個模組因此將 http載入Http模組裡面內建有許多方法可以使用這邊採用 createServer創建一個基本的 http伺服器再將 http伺服器給予一個 server變數裡面的回呼函式 (call back function)可以載入 http伺服器的資料與回應方法 (requestresponse)在程式裡面就可以看到我們直接回應給瀏覽器端所需的 Header回應內容

reswriteHead(200 rsquoContent-Typersquo rsquotextplainrsquo)

resend(rsquoHello Worldnrsquo)

Http伺服器需要設定 port ip在最後需要設定 Http監聽需要使用到listen事件監聽所有 Http伺服器行為

httplisten(port ip)

所有事情都完成之後需要確認伺服器正確執行因此使用 console在javascript裡就有這個原生物件console所印出的資料都會顯示於 nodejs伺服器頁面這邊印出的資料並不會傳送到使用者頁面上之後許多除壞

(debug)都會用到 console物件

consolelog(rdquoServer running at httprdquo + ip + rdquordquo + port)

30 4 Nodejs基礎

Nodejs中文電子書

42 nodejs http路徑建立

前面已經介紹如何建立一個簡單的 http伺服器接下來本章節將會介紹如何處理伺服器路徑 (rout)問題在 http的協定下所有從瀏覽器發出的要求 (request)都需要經過處理路徑上的建立也是如此

路徑就是指伺服器 ip位置或者是網域名稱之後對於伺服器給予的要求修改剛才的 hello world檔案修改如下

server = httpcreateServer(function (req res)

consolelog(requrl)

reswriteHead(200 rsquoContent-Typersquo rsquotextplainrsquo)

resend(rsquohello worldnrsquo)

)

重新啟動 nodejs程式後在瀏覽器端測試一下路徑行為結果如下圖

當在瀏覽器輸入 http1270011337test 在伺服器端會收到兩個要求一個是我們輸入的test要求另外一個則是faviconicotest的路徑要求http伺服器本身需要經過程式設定才有辦法回應給瀏覽器端所需要的回應在伺服器中所有的路徑要求都是需要被解析才有辦法取得資料從

42 nodejs http路徑建立 31

Nodejs中文電子書

上面解說可以了解到在 nodejs當中所有的路徑都需要經過設定未經過設定的路由會讓瀏覽器無法取得任何資料導致錯誤頁面的發生底下將會解

說如何設定路由同時避免發生錯誤情形先前 nodejs程式需要增加一些修改才能讓使用者透過瀏覽器在不同路徑時有不同的結果根據剛才

的程式做如下的修改

var server

ip = rdquo127001rdquo

port = 1337

http = require(rsquohttprsquo)

url = require(rsquourlrsquo)

path

server = httpcreateServer(function (req res)

path = urlparse(requrl)

reswriteHead(200 rsquoContent-Typersquo rsquotextplainrsquo)

switch (pathpathname)

case rdquoindexrdquo

resend(rsquoI am indexnrsquo)

break

case rdquotestrdquo

resend(rsquothis is test pagenrsquo)

break

default

resend(rsquodefault pagenrsquo)

break

)

serverlisten(port ip)

consolelog(rdquoServer running at httprdquo + ip + rdquordquo + port)

程式做了片段的修改首先載入 url模組另外增加一個 path變數url模組就跟如同他的命名一般專門處理 url字串處理裡面提供了許多方法來解決路徑上的問題因為從瀏覽器發出的要求路徑可能會帶有多種需求

或者 GET參數組合等因此我們需要將路徑單純化取用路徑部分的資料即可例如使用者可能會送出 http1270011337testsend=1如果直接

32 4 Nodejs基礎

Nodejs中文電子書

信任 requrl就會收到結果為testsend=1所以需要透過 url模組的方法將路徑資料過濾

在這邊使用 urlparse的方法裡面帶入網址格式資料會回傳路徑資料為了後需方便使用將回傳的資料設定到 path變數當中在回傳的路徑資料裡面包含資訊如下圖

這邊只需要使用單純的路徑要求直接取用 pathpathname就可以達到我們的目的

最後要做路徑的判別在不同的路徑可以指定不同的輸出在範例中

有三個可能結果第一個從瀏覽器輸入index就會顯示 index結果test 就會呈現出 test頁面最後如果都不符合預期的輸入會直接顯示 default的頁面最後的預防可以讓瀏覽器不會出現非預期結果讓程式的可靠性提昇

底下為測試結果

42 nodejs http路徑建立 33

Nodejs中文電子書

43 nodejs檔案讀取

前面已經介紹如何使用路由(rount)做出不同的回應實際應用只有在瀏覽器只有輸出幾個文字資料總是不夠的在本章節中將介紹如何使

用檔案讀取輸出檔案資料讓使用者在前端瀏覽器也可以讀取到完整的

html css javascript檔案輸出

檔案管理最重要的部分就是 lsquoFile system lthttpnodejsorgdocslatestapifshtmlgtlsquo這個模組此模組可以針對檔案做管理監控讀取等行為裡面有許多預設的方法底下是檔案輸出的基本範例底下會有兩個檔案

第一個是靜態 html檔案

ltDOCTYPE htmlgt

lthtml xmlns=rdquohttpwwww3org1999xhtmlrdquo xmllang=rdquozh-CNrdquo lang=rdquozh-CNrdquogt

ltheadgt

ltmeta http-equiv=rdquoContent-Typerdquo content=rdquotexthtml charset=UTF-7rdquo gt

lttitlegtnodejs index html filelttitlegt

ltheadgt

ltbodygt

34 4 Nodejs基礎

Nodejs中文電子書

lth1gtnodejs index html filelth1gt

ltbodygt

lthtmlgt

另一個為 nodejs程式

var fs = require(rdquofsrdquo)

filename = rdquostaticindexhtmlrdquo

encode = rdquoutf8rdquo

fsreadFile(filename encode function(err file)

consolelog(file)

)

一開始直接載入 le system模組 載入名稱為 fs讀取檔案主要使

用的方法為 readFile裡面以三個參數路徑 ( le path) 編碼方式 (encoding)

回應函式 (callback)路徑必須要設定為靜態 html所在位置才能指定到正確的檔案靜態檔案的編碼方式也必須正確這邊使用靜態檔案的編

碼為 utf8如果編碼設定錯誤nodejs讀取出來檔案結果會使用 byte raw格式輸出如果錯誤編碼格式會導致輸出資料為 byte raw

回應函式中裡面會使用兩個變數error為錯誤資訊如果讀取的檔案不存在或者發生錯誤error數值會是 true如果成功讀取資料 error將會是 falsecontent則是檔案內容資料讀取後將會把資料全數丟到 content這個變數當中

最後程式的輸出結果畫面如下

43 nodejs檔案讀取 35

Nodejs中文電子書

44 nodejs http靜態檔案輸出

前面已經了解如何讀取本地端檔案接下來將配合 http伺服器路由讓每個路由都能夠輸出相對應的靜態 html檔案首先新增加幾個靜態 html檔案

ltDOCTYPE htmlgt

lthtml xmlns=rdquohttpwwww3org1999xhtmlrdquo xmllang=rdquozh-CNrdquo lang=rdquozh-CNrdquogt

ltheadgt

ltmeta http-equiv=rdquoContent-Typerdquo content=rdquotexthtml charset=UTF-7rdquo gt

lttitlegtnodejs index html filelttitlegt

ltheadgt

ltbodygt

lth1gtnodejs index html filelth1gt

ltbodygt

lthtmlgt

ltDOCTYPE htmlgt

lthtml xmlns=rdquohttpwwww3org1999xhtmlrdquo xmllang=rdquozh-CNrdquo lang=rdquozh-CNrdquogt

ltheadgt

ltmeta http-equiv=rdquoContent-Typerdquo content=rdquotexthtml charset=UTF-7rdquo gt

lttitlegtnodejs test html filelttitlegt

ltheadgt

ltbodygt

lth1gtnodejs test html filelth1gt

ltbodygt

lthtmlgt

ltDOCTYPE htmlgt

lthtml xmlns=rdquohttpwwww3org1999xhtmlrdquo xmllang=rdquozh-CNrdquo lang=rdquozh-CNrdquogt

ltheadgt

ltmeta http-equiv=rdquoContent-Typerdquo content=rdquotexthtml charset=UTF-7rdquo gt

lttitlegtnodejs static html filelttitlegt

ltheadgt

ltbodygt

lth1gtnodejs static html filelth1gt

ltbodygt

lthtmlgt

36 4 Nodejs基礎

Nodejs中文電子書

準備一個包含基本路由功能的 http伺服器

var server

ip = rdquo127001rdquo

port = 1337

http = require(rsquohttprsquo)

url = require(rsquourlrsquo)

server = httpcreateServer(function (req res)

var path = urlparse(requrl)

)

serverlisten(port ip)

consolelog(rdquoServer running at httprdquo + ip + rdquordquo + port)

加入 le system 模組使用 readFile 的功能將這一段程式放置於

createServer的回應函式中

fsreadFile(filePath encode function(err file)

)

readFile的回應函式裡面加入頁面輸出讓瀏覽器可以正確讀到檔案在這邊我們設定讀取的檔案為 html 靜態檔案所以 Content-type 設定為texthtml讀取到檔案的內容將會正確輸出成 html靜態檔案

fsreadFile(filePath encode function(err file)

reswriteHead(200 rsquoContent-Typersquo rsquotexthtmlrsquo)

reswrite(file)

resend()

)

到這邊為止基本的程式內容都已經完成剩下一些細節的調整首先

路徑上必須做調整目前的靜態檔案全部都放置於 static資料夾底下設定一個變數來記住資料夾位置

接著將瀏覽器發出要求路徑與資料夾組合讀取正確 html靜態檔案使用者有可能會輸入錯誤路徑所以在讀取檔案的時候要加入錯誤處理

同時回應 404伺服器無法正確回應的 http header格式

加入這些細節的修改一個基本的 http 靜態 html輸出伺服器就完成了完整程式碼如下

44 nodejs http靜態檔案輸出 37

Nodejs中文電子書

var server

ip = rdquo127001rdquo

port = 1337

http = require(rsquohttprsquo)

fs = require(rdquofsrdquo)

folderPath = rdquostaticrdquo

url = require(rsquourlrsquo)

path

filePath

encode = rdquoutf8rdquo

server = httpcreateServer(function (req res)

path = urlparse(requrl)

filePath = folderPath + pathpathname

fsreadFile(filePath encode function(err file)

if (err)

reswriteHead(404 rsquoContent-Typersquo rsquotextplainrsquo)

resend()

return

reswriteHead(200 rsquoContent-Typersquo rsquotextapplicationrsquo)

reswrite(file)

resend()

)

)

serverlisten(port ip)

consolelog(rdquoServer running at httprdquo + ip + rdquordquo + port)

45 nodejs http GET資料擷取

http伺服器中除了路由之外另一個最常使用的方法就是擷取 GET資料本單元將會介紹如何透過基本 http伺服器擷取瀏覽器傳來的要求擷取 GET資料

在 http協定中GET參數都是藉由 URL從瀏覽器發出要求送至伺服器

38 4 Nodejs基礎

Nodejs中文電子書

端基本的傳送網址格式可能如下

http127001testsend=1amptest=2

上面這段網址裡面的 GET參數就是 send而這個變數的數值就為 1如果想要在 http伺服器取得 GET資料需要在瀏覽器給予的要求 (request)做處理

首先需要載入 query string這個模組這個模組主要是用來將字串資料

過濾後轉換成javascript物件

qs = require(rsquoquerystringrsquo)

接著在第一階段利用 url模組過濾瀏覽器發出的 URL資料後將回應的物件裡面的 query這個變數是一個字串值資料過濾後如下

send=1amptest=2

透過 query string使用 parse這個方法將資料轉換成 javascript物件就表示 GET的資料已經被伺服器端正式擷取下來

path = urlparse(requrl)

parameter = qsparse(pathquery)

整個 nodejs http GET參數完整擷取程式碼如下

var server

ip = rdquo127001rdquo

port = 1337

http = require(rsquohttprsquo)

qs = require(rsquoquerystringrsquo)

url = require(rsquourlrsquo)

server = httpcreateServer(function (req res)

var path = urlparse(requrl)

parameter = qsparse(pathquery)

consoledir(parameter)

reswriteHead(200 rsquoContent-Typersquo rsquotextplainrsquo)

reswrite(rsquoBrowser test GET parameternrsquo)

resend()

)

45 nodejs http GET資料擷取 39

Nodejs中文電子書

serverlisten(port ip)

consolelog(rdquoServer running at httprdquo + ip + rdquordquo + port)

程式運作之後由瀏覽器輸入要求網址之後nodejs伺服器端回應資料為

Server running at http1270011337

send rsquo1rsquo test rsquo1rsquo

46 本章結語

前面所解說的部份一大部分主要是處理 http伺服器基本問題雖然在某些部分有牽扯到 http 伺服器基本運作原理主要還是希望可以藉由這些基本範例練習 nodejs練習回應函式與語法串接的特點習慣編寫javascript風格程式當然直接這樣開發 nodejs是非常辛苦的接下來在模組實戰開發的部份將會介紹特定的模組一步一步帶領各位從無到有進行

nodejs應用程式開發

40 4 Nodejs基礎

5

NPM套件管理工具

npm全名為 Node Package Manager是 Nodejs的套件(package)管理工具類似 Perl的 ppm或 PHP的 PEAR等安裝 npm後使用npm install

module name指令即可安裝新套件維護管理套件的工作會更加輕鬆

npm可以讓Nodejs的開發者直接利用擴充線上的套件庫(packagesregistry)加速軟體專案的開發npm提供很友善的搜尋功能可以快速找到安裝需要的套件當這些套件發行新版本時npm也可以協助開發者自動更新這些套件

npm不僅可用於安裝新的套件它也支援搜尋列出已安裝模組及更新的功能

51 安裝 NPM

Nodejs在 063版本開始內建 npm讀者安裝的版本若是此版本或更新的版本就可以略過以下安裝說明

若要檢查 npm是否正確安裝可以使用以下的指令

npm -v

41

Nodejs中文電子書

執行結果說明

若 npm正確安裝執行 npm -v將會看到類似 110-2的版本訊息

若讀者安裝的 Nodejs版本比較舊或是有興趣嘗試自己動手安裝 npm工具則可以參考以下的說明

安裝於Windows系統

Nodejs for Windows 於 062 版開始內建 npm使用 nodejsorg 官方提供的安裝程式不需要進一步的設定就可以立即使用 npm指令對於Windows的開發者來說大幅降低環境設定的問題與門檻

除了使用 Nodejs內建的 npm讀者也可以從 npm官方提供的以下網址

httpnpmjsorgdist

這是由 npm提供的 Fancy Windows Install版本請下載壓縮檔(例如npm-110-3zip)並將壓縮檔內容解壓縮至 Nodejs的安裝路徑(例如CProgram Filesnodejs)

解壓縮後在 Nodejs的安裝路徑下應該有以下的檔案及資料夾

bull npmcmd(檔案)

bull node modules(資料夾)

安裝於 Linux系統

Ubuntu Linux的使用者可以加入 NPM Unoffcial PPA這個 repository即可使用 apt-get完成 npm安裝

42 5 NPM套件管理工具

Nodejs中文電子書

Ubuntu Linux使用 apt-get安裝 npm

sudo apt-get install python-software-properties

sudo add-apt-repository ppagias-kay-leenpm

sudo apt-get update

sudo apt-get install npm

npm官方提供的安裝程式 installsh可以適用於大多數的 Linux系統使用這個安裝程式請先確認

1 系統已安裝 curl工具(請使用 curl --version查看版本訊息)

2 已安裝 Nodejs並且 PATH正確設置

3 Nodejs的版本必須大於 04x

以下為 npm提供的安裝指令

curl httpnpmjsorginstallsh | sh

安裝成功會看到如下訊息

installsh安裝成功的訊息

npm10105 homeUSERNAMElocalnodelibnode_modulesnpm

It worked

安裝於Mac OS X

建議採用與 Nodejs相同的方式進行 npm的安裝例如使用MacPorts安裝 Nodejs就同樣使用MacPorts安裝 npm這樣對日後的維護才會更方便容易

使用 MacPorts安裝 npm是本書比較建議的方式它可以讓 npm的安裝移除及更新工作自動化將會幫助開發者節省寶貴時間

51 安裝 NPM 43

Nodejs中文電子書

安裝MacPorts的提示

在MacPorts網站可以取得 OS X系統版本對應的安裝程式(例如 106或 107)httpwwwmacportsorg安裝過程會詢問系統管理者密碼使用預設的選項完成安裝即可安

裝MacPorts之後在終端機執行 port -v將會看到MacPorts的版本訊息

安裝 npm之前先更新MacPorts的套件清單以確保安裝的 npm是最新版本

sudo port -d selfupdate

接著安裝 npm

sudo port install npm

若讀者的 Nodejs並非使用MacPorts安裝則不建議使用MacPorts安裝npm因為 MacPorts會自動檢查並安裝相依套件而 npm相依 nodejs所以MacPorts也會一併將 nodejs套件安裝造成先前讀者使用其它方式安裝的 nodejs被覆蓋

讀者可以先使用 MacPorts安裝 curl(sudo port install curl)

再參考 Linux的 installsh安裝方式即可使用 npm官方提供的安裝程式

NPM安裝後測試

npm是指令列工具(command-line tool)使用時請先打開系統的文字終端機工具

測試 npm安裝與設定是否正確請輸入指令如下

npm -v

或是

npm --version

如果 npm已經正確安裝設定就會顯示版本訊息

44 5 NPM套件管理工具

Nodejs中文電子書

執行結果(範例)

110-2

52 使用 NPM安裝套件

npm目前擁有超過 6000種套件(packages)可以在 npm registry使用關鍵字搜尋套件

httpsearchnpmjsorg

舉例來說在關鍵字欄位輸入「coffee-script」下方的清單就會自動列出包含 coffee-script關鍵字的套件

接著我們回到終端機模式的操作npm的指令工具本身就可以完成套

件搜尋的任務

例如以下的指令同樣可以找出 coffee-script相關套件

npm search coffee-script

以下是搜尋結果的參考畫面

52 使用 NPM安裝套件 45

Nodejs中文電子書

找到需要的套件後(例如 express)即可使用以下指令安裝

npm install coffee-script

值得注意的一點是使用 npm install會將指定的套件安裝在工

作目錄(Working Directory)的 node modules資料夾下

以Windows為例如果執行 npm install的目錄位於

Cproject1

那麼 npm將會自動建立一個 node modules的子目錄(如果不存在)

Cproject1node modules

並且將下載的套件放置於這個子目錄例如

Cproject1node modulescoffee-script

這個設計讓專案可以個別管理相依的套件並且可以在專案佈署或發

行時將這些套件(位於 node modules)一併打包方便其它專案的使用者不必再重新下載套件

這個 npm install的預設安裝模式為 local(本地)只會變更當前專案的資料夾不會影響系統

另一種安裝模式稱為 global(全域)這種模式會將套件安裝到系統資

料夾也就是 npm安裝路徑的 node modules資料夾例如

CProgram Filesnodejsnode modules

46 5 NPM套件管理工具

Nodejs中文電子書

是否要使用全域安裝可以依照套件是否提供新指令來判斷舉例來說express 套件提供 express 這個指令而 coffee-script 則提供 coffee

指令

在 local 安 裝 模 式 中這 些 指 令 的 程 式 檔 案會 被 安 裝 到node modules的 bin這個隱藏資料夾下除非將bin的路徑加入 PATH環境變數否則要執行這些指令將會相當不便

為了方便指令的執行我們可以在 npm install 加上 -g 或 --

global參數啟用 global安裝模式例如

npm install -g coffee-script

npm install -g express

使用 global安裝模式需要注意執行權限的問題若權限不足可能會出現類似以下的錯誤訊息

npm ERR Error EACCES permission denied rsquorsquo

npm ERR

npm ERR Please try running this command again as rootAdministrator

要獲得足夠得執行權限請參考以下說明

bull Windows 7 或 2008 以上在「命令提示字元」的捷徑按右鍵選擇「以系統管理員身分執行」執行 npm指令時就會具有 Administrator身分

bull Mac OS X或 Linux系統可以使用 sudo指令例如

sudo npm install -g express

bull Linux系統可以使用 root權限登入或是以「sudo su -」切換成 root身分(使用 root權限操作系統相當危險因此並不建議使用這種方式)

若加上 -g參數使用 npm install -g coffee-script完成安裝

後就可以在終端機執行 coffee指令例如

coffee -v

52 使用 NPM安裝套件 47

Nodejs中文電子書

執行結果(範例)

CoffeeScript version 120

53 套件的更新及維護

除了前一節說明的 search 及 install 用法npm 還提供其他許多指令(commands)

使用 npm help可以查詢可用的指令

npm help

執行結果(部分)

where ltcommandgt is one of

adduser apihelp author bin bugs c cache completion

config deprecate docs edit explore faq find get

help help-search home i info init install la link

list ll ln login ls outdated owner pack prefix

prune publish r rb rebuild remove restart rm root

run-script s se search set show star start stop

submodule tag test un uninstall unlink unpublish

unstar up update version view whoami

使用 npm help command可以查詢指令的詳細用法例如

npm help list

接下來本節要介紹開發過程常用的 npm指令

使用 list可以列出已安裝套件

npm list

48 5 NPM套件管理工具

Nodejs中文電子書

執行結果(範例)

-- coffee-script120

- express256

- connect185

| -- formidable108

-- mime124

-- mkdirp007

-- qs041

檢視某個套件的詳細資訊例如

npm show express

升級所有套件(如果該套件已發佈更新版本)

npm update

升級指定的套件

npm update express

移除指定的套件

npm uninstall express

54 使用 packagejson

對於正式的 Nodejs專案可以建立一個命名為 packagejson的設

定檔(純文字格式)檔案內容參考範例如下

54 使用 packagejson 49

Nodejs中文電子書

packagejson(範例)

rdquonamerdquo rdquoapplication-namerdquo

rdquoversionrdquo rdquo001rdquo

rdquoprivaterdquo true

rdquodependenciesrdquo

rdquoexpressrdquo rdquo255rdquo

rdquocoffee-scriptrdquo rdquolatestrdquo

rdquomongooserdquo rdquogt= 253rdquo

其中 name與 version依照專案的需求設置

需要注意的是 dependencies的設定它用於指定專案相依的套件名

稱及版本

bull rdquoexpressrdquo rdquo255rdquo

代表此專案相依版本 255的 express套件

bull rdquocoffee-scriptrdquo rdquolatestrdquo

使用最新版的 coffee-script套件(每次更新都會檢查新版)

bull rdquomongooserdquo rdquogt= 253rdquo

使用版本大於 253的 mongoose套件

假設某個套件的新版可能造成專案無法正常運作就必須指定套件的

版本避免專案的程式碼來不及更新以相容新版套件通常在開發初期的

專案需要盡可能維持新套件的相容性(以取得套件的更新或修正)可以

用「gt=」設定最低相容的版本或是使用「latest」設定永遠保持最新

套件

50 5 NPM套件管理工具

6

Express介紹

在前面的 nodejs基礎當中介紹許多許多開設 http的使用方法及介紹以及許多基本的 nodejs基本應用

接下來要介紹一個套件稱為 express [Express](httpexpressjscom)這個套件主要幫忙解決許多 nodejs http server所需要的基本服務讓開發 httpservice變得更為容易不需要像之前需要透過層層模組(module)才有辦法開始編寫自己的程式

這個套件是由 TJ Holowaychuk 製作而成的套件裡面包含基本的路由處理 (route)http資料處理(GETPOSTPUT)另外還與樣板套件(jshtml template engine)搭配同時也可以處理許多複雜化的問題

61 Express安裝

安裝方式十分簡單只要透過之前介紹的 NPM就可以使用簡單的指令安裝指令如下

這邊建議需要將此套件安裝成為全域模組方便日後使用

51

Nodejs中文電子書

62 Express基本操作

express的使用也十分簡單先來建立一個基本的 hello world

var app = require(rsquoexpressrsquo)createServer()

port = 1337

applisten(port)

appget(rsquorsquo function(req res)

ressend(rsquohello worldrsquo)

)

consolelog(rsquostart express servernrsquo)

可以從上面的程式碼發現基本操作與 nodejs http的建立方式沒有太大差異主要差在當我們設定路由時可以直接透過 appget方式設定回應與接受方式

63 Express路由處理

Express對於 http服務上有許多包裝讓開發者使用及設定上更為方便例如有幾個路由設定那我們就統一藉由 appget來處理

Create http server

appget(rsquorsquo function(req res)

ressend(rsquohello worldrsquo)

)

appget(rsquotestrsquo function(req res)

ressend(rsquotest renderrsquo)

)

appget(rsquouserrsquo function(req res)

ressend(rsquouser pagersquo)

)

52 6 Express介紹

Nodejs中文電子書

如上面的程式碼所表示appget可以帶入兩個參數第一個是路徑名稱設定第二個為回應函式 (call back function)回應函式裡面就如同之前的 createServer方法裡面包含 requestresponse兩個物件可供使用使用者就可以透過瀏覽器輸入不同的 url切換到不同的頁面顯示不同的結果

路由設定上也有基本的配對方式讓使用者從瀏覽器輸入的網址可以

是一個變數只要符合型態就可以有對應的頁面產出例如

Create http server

appget(rsquouseridrsquo function(req res)

ressend(rsquouser rsquo + reqparamsid)

)

appget(rsquonumberrsquo function(req res)

ressend(rsquonumber rsquo + reqparamsnumber)

)

裡 面 使 用 到number 從 網 址 輸 入 之 後 就 可 以 直 接 使 用reqparamsnumber 取得所輸入的資料變成 url 參數使用當然前面也是可以加上路徑的設定userid在瀏覽器上路徑必須符合userxxx透

過 reqparamsid就可以取到 xxx這個字串值

另外express參數處理也提供了路由參數配對處理也可以透過正規表示法作為參數設定

var app = require(rsquoexpressrsquo)createServer()

port = 1337

applisten(port)

appget(^ip((d23)((d23))((d23))((d23))) function(req res)

ressend(reqparams)

)

上面程式碼可以發現後面路由設定的型態是正規表示法裡面設定

格式為ip之後必須要加上 ip型態才會符合資料格式同時取得 ip資料已經由正規表示法將資料做分群因此可以取得 ip的四個數字

此程式執行之後可以透過瀏覽器測試輸入網址為 localhost3000ip25525510010可以從頁面獲得資料

63 Express路由處理 53

Nodejs中文電子書

此章節全部範例程式碼如下

overview

author Caesar Chi

blog clonnblogspotcom

version 20120226

create server

var app = require(rsquoexpressrsquo)createServer()

port = 1337

applisten(port)

normal style

appget(rsquorsquo function(req res)

ressend(rsquohello worldrsquo)

)

appget(rsquotestrsquo function(req res)

ressend(rsquotest renderrsquo)

)

parameter style

appget(rsquouseridrsquo function(req res)

ressend(rsquouser rsquo + reqparamsid)

)

appget(rsquonumberrsquo function(req res)

ressend(rsquonumber rsquo + reqparamsnumber)

)

REGX style

appget(^ip((d23)((d23))((d23))) function(req res)

ressend(reqparams)

)

appget(rsquorsquo function(req res)

ressend(rsquoPage not foundrsquo 404)

54 6 Express介紹

Nodejs中文電子書

)

consolelog(rsquostart express servernrsquo)

64 Express middleware

Express 裡面有一個十分好用的應用概念稱為 middleware可以透過middleware做出複雜的效果同時上面也有介紹 next方法參數傳遞就是靠 middleware的概念來傳遞參數讓開發者可以明確的控制程式邏輯

create http server

appuse(expressbodyParser())

appuse(expressmethodOverride())

appuse(expresssession())

上面都是一種 middleware的使用方式透過 appuse方式裡面載入函式執行方法回應函式會包含三個基本參數responserequestnext其中next表示下一個 middleware執行函式同時會自動將預設三個參數繼續帶往下個函式執行底下有個實驗

上面的片段程式執行後開啟瀏覽器連結上 localhost1337會發現伺服器回應結果順序如下

first middle ware

second middle ware

execute middle ware

end middleware function

從上面的結果可以得知剛才設定的 middleware都生效了在 appuse設定的 middleware是所有 url皆會執行方法如果有指定特定方法就可以使用 appget的 middleware設定在 appget函式的第二個參數就可以帶入函式或者是匿名函式只要函式裡面最後會接受 request response next這三個參數同時也有正確指定 next函式的執行時機最後都會執行到最後一個方法當然開發者也可以評估程式邏輯要執行到哪一個階段讓邏輯

可以更為分明

64 Express middleware 55

Nodejs中文電子書

65 Express路由應用

在實際開發上可能會遇到需要使用參數等方式混和變數一起使用

express裡面提供了一個很棒的處理方法 appall這個方式可以先採用基本路由配對再將設定為每個不同的處理方式開發者可以透過這個方式簡

化自己的程式邏輯

overview

author

version 20120226

create server

var app = require(rsquoexpressrsquo)createServer()

port = 1337

users = [

name rsquoClonnrsquo

name rsquoChirsquo

]

applisten(port)

appall(rsquouseridoprsquo function(req res next)

requser = users[reqparamsid]

if (requser)

next()

else

next(new Error(rsquocannot find user rsquo + reqparamsid))

)

appget(rsquouseridrsquo function(req res)

ressend(rsquoviewing rsquo + requsername)

)

appget(rsquouserideditrsquo function(req res)

ressend(rsquoediting rsquo + requsername)

)

56 6 Express介紹

Nodejs中文電子書

appget(rsquouseriddeletersquo function(req res)

ressend(rsquodeleting rsquo + requsername)

)

appget(rsquorsquo function(req res)

ressend(rsquoPage not foundrsquo 404)

)

consolelog(rsquostart express servernrsquo)

內部宣告一組預設的使用者分別給予名稱設定藉由 appall這個方法可以先將路由雛形建立再接下來設定 appget的路徑格式只要符合格式就會分配進入對應的方法中像上面的程式當中如果使用者輸入路徑為user0除了執行 appall程式之後執行 next方法就會對應到路徑設定為userid的這個方法當中如果使用者輸入路徑為user0edit就會執行到useridedit的對應方法

66 Express GET應用範例

我們準備一個使用 GET方法傳送資料的表單

ltDOCTYPE htmlgt

lthtmlgt

ltheadgt

ltmeta http-equiv=rdquoContent-Typerdquo content=rdquotexthtml charset=utf8rdquogt

lttitlegtNodejs 菜鳥筆記 (1)lttitlegt

ltlink rel=rdquostylesheetrdquo href=rdquocssstylecssrdquo type=rdquotextcssrdquo media=rdquoallrdquo gt

ltheadgt

ltbodygt

lth1gtNodejs 菜鳥筆記-註冊lth1gt

ltform id=rdquosignuprdquo method=rdquoGETrdquo action=rdquohttplocalhost3000Signuprdquogt

ltlabelgt 使用者名稱ltlabelgtltinput type=rdquotextrdquo id=rdquousernamerdquo name=rdquousernamerdquo gtltbrgt

ltlabelgt 電子郵件ltlabelgtltinput type=rdquotextrdquo id=rdquoemailrdquo name=rdquoemailrdquo gtltbrgt

ltinput type=rdquosubmitrdquo value=rdquo 註冊我的帳號rdquo gtltbrgt

ltformgt

ltbodygt

lthtmlgt

66 Express GET應用範例 57

Nodejs中文電子書

這個表單沒有什麼特別的地方我們只需要看第 9 行form 使用的method是 GET然後 action是rdquohttplocalhost3000Signupldquo等一下我們要來撰寫Signup這個 URL Path的處理程式

處理 Signup行為

我們知道所謂的 GET方法會透過 URL來把表單的值給帶過去以上面的表單來說到時候 URL會以這樣的形式傳遞

httplocalhost3000Signupusername=xxxampemail=xxx

所以要能處理這樣的資料必須有以下功能

bull 解析 URL

bull 辨別動作是 Signup

bull 解析出 username和 email

一旦能取得 username和 email的值程式就能加以應用了

處理 Signup的程式碼雛形

load module

var url = require(rsquourlrsquo)

urlData = urlparse(requrltrue)

action = urlDatapathname

reswriteHead(200 rdquoContent-Typerdquordquotexthtml charset=utf-8rdquo)

if (action === rdquoSignuprdquo)

user = urlDataquery

resend(rdquolth1gtrdquo + userusername + rdquo 歡迎您的加入 lth1gtltpgt 我們已經將會員啟用信寄至rdquo + useremail + rdquoltpgtrdquo)

首先需要加載 url module它是用來協助我們解析 URL的模組接著使用 urlparse方法第一個傳入 url字串變數也就是 requrl另外第二個參數的用意是設為 ture則引進 querystring模組來協助處理預設是 false它影響到的是 urlDataquery設為 true會傳回物件不然就只是一般的字串urlparse會將字串內容整理成一個物件我們把它指定給 urlData

action變數作為記錄 pathname這是我們稍後要來判斷目前網頁的動作是什麼接著先將 html表頭資訊 (Header)準備好再來判斷路徑邏輯如

58 6 Express介紹

Nodejs中文電子書

果是 Signup這個動作就把 urlDataquery裡的資料指定給 user然後輸出userusername和 useremail把使用者從表單註冊的資料顯示於頁面中

最後進行程式測試啟動 nodejs主程式之後開啟瀏覽器就會看到表單填寫完畢按下送出就可以看到結果了

完整 nodejs程式碼如下

var http = require(rsquohttprsquo)

url = require(rsquourlrsquo)

fs = require(rdquofsrdquo)

server

server = httpcreateServer(function (reqres)

var urlData

encode = rdquoutf8rdquo

filePath = rdquoviewexpress_get_example_formhtmlrdquo

action

urlData = urlparse(requrltrue)

action = urlDatapathname

reswriteHead(200 rdquoContent-Typerdquordquotexthtml charset=utf-8rdquo)

if (action === rdquoSignuprdquo)

user = urlDataquery

resend(rdquolth1gtrdquo + userusername + rdquo 歡迎您的加入 lth1gtltpgt 我們已經將會員啟用信寄至rdquo + useremail + rdquoltpgtrdquo)

else

fsreadFile(filePath encode function(err file)

reswrite(file)

resend()

)

)

serverlisten(3000)

consolelog(rsquoServer 跑起來了現在時間是rsquo + new Date())

66 Express GET應用範例 59

Nodejs中文電子書

67 Express POST應用範例

一開始準備基本的 html表單傳送內容以 POST方式form的 action屬性設定為 POST其餘 html內容與前一個範例應用相同

ltDOCTYPE htmlgt

lthtmlgt

ltheadgt

ltmeta http-equiv=rdquoContent-Typerdquo content=rdquotexthtml charset=utf8rdquogt

lttitlegtNodejs 菜鳥筆記 (1)lttitlegt

ltlink rel=rdquostylesheetrdquo href=rdquocssstylecssrdquo type=rdquotextcssrdquo media=rdquoallrdquo gt

ltheadgt

ltbodygt

lth1gtNodejs 菜鳥筆記-註冊lth1gt

ltform id=rdquosignuprdquo method=rdquoPOSTrdquo action=rdquohttplocalhost3000Signuprdquogt

ltlabelgt 使用者名稱ltlabelgtltinput type=rdquotextrdquo id=rdquousernamerdquo name=rdquousernamerdquo gtltbrgt

ltlabelgt 電子郵件ltlabelgtltinput type=rdquotextrdquo id=rdquoemailrdquo name=rdquoemailrdquo gtltbrgt

ltinput type=rdquosubmitrdquo value=rdquo 註冊我的帳號rdquo gtltbrgt

ltformgt

ltbodygt

lthtmlgt

nodejs的程式處理邏輯與前面 GET範例類似部分程式碼如下

qs = require(rsquoquerystringrsquo)

if (action === rdquoSignuprdquo)

formData = rsquorsquo

reqon(rdquodatardquo function (data)

formData += data

)

reqon(rdquoendrdquo function ()

user = qsparse(formData)

resend(rdquolth1gtrdquo + userusername + rdquo 歡迎您的加入 lth1gtltpgt 我們已經將會員啟用信寄至rdquo + useremail + rdquoltpgtrdquo)

)

60 6 Express介紹

Nodejs中文電子書

主要加入了rsquoquerystringrsquo這個moduel方便我們等一下解析由表單 POST回來的資料另外加入一個 formData的變數用來搜集待等一下表單回傳的資料前面的 GET範例我們只從 req拿出 url的資料這次要在利用req身上的事件處理

JavaScript 在訂閱事件時使用 addEventListener而 nodejs 使用的則是on這邊加上了監聽 data的事件會在瀏覽器傳送資料到Web Server時被執行參數是它所接收到的資料型態是字串

接著再增加 end的事件當瀏覽器的請求事件結束時它就會動作

由於瀏覽器使用 POST在上傳資料時會將資料一塊塊地上傳因為我們在監聽 data事件時透過 formData變數將它累加起來lt不過由於我們

上傳的資料很少一次就結束不過如果日後需要傳的是資料比較大的檔

案這個累加動作就很重要

當資料傳完就進到 end 事件中會用到 qsparse 來解析 formDataformData的內容是字串內容是

username=wordsmithampemail=wordsmith40somewhere

而 qsparse可以幫我們把這個 querystring轉成物件的格式也就是

username=wordsmithampemail=wordsmith40somewhere

一旦轉成物件並指定給 user之後其他的事情就和 GET方法時操作的一樣寫 response的表頭將內容回傳並將 userusername和 useremail代入到內容中

修改完成後接著執行 nodejs程式啟動 web server開啟瀏覽器進入表單測試看看POST的方式能否順利運作

完整程式碼如下

var http = require(rsquohttprsquo)

url = require(rsquourlrsquo)

fs = require(rdquofsrdquo)

qs = require(rsquoquerystringrsquo)

server

server = httpcreateServer(function (reqres)

var urlData

67 Express POST應用範例 61

Nodejs中文電子書

encode = rdquoutf8rdquo

filePath = rdquoviewexpress_post_example_formhtmlrdquo

formData

action

urlData = urlparse(requrltrue)

action = urlDatapathname

reswriteHead(200 rdquoContent-Typerdquordquotexthtml charset=utf-8rdquo)

if (action === rdquoSignuprdquo)

formData = rsquorsquo

reqon(rdquodatardquo function (data)

formData += data

)

reqon(rdquoendrdquo function ()

user = qsparse(formData)

resend(rdquolth1gtrdquo + userusername + rdquo 歡迎您的加入 lth1gtltpgt 我們已經將會員啟用信寄至rdquo + useremail + rdquoltpgtrdquo)

)

else

fsreadFile(filePath encode function(err file)

reswrite(file)

resend()

)

)

serverlisten(3000)

consolelog(rsquoServer 跑起來了現在時間是rsquo + new Date())

62 6 Express介紹

Nodejs中文電子書

68 Express AJAX應用範例

在 Nodejs要使用 Ajax傳送資料並且與之互動在接受資料的部份沒有太大的差別client端不是用 GET就是用 POST來傳資料重點在處理完後用 JSON格式回傳當然 Ajax不見得只傳 JSON格式有時是回傳一段 HTML碼不過後者對伺服器來說基本上就和前兩篇沒有差別了所以我們還是以回傳 JSON做為這一回的主題

這一回其實大多數的工作都會落在前端 Ajax上面前端要負責發送與接收資料並在接收資料後撤掉原先發送資料的表單並將取得的資料

改成 HTML格式之後放上頁面

首先先準備 HTML靜態頁面資料

ltDOCTYPE htmlgt

lthtmlgt

ltheadgt

lttitlegtNodejs 菜鳥筆記 (3)lttitlegt

ltlink rel=rdquostylesheetrdquo href=rdquocssstylecssrdquo type=rdquotextcssrdquo media=rdquoallrdquo gt

ltscript src=rdquohttpsajaxgoogleapiscomajaxlibsjquery171jqueryminjsrdquogtltscriptgt

ltheadgt

ltbodygt

lth1gtNodejs 菜鳥筆記-註冊 (用 Ajax)lth1gt

ltform id=rdquosignuprdquo action=rdquoSignuprdquo method=rdquoPOSTrdquogt

ltlabelgt 使用者名稱ltlabelgtltinput type=rdquotextrdquo id=rdquousernamerdquo name=rdquousernamerdquo gtltbrgt

ltlabelgt 電子郵件ltlabelgtltinput type=rdquotextrdquo id=rdquoemailrdquo name=rdquoemailrdquo gtltbrgt

ltinput type=rdquosubmitrdquo value=rdquo 註冊我的帳號rdquo gtltbrgt

ltformgt

ltdiv id=rdquoresultrdquogt

ltdivgt

ltscript type=rdquotextjavascriptrdquogt

$(function()$(rsquosignup input[type=rdquosubmitrdquo]rsquo)click(function(e)

var user =

userusername = $(rdquousernamerdquo)val()useremail = $(rdquoemailrdquo)val()

$post(rdquoSignuprdquouserfunction(data)greet(data)

)

68 Express AJAX應用範例 63

Nodejs中文電子書

epreventDefault()

)

var greet = function(msg)

var resultNode = $(rsquoresultrsquo)greeting = $(rsquolth2gtHirsquo+msgusername+rsquo 你的會員 id 是rsquo+ msgid +rsquo 我們會將會員啟動信寄至rsquo+msgemail+rsquolth2gtrsquo)

$(rsquosignuprsquo)hide()resultNodehtml(rdquordquo)

resultNodeappend(greeting)

)

ltscriptgt

ltbodygt

lthtmlgt

HTML頁面上準備了一個表單用來傳送註冊資料接著直接引用了Google CDN來載入 jQuery用來幫我們處理 Ajax的工作這次要傳送和接收的工作很大的變動都在 HTML頁面上的 JavaScript當中我們要做的事有 (相關 jQuery處理這邊不多做贅述指提起主要功能解說)

bull 用 jQUery取得 submit按鈕綁定它的 click動作

bull 取得表單 username和 email的值存放在 user這個物件中

bull 用 jQuery的 $post方法將 user的資料傳到 Server

bull 一旦成功取得資料後透過 greet這個 function組成回報給 user的訊息

bull 清空原本給使用者填資料的表單

bull 將 Server回傳的 usernameemail和 id這 3個資料組成回應的訊息

bull 將訊息放到原本表單的位置

經過以上的處理後一個 Ajax的表單的基本功能已經完成

接著進行 nodejs主要程式的編輯部分程式碼如下

var fs = require(rdquofsrdquo)

64 6 Express介紹

Nodejs中文電子書

qs = require(rsquoquerystringrsquo)

if (action === rdquoSignuprdquo)

formData = rsquorsquo

reqon(rdquodatardquo function (data)

formData += data

)

reqon(rdquoendrdquo function ()

var msg

user = qsparse(formData)

userid = rdquo123456rdquo

msg = JSONstringify(user)

reswriteHead(200 rdquoContent-Typerdquordquoapplicationjson charset=utf-8rdquordquoContent-Lengthrdquomsglength)

resend(msg)

)

這裡的程式和前面 POST範例基本上大同小異差別在

bull 幫 user的資料加上 id隨意存放一些文字進去讓 Server回傳的資料多於 Client端傳上來的不然會覺得 Server都沒做事

bull 增加了 msg 這個變數存放將 user 物件 JSON 文字化的結果JSONstringify這個轉換函式是 V8引擎所提供的如果你好奇的話

bull 大重點來了我們要告訴 Client端這次回傳的資料格式是 JSON所在 Content-type和 Content-Length要提供給 Client

Server很輕鬆就完成任務了最後進行程式測試啟動 nodejs主程式之後開啟瀏覽器就會看到表單填寫完畢按下送出就可以看到結果了

最後 nodejs本篇範例程式碼如下

var http = require(rsquohttprsquo)

url = require(rsquourlrsquo)

fs = require(rdquofsrdquo)

qs = require(rsquoquerystringrsquo)

server

68 Express AJAX應用範例 65

Nodejs中文電子書

server = httpcreateServer(function (reqres)

var urlData

encode = rdquoutf8rdquo

filePath = rdquoviewexpress_ajax_example_formhtmlrdquo

formData

action

urlData = urlparse(requrltrue)

action = urlDatapathname

if (action === rdquoSignuprdquo)

formData = rsquorsquo

reqon(rdquodatardquo function (data)

formData += data

)

reqon(rdquoendrdquo function ()

var msg

user = qsparse(formData)

userid = rdquo123456rdquo

msg = JSONstringify(user)

reswriteHead(200 rdquoContent-TyperdquordquoapplicationjsonrdquordquoContent-Lengthrdquomsglength)

resend(msg)

)

else

fsreadFile(filePath encode function(err file)

reswriteHead(200 rdquoContent-Typerdquordquotexthtml charset=utf-8rdquo)

reswrite(file)

resend()

)

)

serverlisten(3000)

66 6 Express介紹

Nodejs中文電子書

consolelog(rsquoServer 跑起來了現在時間是rsquo + new Date())

69 原始資料提供

bull [NodeJS初學者筆記 (1)-用 GET傳送資料] (httpithelpithomecomtwquestion10087402)

bull [NodeJS初學者筆記 (2)-用 POST傳送資料] (httpithelpithomecomtwquestion10087489)

bull [NodeJS 初學者筆記 (3)-用 Ajax 傳送資料] (httpithelpithomecomtwquestion10087627)

69 原始資料提供 67

Nodejs中文電子書

68 6 Express介紹

7

CoffeeScript

bull Spine

bull Hem

bull Spineapp

Letrsquos Have a Cup of CoffeeScript

bull es5-shim ECMAScript 5 compatibility shims for legacy JavaScript engines

ESnext

node-iform - A middleware for node-validator httpsgithubcomguileennode-iform

69

Nodejs中文電子書

70 7 CoffeeScript

8

製作一個 Hubot的 Plurk Adapter

81 應用事項提醒

此應用範例以CoffeeScript編寫主要程式架構採用Github釋放的Hubot專案以下有幾個主要注意事項請讀者注意

bull Hubot 一開始的架構除了內建的兩個 Adapter 之外其餘都要以Nodejs的Module方式才能運作

bull Hubot的 binhubot裡面寫著 npm install所以不管你怎麼改原始碼也不能改變第一點的狀況

其實上述都在討論同一件事情NodeJS的模組而且模組不能設定相對路徑之類的來安裝一定要包裝成 npm相符和格式才有辦法正確執行

82 建立 Adapter

首先需要準備兩個檔案

bull packagejson

bull plurkcoffee

71

Nodejs中文電子書

有這兩個就足以變成 NodeJS的模組了在開始 Coding之前先來設定一下 packagejson相依性

rdquonamerdquo rdquohubot-plurkrdquo

rdquoversionrdquo rdquo011rdquo

rdquomainrdquo rdquoplurkrdquo

rdquodependenciesrdquo

rdquohubotrdquo rdquogt=205rdquo

rdquooauthrdquo rdquordquo

rdquocronrdquordquordquo

這邊會使用到NodeJS的 cron模組(Module)而登入 Plurk需要OAuth標準授權才行所以也需要加載 OAuth模組

注意Hubot在讀取非內建模組時會自動在前面加上 hubot-的前置

83 建立 Robot跟 API

本篇應用基本上程式碼大部分參考 Hubot - Twitter Adapter來製作主要差異只有在使用 OAuth這個模組Twitter有 Streaming可用而 Plurk則得用 Comet方式來達到即時讀取

plurkcoffee程式碼

Robot = require(rdquohubotrdquo)robot()

Adapter = require(rdquohubtordquo)adapter()

EventEmitter = require(rdquoeventsrdquo)EventEmitter

oauth = require(rdquooauthrdquo)

cronJob = require(rdquocronrdquo)CronJob

class Plurk exntends Adapter

class PlurkStreaming exnteds EventEmitter

72 8 製作一個 Hubot的 Plurk Adapter

Nodejs中文電子書

先弄個基本架構主要 Class皆參考 Twitter Adapter命名方式接著再將 Plurk這個 Class增加幾個Method基本上只要有 run send reply就夠了而 run用來做初始化的部分

class Plurk entends Adapter

send (plurk id strings⋯) -gt

reply (plurk id strings⋯) -gt

run -gt

看起來有點東西接著來處理主要的 Plurk API結合部分

class PlurkStreaming extends EventEmitter

consuctor (options) -gt

plurk (callback) -gt

觀察河道

getChannel -gt

取得 Comet 網址

reply (plurk id message) -gt

回噗

acceptFriends -gt

接受好友

get (path callback) -gt

GET 請求

post (path body callback)-gt

POST 請求(其實是裝飾)

request (method path body callback)-gt

主要的 OAuth 請求

comet (server callback)-gt

噗浪的 Comet 傳回是 JavaScript Callback 要另外處理後才會變成 JSON

然後把注意力集中到 constructor上先把建構子弄好

constructor (options) -gt

super()

if optionskey and optionssecret and optionstoken and optionstoken secret

key = optionskey

secret = optionssecret

token = optionstoken

token secret = optionstoken secret

83 建立 Robot跟 API 73

Nodejs中文電子書

建立 OAuth 連接

consumer = new oauthOAuth(

rdquohttpwwwplurkcomOAuthrequest tokenrdquo

rdquohttpwwwplurkcomOAuthaccess tokenrdquo

key

secret

rdquo10rdquo

rdquohttpwwwplurkcomOAuthauthorizerdquo

rdquoHMAC-SHA1rdquo

)

domain = rdquowwwplurkcomrdquo

初始化取得 Comet 網址

do getChannel

else

throw new Error(rdquo 參數不足需要 Key Secret Token Token Secretrdquo)

接著來處理 request這個 method

request (method path body callback) -gt

記錄一下這次的 Request

consolelog(rdquohttpdomainpathrdquo)

Callback 這邊先不丟進去要用另一種方式處理

request = consumerget(rdquohttpdomainpathrdquo token token secret null)

requeston rdquoresponserdquo (res) -gt

reson rdquodatardquo (chunk) -gt

parseResponse(chunk+rsquorsquo callback)

reson rdquoendrdquo (data) -gt

consolelog rdquoEnd Request pathrdquo

reson rdquoerrorrdquo (data) -gt

consolelog rdquoError rdquo + data

requestend()

處理資料

parseResponse = (data callback) -gt

if datalength gt 0

用 TryCatch 避免處理 JSON 出錯導致整個中斷

try

callback null JSONparse(data)

catch err

74 8 製作一個 Hubot的 Plurk Adapter

Nodejs中文電子書

consolelog(rdquoError Parse JSONrdquo + data err)

繼續執行

callback null data ||

大致上就是這樣上面程式的架構已經將整個 Hubot Plurk Adapter完成因為在測試時竟然因為噗浪 Lag而沒讀到完整的 Comet資料然後造成程式異常為了避免這個問題發生因此需要加上為了完美呈現需要再

加上 Comet的處理所以要使用到 EventEmitter的功能

comet (server callback) -gt

在 Callback 裡面會找不到自身所以設定區域變數

self =

記錄一下這次的 Request

consolelog(rdquo[Comet] serverrdquo)

Callback 這邊先不丟進去要用另一種方式處理

request = consumerget(rdquohttpdomainpathrdquo token token secret null)

requeston rdquoresponserdquo (res) -gt

reson rdquodatardquo (chunk) -gt

parseResponse(chunk+rsquorsquo callback)

reson rdquoendrdquo (data) -gt

consolelog rdquoEnd Request pathrdquo

請求結束發出事件通知可以進行下一次請求

selfemit rdquonextPlurkrdquo

reson rdquoerrorrdquo (data) -gt

consolelog rdquoError rdquo + data

requestend()

處理資料

parseResponse = (data callback) -gt

if datalength gt 0

用 trycatch 避免失敗中斷

try

去掉 JavaScript 的 Callback

data = datamatch(CometChannelscriptCallback((+))s)

jsonData = rdquordquo

if data

83 建立 Robot跟 API 75

Nodejs中文電子書

jsonData = JSONparse(data[1])

else

如果沒有任何 Match 嘗試直接 parse

jsonData = JSONparse(data)

catch err

consolelog(rdquo[Comet] Errorrdquo data err)

用 TryCatch 避免處理 JSON 出錯導致整個中斷

try

只傳入 json 的 data 部分

callback null jsonDatadata

catch err

consolelog(rdquo[Comet]Error Parse JSONrdquo + data err)

繼續執行

callback null data ||

後面的 get跟 post就簡單多了

get (path callback) -gt

request(rdquoGETrdquo path null callback)

post (path body callback) -gt

request(rdquoPOSTrdquo path body callback)

接著處理取的 Comet網址的 getChannel

getChannel -gt

self =

get rdquoAPPRealtimegetUserChannelrdquo (error data) -gt

if error

檢查是否有 comet server

if datacomet server

selfchannel = datacomet server

如果沒有 Channel Ready 就嘗試連接會失敗

selfemit(rsquochannel readyrsquo)

那麼先來處理 Plurk Adaper好處理的部份

send (plruk id strings⋯)-gt

跟 Reply 一樣直接交給 reply 做

reply plurk id strings⋯

76 8 製作一個 Hubot的 Plurk Adapter

Nodejs中文電子書

reply (plurk id strings⋯) -gt

stringsforEach (message) =gt

botreply(plruk id message)

接著把 run處理好就可以上線運作摟

run -gt

self =

options =

key processenvHUBOT PLURK KEY

secret processenvHUBOT PLURK SECRET

token processenvHUBOT PLURK TOKEN

token secret processenvHUBOT PLURK TOKEN SECRET

創建剛剛的 API

bot = new PlurkStreaming(options)

依照 Twitter 的 new RobotTextMessage 會沒有反應所以參考 hubot-minecraft 的方式

r = robotconstructor

處理噗浪河道訊息

doPlurk = (data)-gt

檢查是否為回噗

if dataresponse

datacontent raw = dataresponsecontent raw

datauser id = dataresponseuser id

確定有噗浪 ID 跟訊息

if dataplurk id and datacontent raw

selfreceive new rTextMessage(dataplurk id datacontent raw)

取得 Comet Server 完成開始第一次 Comet 連接

boton rdquochannel readyrdquo () -gt

botplurk selfdoPlurk

上一次 Comet 完成繼續 Polling

boton rdquonextPlurkrdquo ()-gt

botplurk selfdoPlurk

定時接受好友邀請

do botacceptFriends

bot = bot

83 建立 Robot跟 API 77

Nodejs中文電子書

終於完成 AdapterHubot專案裡面的 scripts資料夾內是互動部分不需要像 Adapter如此大費周章處理只新增檔案並且設計好對白之後就會回噗了我開發用的機器人在此大家可以去跟他玩玩[httpplurkcomelct9620 bot](httpplurkcomelct9620 bot)

84 原始資料提供

bull [製 作 一 個 Hubot 的 噗 浪 Adapter](http revo-skillfrosttw blog20120318create-a-hubot-plurk-adapter)

78 8 製作一個 Hubot的 Plurk Adapter

9

Nodejs好用工具介紹

91 logio

Real-time log monitoring in your browser

httplogioorg

nodejs (gt=0412 lt=0611)

npm config set unsafe-perm true

npm install -g --prefix=usrlocal logio

logio server start

httplocalhost8998

79

Nodejs中文電子書

80 9 Nodejs好用工具介紹

10

精選文章

101 Express

bull 使用 NodeJS + Express從 GETPOST Request取值 [邀稿中]

bull link2 [邀稿中]

bull link3 [邀稿中]

81

Nodejs中文電子書

82 10 精選文章

11

參考資源

111 Nodejs書籍

bull Programming Nodejs April 2012 (est) Travis Swicegood Pragmatic Bookshelf

bull e Little Book on CoffeeScript January 2012 Alex MacCaw OrsquoReilly Media

bull Node for Front-End Developers January 2012 Garann Means OrsquoReilly Media

bull Building Hypermedia APIs with HTML5 and Node November 2011 MikeAmundsen OrsquoReilly Media

bull Node Web Development August 2011 David Herron PacktPub

bull CoffeeScript Accelerated JavaScript Development July 2011 Trevor BurnhamPragmatic Bookshelf

bull Getting Started with GEO CouchDB and Nodejs July 2011 Mick omp-son OrsquoReilly Media

bull What Is Node July 2011 Brett McLaughlin OrsquoReilly Media

bull Node Up and Running May 2011 Tom Hughes-Croucher Mike WilsonOrsquoReilly Media

83

Nodejs中文電子書

bull Hands-on Nodejs e Nodejs introduction and API reference May 2011 Pe-dro Teixeira LeanPub

bull e Node Beginner Book Manuel Kiessling

bull Mastering Nodejs visionmedia

112 Nodejs影音教學

bull e Node Sessions e Best of OSCON 2011 August 2011 OrsquoReilly Media(Video)

bull Tom Hughes-Croucher on Node March 2011 OrsquoReilly Media (Video)

113 Nodejs教學網站

bull Node Tuts

114 Nodejs課程

bull Introduction To Nodejs Van Nguyen CodeLesson

84 11 參考資源

Page 37: Nodejs Wiki

Nodejs中文電子書

42 nodejs http路徑建立

前面已經介紹如何建立一個簡單的 http伺服器接下來本章節將會介紹如何處理伺服器路徑 (rout)問題在 http的協定下所有從瀏覽器發出的要求 (request)都需要經過處理路徑上的建立也是如此

路徑就是指伺服器 ip位置或者是網域名稱之後對於伺服器給予的要求修改剛才的 hello world檔案修改如下

server = httpcreateServer(function (req res)

consolelog(requrl)

reswriteHead(200 rsquoContent-Typersquo rsquotextplainrsquo)

resend(rsquohello worldnrsquo)

)

重新啟動 nodejs程式後在瀏覽器端測試一下路徑行為結果如下圖

當在瀏覽器輸入 http1270011337test 在伺服器端會收到兩個要求一個是我們輸入的test要求另外一個則是faviconicotest的路徑要求http伺服器本身需要經過程式設定才有辦法回應給瀏覽器端所需要的回應在伺服器中所有的路徑要求都是需要被解析才有辦法取得資料從

42 nodejs http路徑建立 31

Nodejs中文電子書

上面解說可以了解到在 nodejs當中所有的路徑都需要經過設定未經過設定的路由會讓瀏覽器無法取得任何資料導致錯誤頁面的發生底下將會解

說如何設定路由同時避免發生錯誤情形先前 nodejs程式需要增加一些修改才能讓使用者透過瀏覽器在不同路徑時有不同的結果根據剛才

的程式做如下的修改

var server

ip = rdquo127001rdquo

port = 1337

http = require(rsquohttprsquo)

url = require(rsquourlrsquo)

path

server = httpcreateServer(function (req res)

path = urlparse(requrl)

reswriteHead(200 rsquoContent-Typersquo rsquotextplainrsquo)

switch (pathpathname)

case rdquoindexrdquo

resend(rsquoI am indexnrsquo)

break

case rdquotestrdquo

resend(rsquothis is test pagenrsquo)

break

default

resend(rsquodefault pagenrsquo)

break

)

serverlisten(port ip)

consolelog(rdquoServer running at httprdquo + ip + rdquordquo + port)

程式做了片段的修改首先載入 url模組另外增加一個 path變數url模組就跟如同他的命名一般專門處理 url字串處理裡面提供了許多方法來解決路徑上的問題因為從瀏覽器發出的要求路徑可能會帶有多種需求

或者 GET參數組合等因此我們需要將路徑單純化取用路徑部分的資料即可例如使用者可能會送出 http1270011337testsend=1如果直接

32 4 Nodejs基礎

Nodejs中文電子書

信任 requrl就會收到結果為testsend=1所以需要透過 url模組的方法將路徑資料過濾

在這邊使用 urlparse的方法裡面帶入網址格式資料會回傳路徑資料為了後需方便使用將回傳的資料設定到 path變數當中在回傳的路徑資料裡面包含資訊如下圖

這邊只需要使用單純的路徑要求直接取用 pathpathname就可以達到我們的目的

最後要做路徑的判別在不同的路徑可以指定不同的輸出在範例中

有三個可能結果第一個從瀏覽器輸入index就會顯示 index結果test 就會呈現出 test頁面最後如果都不符合預期的輸入會直接顯示 default的頁面最後的預防可以讓瀏覽器不會出現非預期結果讓程式的可靠性提昇

底下為測試結果

42 nodejs http路徑建立 33

Nodejs中文電子書

43 nodejs檔案讀取

前面已經介紹如何使用路由(rount)做出不同的回應實際應用只有在瀏覽器只有輸出幾個文字資料總是不夠的在本章節中將介紹如何使

用檔案讀取輸出檔案資料讓使用者在前端瀏覽器也可以讀取到完整的

html css javascript檔案輸出

檔案管理最重要的部分就是 lsquoFile system lthttpnodejsorgdocslatestapifshtmlgtlsquo這個模組此模組可以針對檔案做管理監控讀取等行為裡面有許多預設的方法底下是檔案輸出的基本範例底下會有兩個檔案

第一個是靜態 html檔案

ltDOCTYPE htmlgt

lthtml xmlns=rdquohttpwwww3org1999xhtmlrdquo xmllang=rdquozh-CNrdquo lang=rdquozh-CNrdquogt

ltheadgt

ltmeta http-equiv=rdquoContent-Typerdquo content=rdquotexthtml charset=UTF-7rdquo gt

lttitlegtnodejs index html filelttitlegt

ltheadgt

ltbodygt

34 4 Nodejs基礎

Nodejs中文電子書

lth1gtnodejs index html filelth1gt

ltbodygt

lthtmlgt

另一個為 nodejs程式

var fs = require(rdquofsrdquo)

filename = rdquostaticindexhtmlrdquo

encode = rdquoutf8rdquo

fsreadFile(filename encode function(err file)

consolelog(file)

)

一開始直接載入 le system模組 載入名稱為 fs讀取檔案主要使

用的方法為 readFile裡面以三個參數路徑 ( le path) 編碼方式 (encoding)

回應函式 (callback)路徑必須要設定為靜態 html所在位置才能指定到正確的檔案靜態檔案的編碼方式也必須正確這邊使用靜態檔案的編

碼為 utf8如果編碼設定錯誤nodejs讀取出來檔案結果會使用 byte raw格式輸出如果錯誤編碼格式會導致輸出資料為 byte raw

回應函式中裡面會使用兩個變數error為錯誤資訊如果讀取的檔案不存在或者發生錯誤error數值會是 true如果成功讀取資料 error將會是 falsecontent則是檔案內容資料讀取後將會把資料全數丟到 content這個變數當中

最後程式的輸出結果畫面如下

43 nodejs檔案讀取 35

Nodejs中文電子書

44 nodejs http靜態檔案輸出

前面已經了解如何讀取本地端檔案接下來將配合 http伺服器路由讓每個路由都能夠輸出相對應的靜態 html檔案首先新增加幾個靜態 html檔案

ltDOCTYPE htmlgt

lthtml xmlns=rdquohttpwwww3org1999xhtmlrdquo xmllang=rdquozh-CNrdquo lang=rdquozh-CNrdquogt

ltheadgt

ltmeta http-equiv=rdquoContent-Typerdquo content=rdquotexthtml charset=UTF-7rdquo gt

lttitlegtnodejs index html filelttitlegt

ltheadgt

ltbodygt

lth1gtnodejs index html filelth1gt

ltbodygt

lthtmlgt

ltDOCTYPE htmlgt

lthtml xmlns=rdquohttpwwww3org1999xhtmlrdquo xmllang=rdquozh-CNrdquo lang=rdquozh-CNrdquogt

ltheadgt

ltmeta http-equiv=rdquoContent-Typerdquo content=rdquotexthtml charset=UTF-7rdquo gt

lttitlegtnodejs test html filelttitlegt

ltheadgt

ltbodygt

lth1gtnodejs test html filelth1gt

ltbodygt

lthtmlgt

ltDOCTYPE htmlgt

lthtml xmlns=rdquohttpwwww3org1999xhtmlrdquo xmllang=rdquozh-CNrdquo lang=rdquozh-CNrdquogt

ltheadgt

ltmeta http-equiv=rdquoContent-Typerdquo content=rdquotexthtml charset=UTF-7rdquo gt

lttitlegtnodejs static html filelttitlegt

ltheadgt

ltbodygt

lth1gtnodejs static html filelth1gt

ltbodygt

lthtmlgt

36 4 Nodejs基礎

Nodejs中文電子書

準備一個包含基本路由功能的 http伺服器

var server

ip = rdquo127001rdquo

port = 1337

http = require(rsquohttprsquo)

url = require(rsquourlrsquo)

server = httpcreateServer(function (req res)

var path = urlparse(requrl)

)

serverlisten(port ip)

consolelog(rdquoServer running at httprdquo + ip + rdquordquo + port)

加入 le system 模組使用 readFile 的功能將這一段程式放置於

createServer的回應函式中

fsreadFile(filePath encode function(err file)

)

readFile的回應函式裡面加入頁面輸出讓瀏覽器可以正確讀到檔案在這邊我們設定讀取的檔案為 html 靜態檔案所以 Content-type 設定為texthtml讀取到檔案的內容將會正確輸出成 html靜態檔案

fsreadFile(filePath encode function(err file)

reswriteHead(200 rsquoContent-Typersquo rsquotexthtmlrsquo)

reswrite(file)

resend()

)

到這邊為止基本的程式內容都已經完成剩下一些細節的調整首先

路徑上必須做調整目前的靜態檔案全部都放置於 static資料夾底下設定一個變數來記住資料夾位置

接著將瀏覽器發出要求路徑與資料夾組合讀取正確 html靜態檔案使用者有可能會輸入錯誤路徑所以在讀取檔案的時候要加入錯誤處理

同時回應 404伺服器無法正確回應的 http header格式

加入這些細節的修改一個基本的 http 靜態 html輸出伺服器就完成了完整程式碼如下

44 nodejs http靜態檔案輸出 37

Nodejs中文電子書

var server

ip = rdquo127001rdquo

port = 1337

http = require(rsquohttprsquo)

fs = require(rdquofsrdquo)

folderPath = rdquostaticrdquo

url = require(rsquourlrsquo)

path

filePath

encode = rdquoutf8rdquo

server = httpcreateServer(function (req res)

path = urlparse(requrl)

filePath = folderPath + pathpathname

fsreadFile(filePath encode function(err file)

if (err)

reswriteHead(404 rsquoContent-Typersquo rsquotextplainrsquo)

resend()

return

reswriteHead(200 rsquoContent-Typersquo rsquotextapplicationrsquo)

reswrite(file)

resend()

)

)

serverlisten(port ip)

consolelog(rdquoServer running at httprdquo + ip + rdquordquo + port)

45 nodejs http GET資料擷取

http伺服器中除了路由之外另一個最常使用的方法就是擷取 GET資料本單元將會介紹如何透過基本 http伺服器擷取瀏覽器傳來的要求擷取 GET資料

在 http協定中GET參數都是藉由 URL從瀏覽器發出要求送至伺服器

38 4 Nodejs基礎

Nodejs中文電子書

端基本的傳送網址格式可能如下

http127001testsend=1amptest=2

上面這段網址裡面的 GET參數就是 send而這個變數的數值就為 1如果想要在 http伺服器取得 GET資料需要在瀏覽器給予的要求 (request)做處理

首先需要載入 query string這個模組這個模組主要是用來將字串資料

過濾後轉換成javascript物件

qs = require(rsquoquerystringrsquo)

接著在第一階段利用 url模組過濾瀏覽器發出的 URL資料後將回應的物件裡面的 query這個變數是一個字串值資料過濾後如下

send=1amptest=2

透過 query string使用 parse這個方法將資料轉換成 javascript物件就表示 GET的資料已經被伺服器端正式擷取下來

path = urlparse(requrl)

parameter = qsparse(pathquery)

整個 nodejs http GET參數完整擷取程式碼如下

var server

ip = rdquo127001rdquo

port = 1337

http = require(rsquohttprsquo)

qs = require(rsquoquerystringrsquo)

url = require(rsquourlrsquo)

server = httpcreateServer(function (req res)

var path = urlparse(requrl)

parameter = qsparse(pathquery)

consoledir(parameter)

reswriteHead(200 rsquoContent-Typersquo rsquotextplainrsquo)

reswrite(rsquoBrowser test GET parameternrsquo)

resend()

)

45 nodejs http GET資料擷取 39

Nodejs中文電子書

serverlisten(port ip)

consolelog(rdquoServer running at httprdquo + ip + rdquordquo + port)

程式運作之後由瀏覽器輸入要求網址之後nodejs伺服器端回應資料為

Server running at http1270011337

send rsquo1rsquo test rsquo1rsquo

46 本章結語

前面所解說的部份一大部分主要是處理 http伺服器基本問題雖然在某些部分有牽扯到 http 伺服器基本運作原理主要還是希望可以藉由這些基本範例練習 nodejs練習回應函式與語法串接的特點習慣編寫javascript風格程式當然直接這樣開發 nodejs是非常辛苦的接下來在模組實戰開發的部份將會介紹特定的模組一步一步帶領各位從無到有進行

nodejs應用程式開發

40 4 Nodejs基礎

5

NPM套件管理工具

npm全名為 Node Package Manager是 Nodejs的套件(package)管理工具類似 Perl的 ppm或 PHP的 PEAR等安裝 npm後使用npm install

module name指令即可安裝新套件維護管理套件的工作會更加輕鬆

npm可以讓Nodejs的開發者直接利用擴充線上的套件庫(packagesregistry)加速軟體專案的開發npm提供很友善的搜尋功能可以快速找到安裝需要的套件當這些套件發行新版本時npm也可以協助開發者自動更新這些套件

npm不僅可用於安裝新的套件它也支援搜尋列出已安裝模組及更新的功能

51 安裝 NPM

Nodejs在 063版本開始內建 npm讀者安裝的版本若是此版本或更新的版本就可以略過以下安裝說明

若要檢查 npm是否正確安裝可以使用以下的指令

npm -v

41

Nodejs中文電子書

執行結果說明

若 npm正確安裝執行 npm -v將會看到類似 110-2的版本訊息

若讀者安裝的 Nodejs版本比較舊或是有興趣嘗試自己動手安裝 npm工具則可以參考以下的說明

安裝於Windows系統

Nodejs for Windows 於 062 版開始內建 npm使用 nodejsorg 官方提供的安裝程式不需要進一步的設定就可以立即使用 npm指令對於Windows的開發者來說大幅降低環境設定的問題與門檻

除了使用 Nodejs內建的 npm讀者也可以從 npm官方提供的以下網址

httpnpmjsorgdist

這是由 npm提供的 Fancy Windows Install版本請下載壓縮檔(例如npm-110-3zip)並將壓縮檔內容解壓縮至 Nodejs的安裝路徑(例如CProgram Filesnodejs)

解壓縮後在 Nodejs的安裝路徑下應該有以下的檔案及資料夾

bull npmcmd(檔案)

bull node modules(資料夾)

安裝於 Linux系統

Ubuntu Linux的使用者可以加入 NPM Unoffcial PPA這個 repository即可使用 apt-get完成 npm安裝

42 5 NPM套件管理工具

Nodejs中文電子書

Ubuntu Linux使用 apt-get安裝 npm

sudo apt-get install python-software-properties

sudo add-apt-repository ppagias-kay-leenpm

sudo apt-get update

sudo apt-get install npm

npm官方提供的安裝程式 installsh可以適用於大多數的 Linux系統使用這個安裝程式請先確認

1 系統已安裝 curl工具(請使用 curl --version查看版本訊息)

2 已安裝 Nodejs並且 PATH正確設置

3 Nodejs的版本必須大於 04x

以下為 npm提供的安裝指令

curl httpnpmjsorginstallsh | sh

安裝成功會看到如下訊息

installsh安裝成功的訊息

npm10105 homeUSERNAMElocalnodelibnode_modulesnpm

It worked

安裝於Mac OS X

建議採用與 Nodejs相同的方式進行 npm的安裝例如使用MacPorts安裝 Nodejs就同樣使用MacPorts安裝 npm這樣對日後的維護才會更方便容易

使用 MacPorts安裝 npm是本書比較建議的方式它可以讓 npm的安裝移除及更新工作自動化將會幫助開發者節省寶貴時間

51 安裝 NPM 43

Nodejs中文電子書

安裝MacPorts的提示

在MacPorts網站可以取得 OS X系統版本對應的安裝程式(例如 106或 107)httpwwwmacportsorg安裝過程會詢問系統管理者密碼使用預設的選項完成安裝即可安

裝MacPorts之後在終端機執行 port -v將會看到MacPorts的版本訊息

安裝 npm之前先更新MacPorts的套件清單以確保安裝的 npm是最新版本

sudo port -d selfupdate

接著安裝 npm

sudo port install npm

若讀者的 Nodejs並非使用MacPorts安裝則不建議使用MacPorts安裝npm因為 MacPorts會自動檢查並安裝相依套件而 npm相依 nodejs所以MacPorts也會一併將 nodejs套件安裝造成先前讀者使用其它方式安裝的 nodejs被覆蓋

讀者可以先使用 MacPorts安裝 curl(sudo port install curl)

再參考 Linux的 installsh安裝方式即可使用 npm官方提供的安裝程式

NPM安裝後測試

npm是指令列工具(command-line tool)使用時請先打開系統的文字終端機工具

測試 npm安裝與設定是否正確請輸入指令如下

npm -v

或是

npm --version

如果 npm已經正確安裝設定就會顯示版本訊息

44 5 NPM套件管理工具

Nodejs中文電子書

執行結果(範例)

110-2

52 使用 NPM安裝套件

npm目前擁有超過 6000種套件(packages)可以在 npm registry使用關鍵字搜尋套件

httpsearchnpmjsorg

舉例來說在關鍵字欄位輸入「coffee-script」下方的清單就會自動列出包含 coffee-script關鍵字的套件

接著我們回到終端機模式的操作npm的指令工具本身就可以完成套

件搜尋的任務

例如以下的指令同樣可以找出 coffee-script相關套件

npm search coffee-script

以下是搜尋結果的參考畫面

52 使用 NPM安裝套件 45

Nodejs中文電子書

找到需要的套件後(例如 express)即可使用以下指令安裝

npm install coffee-script

值得注意的一點是使用 npm install會將指定的套件安裝在工

作目錄(Working Directory)的 node modules資料夾下

以Windows為例如果執行 npm install的目錄位於

Cproject1

那麼 npm將會自動建立一個 node modules的子目錄(如果不存在)

Cproject1node modules

並且將下載的套件放置於這個子目錄例如

Cproject1node modulescoffee-script

這個設計讓專案可以個別管理相依的套件並且可以在專案佈署或發

行時將這些套件(位於 node modules)一併打包方便其它專案的使用者不必再重新下載套件

這個 npm install的預設安裝模式為 local(本地)只會變更當前專案的資料夾不會影響系統

另一種安裝模式稱為 global(全域)這種模式會將套件安裝到系統資

料夾也就是 npm安裝路徑的 node modules資料夾例如

CProgram Filesnodejsnode modules

46 5 NPM套件管理工具

Nodejs中文電子書

是否要使用全域安裝可以依照套件是否提供新指令來判斷舉例來說express 套件提供 express 這個指令而 coffee-script 則提供 coffee

指令

在 local 安 裝 模 式 中這 些 指 令 的 程 式 檔 案會 被 安 裝 到node modules的 bin這個隱藏資料夾下除非將bin的路徑加入 PATH環境變數否則要執行這些指令將會相當不便

為了方便指令的執行我們可以在 npm install 加上 -g 或 --

global參數啟用 global安裝模式例如

npm install -g coffee-script

npm install -g express

使用 global安裝模式需要注意執行權限的問題若權限不足可能會出現類似以下的錯誤訊息

npm ERR Error EACCES permission denied rsquorsquo

npm ERR

npm ERR Please try running this command again as rootAdministrator

要獲得足夠得執行權限請參考以下說明

bull Windows 7 或 2008 以上在「命令提示字元」的捷徑按右鍵選擇「以系統管理員身分執行」執行 npm指令時就會具有 Administrator身分

bull Mac OS X或 Linux系統可以使用 sudo指令例如

sudo npm install -g express

bull Linux系統可以使用 root權限登入或是以「sudo su -」切換成 root身分(使用 root權限操作系統相當危險因此並不建議使用這種方式)

若加上 -g參數使用 npm install -g coffee-script完成安裝

後就可以在終端機執行 coffee指令例如

coffee -v

52 使用 NPM安裝套件 47

Nodejs中文電子書

執行結果(範例)

CoffeeScript version 120

53 套件的更新及維護

除了前一節說明的 search 及 install 用法npm 還提供其他許多指令(commands)

使用 npm help可以查詢可用的指令

npm help

執行結果(部分)

where ltcommandgt is one of

adduser apihelp author bin bugs c cache completion

config deprecate docs edit explore faq find get

help help-search home i info init install la link

list ll ln login ls outdated owner pack prefix

prune publish r rb rebuild remove restart rm root

run-script s se search set show star start stop

submodule tag test un uninstall unlink unpublish

unstar up update version view whoami

使用 npm help command可以查詢指令的詳細用法例如

npm help list

接下來本節要介紹開發過程常用的 npm指令

使用 list可以列出已安裝套件

npm list

48 5 NPM套件管理工具

Nodejs中文電子書

執行結果(範例)

-- coffee-script120

- express256

- connect185

| -- formidable108

-- mime124

-- mkdirp007

-- qs041

檢視某個套件的詳細資訊例如

npm show express

升級所有套件(如果該套件已發佈更新版本)

npm update

升級指定的套件

npm update express

移除指定的套件

npm uninstall express

54 使用 packagejson

對於正式的 Nodejs專案可以建立一個命名為 packagejson的設

定檔(純文字格式)檔案內容參考範例如下

54 使用 packagejson 49

Nodejs中文電子書

packagejson(範例)

rdquonamerdquo rdquoapplication-namerdquo

rdquoversionrdquo rdquo001rdquo

rdquoprivaterdquo true

rdquodependenciesrdquo

rdquoexpressrdquo rdquo255rdquo

rdquocoffee-scriptrdquo rdquolatestrdquo

rdquomongooserdquo rdquogt= 253rdquo

其中 name與 version依照專案的需求設置

需要注意的是 dependencies的設定它用於指定專案相依的套件名

稱及版本

bull rdquoexpressrdquo rdquo255rdquo

代表此專案相依版本 255的 express套件

bull rdquocoffee-scriptrdquo rdquolatestrdquo

使用最新版的 coffee-script套件(每次更新都會檢查新版)

bull rdquomongooserdquo rdquogt= 253rdquo

使用版本大於 253的 mongoose套件

假設某個套件的新版可能造成專案無法正常運作就必須指定套件的

版本避免專案的程式碼來不及更新以相容新版套件通常在開發初期的

專案需要盡可能維持新套件的相容性(以取得套件的更新或修正)可以

用「gt=」設定最低相容的版本或是使用「latest」設定永遠保持最新

套件

50 5 NPM套件管理工具

6

Express介紹

在前面的 nodejs基礎當中介紹許多許多開設 http的使用方法及介紹以及許多基本的 nodejs基本應用

接下來要介紹一個套件稱為 express [Express](httpexpressjscom)這個套件主要幫忙解決許多 nodejs http server所需要的基本服務讓開發 httpservice變得更為容易不需要像之前需要透過層層模組(module)才有辦法開始編寫自己的程式

這個套件是由 TJ Holowaychuk 製作而成的套件裡面包含基本的路由處理 (route)http資料處理(GETPOSTPUT)另外還與樣板套件(jshtml template engine)搭配同時也可以處理許多複雜化的問題

61 Express安裝

安裝方式十分簡單只要透過之前介紹的 NPM就可以使用簡單的指令安裝指令如下

這邊建議需要將此套件安裝成為全域模組方便日後使用

51

Nodejs中文電子書

62 Express基本操作

express的使用也十分簡單先來建立一個基本的 hello world

var app = require(rsquoexpressrsquo)createServer()

port = 1337

applisten(port)

appget(rsquorsquo function(req res)

ressend(rsquohello worldrsquo)

)

consolelog(rsquostart express servernrsquo)

可以從上面的程式碼發現基本操作與 nodejs http的建立方式沒有太大差異主要差在當我們設定路由時可以直接透過 appget方式設定回應與接受方式

63 Express路由處理

Express對於 http服務上有許多包裝讓開發者使用及設定上更為方便例如有幾個路由設定那我們就統一藉由 appget來處理

Create http server

appget(rsquorsquo function(req res)

ressend(rsquohello worldrsquo)

)

appget(rsquotestrsquo function(req res)

ressend(rsquotest renderrsquo)

)

appget(rsquouserrsquo function(req res)

ressend(rsquouser pagersquo)

)

52 6 Express介紹

Nodejs中文電子書

如上面的程式碼所表示appget可以帶入兩個參數第一個是路徑名稱設定第二個為回應函式 (call back function)回應函式裡面就如同之前的 createServer方法裡面包含 requestresponse兩個物件可供使用使用者就可以透過瀏覽器輸入不同的 url切換到不同的頁面顯示不同的結果

路由設定上也有基本的配對方式讓使用者從瀏覽器輸入的網址可以

是一個變數只要符合型態就可以有對應的頁面產出例如

Create http server

appget(rsquouseridrsquo function(req res)

ressend(rsquouser rsquo + reqparamsid)

)

appget(rsquonumberrsquo function(req res)

ressend(rsquonumber rsquo + reqparamsnumber)

)

裡 面 使 用 到number 從 網 址 輸 入 之 後 就 可 以 直 接 使 用reqparamsnumber 取得所輸入的資料變成 url 參數使用當然前面也是可以加上路徑的設定userid在瀏覽器上路徑必須符合userxxx透

過 reqparamsid就可以取到 xxx這個字串值

另外express參數處理也提供了路由參數配對處理也可以透過正規表示法作為參數設定

var app = require(rsquoexpressrsquo)createServer()

port = 1337

applisten(port)

appget(^ip((d23)((d23))((d23))((d23))) function(req res)

ressend(reqparams)

)

上面程式碼可以發現後面路由設定的型態是正規表示法裡面設定

格式為ip之後必須要加上 ip型態才會符合資料格式同時取得 ip資料已經由正規表示法將資料做分群因此可以取得 ip的四個數字

此程式執行之後可以透過瀏覽器測試輸入網址為 localhost3000ip25525510010可以從頁面獲得資料

63 Express路由處理 53

Nodejs中文電子書

此章節全部範例程式碼如下

overview

author Caesar Chi

blog clonnblogspotcom

version 20120226

create server

var app = require(rsquoexpressrsquo)createServer()

port = 1337

applisten(port)

normal style

appget(rsquorsquo function(req res)

ressend(rsquohello worldrsquo)

)

appget(rsquotestrsquo function(req res)

ressend(rsquotest renderrsquo)

)

parameter style

appget(rsquouseridrsquo function(req res)

ressend(rsquouser rsquo + reqparamsid)

)

appget(rsquonumberrsquo function(req res)

ressend(rsquonumber rsquo + reqparamsnumber)

)

REGX style

appget(^ip((d23)((d23))((d23))) function(req res)

ressend(reqparams)

)

appget(rsquorsquo function(req res)

ressend(rsquoPage not foundrsquo 404)

54 6 Express介紹

Nodejs中文電子書

)

consolelog(rsquostart express servernrsquo)

64 Express middleware

Express 裡面有一個十分好用的應用概念稱為 middleware可以透過middleware做出複雜的效果同時上面也有介紹 next方法參數傳遞就是靠 middleware的概念來傳遞參數讓開發者可以明確的控制程式邏輯

create http server

appuse(expressbodyParser())

appuse(expressmethodOverride())

appuse(expresssession())

上面都是一種 middleware的使用方式透過 appuse方式裡面載入函式執行方法回應函式會包含三個基本參數responserequestnext其中next表示下一個 middleware執行函式同時會自動將預設三個參數繼續帶往下個函式執行底下有個實驗

上面的片段程式執行後開啟瀏覽器連結上 localhost1337會發現伺服器回應結果順序如下

first middle ware

second middle ware

execute middle ware

end middleware function

從上面的結果可以得知剛才設定的 middleware都生效了在 appuse設定的 middleware是所有 url皆會執行方法如果有指定特定方法就可以使用 appget的 middleware設定在 appget函式的第二個參數就可以帶入函式或者是匿名函式只要函式裡面最後會接受 request response next這三個參數同時也有正確指定 next函式的執行時機最後都會執行到最後一個方法當然開發者也可以評估程式邏輯要執行到哪一個階段讓邏輯

可以更為分明

64 Express middleware 55

Nodejs中文電子書

65 Express路由應用

在實際開發上可能會遇到需要使用參數等方式混和變數一起使用

express裡面提供了一個很棒的處理方法 appall這個方式可以先採用基本路由配對再將設定為每個不同的處理方式開發者可以透過這個方式簡

化自己的程式邏輯

overview

author

version 20120226

create server

var app = require(rsquoexpressrsquo)createServer()

port = 1337

users = [

name rsquoClonnrsquo

name rsquoChirsquo

]

applisten(port)

appall(rsquouseridoprsquo function(req res next)

requser = users[reqparamsid]

if (requser)

next()

else

next(new Error(rsquocannot find user rsquo + reqparamsid))

)

appget(rsquouseridrsquo function(req res)

ressend(rsquoviewing rsquo + requsername)

)

appget(rsquouserideditrsquo function(req res)

ressend(rsquoediting rsquo + requsername)

)

56 6 Express介紹

Nodejs中文電子書

appget(rsquouseriddeletersquo function(req res)

ressend(rsquodeleting rsquo + requsername)

)

appget(rsquorsquo function(req res)

ressend(rsquoPage not foundrsquo 404)

)

consolelog(rsquostart express servernrsquo)

內部宣告一組預設的使用者分別給予名稱設定藉由 appall這個方法可以先將路由雛形建立再接下來設定 appget的路徑格式只要符合格式就會分配進入對應的方法中像上面的程式當中如果使用者輸入路徑為user0除了執行 appall程式之後執行 next方法就會對應到路徑設定為userid的這個方法當中如果使用者輸入路徑為user0edit就會執行到useridedit的對應方法

66 Express GET應用範例

我們準備一個使用 GET方法傳送資料的表單

ltDOCTYPE htmlgt

lthtmlgt

ltheadgt

ltmeta http-equiv=rdquoContent-Typerdquo content=rdquotexthtml charset=utf8rdquogt

lttitlegtNodejs 菜鳥筆記 (1)lttitlegt

ltlink rel=rdquostylesheetrdquo href=rdquocssstylecssrdquo type=rdquotextcssrdquo media=rdquoallrdquo gt

ltheadgt

ltbodygt

lth1gtNodejs 菜鳥筆記-註冊lth1gt

ltform id=rdquosignuprdquo method=rdquoGETrdquo action=rdquohttplocalhost3000Signuprdquogt

ltlabelgt 使用者名稱ltlabelgtltinput type=rdquotextrdquo id=rdquousernamerdquo name=rdquousernamerdquo gtltbrgt

ltlabelgt 電子郵件ltlabelgtltinput type=rdquotextrdquo id=rdquoemailrdquo name=rdquoemailrdquo gtltbrgt

ltinput type=rdquosubmitrdquo value=rdquo 註冊我的帳號rdquo gtltbrgt

ltformgt

ltbodygt

lthtmlgt

66 Express GET應用範例 57

Nodejs中文電子書

這個表單沒有什麼特別的地方我們只需要看第 9 行form 使用的method是 GET然後 action是rdquohttplocalhost3000Signupldquo等一下我們要來撰寫Signup這個 URL Path的處理程式

處理 Signup行為

我們知道所謂的 GET方法會透過 URL來把表單的值給帶過去以上面的表單來說到時候 URL會以這樣的形式傳遞

httplocalhost3000Signupusername=xxxampemail=xxx

所以要能處理這樣的資料必須有以下功能

bull 解析 URL

bull 辨別動作是 Signup

bull 解析出 username和 email

一旦能取得 username和 email的值程式就能加以應用了

處理 Signup的程式碼雛形

load module

var url = require(rsquourlrsquo)

urlData = urlparse(requrltrue)

action = urlDatapathname

reswriteHead(200 rdquoContent-Typerdquordquotexthtml charset=utf-8rdquo)

if (action === rdquoSignuprdquo)

user = urlDataquery

resend(rdquolth1gtrdquo + userusername + rdquo 歡迎您的加入 lth1gtltpgt 我們已經將會員啟用信寄至rdquo + useremail + rdquoltpgtrdquo)

首先需要加載 url module它是用來協助我們解析 URL的模組接著使用 urlparse方法第一個傳入 url字串變數也就是 requrl另外第二個參數的用意是設為 ture則引進 querystring模組來協助處理預設是 false它影響到的是 urlDataquery設為 true會傳回物件不然就只是一般的字串urlparse會將字串內容整理成一個物件我們把它指定給 urlData

action變數作為記錄 pathname這是我們稍後要來判斷目前網頁的動作是什麼接著先將 html表頭資訊 (Header)準備好再來判斷路徑邏輯如

58 6 Express介紹

Nodejs中文電子書

果是 Signup這個動作就把 urlDataquery裡的資料指定給 user然後輸出userusername和 useremail把使用者從表單註冊的資料顯示於頁面中

最後進行程式測試啟動 nodejs主程式之後開啟瀏覽器就會看到表單填寫完畢按下送出就可以看到結果了

完整 nodejs程式碼如下

var http = require(rsquohttprsquo)

url = require(rsquourlrsquo)

fs = require(rdquofsrdquo)

server

server = httpcreateServer(function (reqres)

var urlData

encode = rdquoutf8rdquo

filePath = rdquoviewexpress_get_example_formhtmlrdquo

action

urlData = urlparse(requrltrue)

action = urlDatapathname

reswriteHead(200 rdquoContent-Typerdquordquotexthtml charset=utf-8rdquo)

if (action === rdquoSignuprdquo)

user = urlDataquery

resend(rdquolth1gtrdquo + userusername + rdquo 歡迎您的加入 lth1gtltpgt 我們已經將會員啟用信寄至rdquo + useremail + rdquoltpgtrdquo)

else

fsreadFile(filePath encode function(err file)

reswrite(file)

resend()

)

)

serverlisten(3000)

consolelog(rsquoServer 跑起來了現在時間是rsquo + new Date())

66 Express GET應用範例 59

Nodejs中文電子書

67 Express POST應用範例

一開始準備基本的 html表單傳送內容以 POST方式form的 action屬性設定為 POST其餘 html內容與前一個範例應用相同

ltDOCTYPE htmlgt

lthtmlgt

ltheadgt

ltmeta http-equiv=rdquoContent-Typerdquo content=rdquotexthtml charset=utf8rdquogt

lttitlegtNodejs 菜鳥筆記 (1)lttitlegt

ltlink rel=rdquostylesheetrdquo href=rdquocssstylecssrdquo type=rdquotextcssrdquo media=rdquoallrdquo gt

ltheadgt

ltbodygt

lth1gtNodejs 菜鳥筆記-註冊lth1gt

ltform id=rdquosignuprdquo method=rdquoPOSTrdquo action=rdquohttplocalhost3000Signuprdquogt

ltlabelgt 使用者名稱ltlabelgtltinput type=rdquotextrdquo id=rdquousernamerdquo name=rdquousernamerdquo gtltbrgt

ltlabelgt 電子郵件ltlabelgtltinput type=rdquotextrdquo id=rdquoemailrdquo name=rdquoemailrdquo gtltbrgt

ltinput type=rdquosubmitrdquo value=rdquo 註冊我的帳號rdquo gtltbrgt

ltformgt

ltbodygt

lthtmlgt

nodejs的程式處理邏輯與前面 GET範例類似部分程式碼如下

qs = require(rsquoquerystringrsquo)

if (action === rdquoSignuprdquo)

formData = rsquorsquo

reqon(rdquodatardquo function (data)

formData += data

)

reqon(rdquoendrdquo function ()

user = qsparse(formData)

resend(rdquolth1gtrdquo + userusername + rdquo 歡迎您的加入 lth1gtltpgt 我們已經將會員啟用信寄至rdquo + useremail + rdquoltpgtrdquo)

)

60 6 Express介紹

Nodejs中文電子書

主要加入了rsquoquerystringrsquo這個moduel方便我們等一下解析由表單 POST回來的資料另外加入一個 formData的變數用來搜集待等一下表單回傳的資料前面的 GET範例我們只從 req拿出 url的資料這次要在利用req身上的事件處理

JavaScript 在訂閱事件時使用 addEventListener而 nodejs 使用的則是on這邊加上了監聽 data的事件會在瀏覽器傳送資料到Web Server時被執行參數是它所接收到的資料型態是字串

接著再增加 end的事件當瀏覽器的請求事件結束時它就會動作

由於瀏覽器使用 POST在上傳資料時會將資料一塊塊地上傳因為我們在監聽 data事件時透過 formData變數將它累加起來lt不過由於我們

上傳的資料很少一次就結束不過如果日後需要傳的是資料比較大的檔

案這個累加動作就很重要

當資料傳完就進到 end 事件中會用到 qsparse 來解析 formDataformData的內容是字串內容是

username=wordsmithampemail=wordsmith40somewhere

而 qsparse可以幫我們把這個 querystring轉成物件的格式也就是

username=wordsmithampemail=wordsmith40somewhere

一旦轉成物件並指定給 user之後其他的事情就和 GET方法時操作的一樣寫 response的表頭將內容回傳並將 userusername和 useremail代入到內容中

修改完成後接著執行 nodejs程式啟動 web server開啟瀏覽器進入表單測試看看POST的方式能否順利運作

完整程式碼如下

var http = require(rsquohttprsquo)

url = require(rsquourlrsquo)

fs = require(rdquofsrdquo)

qs = require(rsquoquerystringrsquo)

server

server = httpcreateServer(function (reqres)

var urlData

67 Express POST應用範例 61

Nodejs中文電子書

encode = rdquoutf8rdquo

filePath = rdquoviewexpress_post_example_formhtmlrdquo

formData

action

urlData = urlparse(requrltrue)

action = urlDatapathname

reswriteHead(200 rdquoContent-Typerdquordquotexthtml charset=utf-8rdquo)

if (action === rdquoSignuprdquo)

formData = rsquorsquo

reqon(rdquodatardquo function (data)

formData += data

)

reqon(rdquoendrdquo function ()

user = qsparse(formData)

resend(rdquolth1gtrdquo + userusername + rdquo 歡迎您的加入 lth1gtltpgt 我們已經將會員啟用信寄至rdquo + useremail + rdquoltpgtrdquo)

)

else

fsreadFile(filePath encode function(err file)

reswrite(file)

resend()

)

)

serverlisten(3000)

consolelog(rsquoServer 跑起來了現在時間是rsquo + new Date())

62 6 Express介紹

Nodejs中文電子書

68 Express AJAX應用範例

在 Nodejs要使用 Ajax傳送資料並且與之互動在接受資料的部份沒有太大的差別client端不是用 GET就是用 POST來傳資料重點在處理完後用 JSON格式回傳當然 Ajax不見得只傳 JSON格式有時是回傳一段 HTML碼不過後者對伺服器來說基本上就和前兩篇沒有差別了所以我們還是以回傳 JSON做為這一回的主題

這一回其實大多數的工作都會落在前端 Ajax上面前端要負責發送與接收資料並在接收資料後撤掉原先發送資料的表單並將取得的資料

改成 HTML格式之後放上頁面

首先先準備 HTML靜態頁面資料

ltDOCTYPE htmlgt

lthtmlgt

ltheadgt

lttitlegtNodejs 菜鳥筆記 (3)lttitlegt

ltlink rel=rdquostylesheetrdquo href=rdquocssstylecssrdquo type=rdquotextcssrdquo media=rdquoallrdquo gt

ltscript src=rdquohttpsajaxgoogleapiscomajaxlibsjquery171jqueryminjsrdquogtltscriptgt

ltheadgt

ltbodygt

lth1gtNodejs 菜鳥筆記-註冊 (用 Ajax)lth1gt

ltform id=rdquosignuprdquo action=rdquoSignuprdquo method=rdquoPOSTrdquogt

ltlabelgt 使用者名稱ltlabelgtltinput type=rdquotextrdquo id=rdquousernamerdquo name=rdquousernamerdquo gtltbrgt

ltlabelgt 電子郵件ltlabelgtltinput type=rdquotextrdquo id=rdquoemailrdquo name=rdquoemailrdquo gtltbrgt

ltinput type=rdquosubmitrdquo value=rdquo 註冊我的帳號rdquo gtltbrgt

ltformgt

ltdiv id=rdquoresultrdquogt

ltdivgt

ltscript type=rdquotextjavascriptrdquogt

$(function()$(rsquosignup input[type=rdquosubmitrdquo]rsquo)click(function(e)

var user =

userusername = $(rdquousernamerdquo)val()useremail = $(rdquoemailrdquo)val()

$post(rdquoSignuprdquouserfunction(data)greet(data)

)

68 Express AJAX應用範例 63

Nodejs中文電子書

epreventDefault()

)

var greet = function(msg)

var resultNode = $(rsquoresultrsquo)greeting = $(rsquolth2gtHirsquo+msgusername+rsquo 你的會員 id 是rsquo+ msgid +rsquo 我們會將會員啟動信寄至rsquo+msgemail+rsquolth2gtrsquo)

$(rsquosignuprsquo)hide()resultNodehtml(rdquordquo)

resultNodeappend(greeting)

)

ltscriptgt

ltbodygt

lthtmlgt

HTML頁面上準備了一個表單用來傳送註冊資料接著直接引用了Google CDN來載入 jQuery用來幫我們處理 Ajax的工作這次要傳送和接收的工作很大的變動都在 HTML頁面上的 JavaScript當中我們要做的事有 (相關 jQuery處理這邊不多做贅述指提起主要功能解說)

bull 用 jQUery取得 submit按鈕綁定它的 click動作

bull 取得表單 username和 email的值存放在 user這個物件中

bull 用 jQuery的 $post方法將 user的資料傳到 Server

bull 一旦成功取得資料後透過 greet這個 function組成回報給 user的訊息

bull 清空原本給使用者填資料的表單

bull 將 Server回傳的 usernameemail和 id這 3個資料組成回應的訊息

bull 將訊息放到原本表單的位置

經過以上的處理後一個 Ajax的表單的基本功能已經完成

接著進行 nodejs主要程式的編輯部分程式碼如下

var fs = require(rdquofsrdquo)

64 6 Express介紹

Nodejs中文電子書

qs = require(rsquoquerystringrsquo)

if (action === rdquoSignuprdquo)

formData = rsquorsquo

reqon(rdquodatardquo function (data)

formData += data

)

reqon(rdquoendrdquo function ()

var msg

user = qsparse(formData)

userid = rdquo123456rdquo

msg = JSONstringify(user)

reswriteHead(200 rdquoContent-Typerdquordquoapplicationjson charset=utf-8rdquordquoContent-Lengthrdquomsglength)

resend(msg)

)

這裡的程式和前面 POST範例基本上大同小異差別在

bull 幫 user的資料加上 id隨意存放一些文字進去讓 Server回傳的資料多於 Client端傳上來的不然會覺得 Server都沒做事

bull 增加了 msg 這個變數存放將 user 物件 JSON 文字化的結果JSONstringify這個轉換函式是 V8引擎所提供的如果你好奇的話

bull 大重點來了我們要告訴 Client端這次回傳的資料格式是 JSON所在 Content-type和 Content-Length要提供給 Client

Server很輕鬆就完成任務了最後進行程式測試啟動 nodejs主程式之後開啟瀏覽器就會看到表單填寫完畢按下送出就可以看到結果了

最後 nodejs本篇範例程式碼如下

var http = require(rsquohttprsquo)

url = require(rsquourlrsquo)

fs = require(rdquofsrdquo)

qs = require(rsquoquerystringrsquo)

server

68 Express AJAX應用範例 65

Nodejs中文電子書

server = httpcreateServer(function (reqres)

var urlData

encode = rdquoutf8rdquo

filePath = rdquoviewexpress_ajax_example_formhtmlrdquo

formData

action

urlData = urlparse(requrltrue)

action = urlDatapathname

if (action === rdquoSignuprdquo)

formData = rsquorsquo

reqon(rdquodatardquo function (data)

formData += data

)

reqon(rdquoendrdquo function ()

var msg

user = qsparse(formData)

userid = rdquo123456rdquo

msg = JSONstringify(user)

reswriteHead(200 rdquoContent-TyperdquordquoapplicationjsonrdquordquoContent-Lengthrdquomsglength)

resend(msg)

)

else

fsreadFile(filePath encode function(err file)

reswriteHead(200 rdquoContent-Typerdquordquotexthtml charset=utf-8rdquo)

reswrite(file)

resend()

)

)

serverlisten(3000)

66 6 Express介紹

Nodejs中文電子書

consolelog(rsquoServer 跑起來了現在時間是rsquo + new Date())

69 原始資料提供

bull [NodeJS初學者筆記 (1)-用 GET傳送資料] (httpithelpithomecomtwquestion10087402)

bull [NodeJS初學者筆記 (2)-用 POST傳送資料] (httpithelpithomecomtwquestion10087489)

bull [NodeJS 初學者筆記 (3)-用 Ajax 傳送資料] (httpithelpithomecomtwquestion10087627)

69 原始資料提供 67

Nodejs中文電子書

68 6 Express介紹

7

CoffeeScript

bull Spine

bull Hem

bull Spineapp

Letrsquos Have a Cup of CoffeeScript

bull es5-shim ECMAScript 5 compatibility shims for legacy JavaScript engines

ESnext

node-iform - A middleware for node-validator httpsgithubcomguileennode-iform

69

Nodejs中文電子書

70 7 CoffeeScript

8

製作一個 Hubot的 Plurk Adapter

81 應用事項提醒

此應用範例以CoffeeScript編寫主要程式架構採用Github釋放的Hubot專案以下有幾個主要注意事項請讀者注意

bull Hubot 一開始的架構除了內建的兩個 Adapter 之外其餘都要以Nodejs的Module方式才能運作

bull Hubot的 binhubot裡面寫著 npm install所以不管你怎麼改原始碼也不能改變第一點的狀況

其實上述都在討論同一件事情NodeJS的模組而且模組不能設定相對路徑之類的來安裝一定要包裝成 npm相符和格式才有辦法正確執行

82 建立 Adapter

首先需要準備兩個檔案

bull packagejson

bull plurkcoffee

71

Nodejs中文電子書

有這兩個就足以變成 NodeJS的模組了在開始 Coding之前先來設定一下 packagejson相依性

rdquonamerdquo rdquohubot-plurkrdquo

rdquoversionrdquo rdquo011rdquo

rdquomainrdquo rdquoplurkrdquo

rdquodependenciesrdquo

rdquohubotrdquo rdquogt=205rdquo

rdquooauthrdquo rdquordquo

rdquocronrdquordquordquo

這邊會使用到NodeJS的 cron模組(Module)而登入 Plurk需要OAuth標準授權才行所以也需要加載 OAuth模組

注意Hubot在讀取非內建模組時會自動在前面加上 hubot-的前置

83 建立 Robot跟 API

本篇應用基本上程式碼大部分參考 Hubot - Twitter Adapter來製作主要差異只有在使用 OAuth這個模組Twitter有 Streaming可用而 Plurk則得用 Comet方式來達到即時讀取

plurkcoffee程式碼

Robot = require(rdquohubotrdquo)robot()

Adapter = require(rdquohubtordquo)adapter()

EventEmitter = require(rdquoeventsrdquo)EventEmitter

oauth = require(rdquooauthrdquo)

cronJob = require(rdquocronrdquo)CronJob

class Plurk exntends Adapter

class PlurkStreaming exnteds EventEmitter

72 8 製作一個 Hubot的 Plurk Adapter

Nodejs中文電子書

先弄個基本架構主要 Class皆參考 Twitter Adapter命名方式接著再將 Plurk這個 Class增加幾個Method基本上只要有 run send reply就夠了而 run用來做初始化的部分

class Plurk entends Adapter

send (plurk id strings⋯) -gt

reply (plurk id strings⋯) -gt

run -gt

看起來有點東西接著來處理主要的 Plurk API結合部分

class PlurkStreaming extends EventEmitter

consuctor (options) -gt

plurk (callback) -gt

觀察河道

getChannel -gt

取得 Comet 網址

reply (plurk id message) -gt

回噗

acceptFriends -gt

接受好友

get (path callback) -gt

GET 請求

post (path body callback)-gt

POST 請求(其實是裝飾)

request (method path body callback)-gt

主要的 OAuth 請求

comet (server callback)-gt

噗浪的 Comet 傳回是 JavaScript Callback 要另外處理後才會變成 JSON

然後把注意力集中到 constructor上先把建構子弄好

constructor (options) -gt

super()

if optionskey and optionssecret and optionstoken and optionstoken secret

key = optionskey

secret = optionssecret

token = optionstoken

token secret = optionstoken secret

83 建立 Robot跟 API 73

Nodejs中文電子書

建立 OAuth 連接

consumer = new oauthOAuth(

rdquohttpwwwplurkcomOAuthrequest tokenrdquo

rdquohttpwwwplurkcomOAuthaccess tokenrdquo

key

secret

rdquo10rdquo

rdquohttpwwwplurkcomOAuthauthorizerdquo

rdquoHMAC-SHA1rdquo

)

domain = rdquowwwplurkcomrdquo

初始化取得 Comet 網址

do getChannel

else

throw new Error(rdquo 參數不足需要 Key Secret Token Token Secretrdquo)

接著來處理 request這個 method

request (method path body callback) -gt

記錄一下這次的 Request

consolelog(rdquohttpdomainpathrdquo)

Callback 這邊先不丟進去要用另一種方式處理

request = consumerget(rdquohttpdomainpathrdquo token token secret null)

requeston rdquoresponserdquo (res) -gt

reson rdquodatardquo (chunk) -gt

parseResponse(chunk+rsquorsquo callback)

reson rdquoendrdquo (data) -gt

consolelog rdquoEnd Request pathrdquo

reson rdquoerrorrdquo (data) -gt

consolelog rdquoError rdquo + data

requestend()

處理資料

parseResponse = (data callback) -gt

if datalength gt 0

用 TryCatch 避免處理 JSON 出錯導致整個中斷

try

callback null JSONparse(data)

catch err

74 8 製作一個 Hubot的 Plurk Adapter

Nodejs中文電子書

consolelog(rdquoError Parse JSONrdquo + data err)

繼續執行

callback null data ||

大致上就是這樣上面程式的架構已經將整個 Hubot Plurk Adapter完成因為在測試時竟然因為噗浪 Lag而沒讀到完整的 Comet資料然後造成程式異常為了避免這個問題發生因此需要加上為了完美呈現需要再

加上 Comet的處理所以要使用到 EventEmitter的功能

comet (server callback) -gt

在 Callback 裡面會找不到自身所以設定區域變數

self =

記錄一下這次的 Request

consolelog(rdquo[Comet] serverrdquo)

Callback 這邊先不丟進去要用另一種方式處理

request = consumerget(rdquohttpdomainpathrdquo token token secret null)

requeston rdquoresponserdquo (res) -gt

reson rdquodatardquo (chunk) -gt

parseResponse(chunk+rsquorsquo callback)

reson rdquoendrdquo (data) -gt

consolelog rdquoEnd Request pathrdquo

請求結束發出事件通知可以進行下一次請求

selfemit rdquonextPlurkrdquo

reson rdquoerrorrdquo (data) -gt

consolelog rdquoError rdquo + data

requestend()

處理資料

parseResponse = (data callback) -gt

if datalength gt 0

用 trycatch 避免失敗中斷

try

去掉 JavaScript 的 Callback

data = datamatch(CometChannelscriptCallback((+))s)

jsonData = rdquordquo

if data

83 建立 Robot跟 API 75

Nodejs中文電子書

jsonData = JSONparse(data[1])

else

如果沒有任何 Match 嘗試直接 parse

jsonData = JSONparse(data)

catch err

consolelog(rdquo[Comet] Errorrdquo data err)

用 TryCatch 避免處理 JSON 出錯導致整個中斷

try

只傳入 json 的 data 部分

callback null jsonDatadata

catch err

consolelog(rdquo[Comet]Error Parse JSONrdquo + data err)

繼續執行

callback null data ||

後面的 get跟 post就簡單多了

get (path callback) -gt

request(rdquoGETrdquo path null callback)

post (path body callback) -gt

request(rdquoPOSTrdquo path body callback)

接著處理取的 Comet網址的 getChannel

getChannel -gt

self =

get rdquoAPPRealtimegetUserChannelrdquo (error data) -gt

if error

檢查是否有 comet server

if datacomet server

selfchannel = datacomet server

如果沒有 Channel Ready 就嘗試連接會失敗

selfemit(rsquochannel readyrsquo)

那麼先來處理 Plurk Adaper好處理的部份

send (plruk id strings⋯)-gt

跟 Reply 一樣直接交給 reply 做

reply plurk id strings⋯

76 8 製作一個 Hubot的 Plurk Adapter

Nodejs中文電子書

reply (plurk id strings⋯) -gt

stringsforEach (message) =gt

botreply(plruk id message)

接著把 run處理好就可以上線運作摟

run -gt

self =

options =

key processenvHUBOT PLURK KEY

secret processenvHUBOT PLURK SECRET

token processenvHUBOT PLURK TOKEN

token secret processenvHUBOT PLURK TOKEN SECRET

創建剛剛的 API

bot = new PlurkStreaming(options)

依照 Twitter 的 new RobotTextMessage 會沒有反應所以參考 hubot-minecraft 的方式

r = robotconstructor

處理噗浪河道訊息

doPlurk = (data)-gt

檢查是否為回噗

if dataresponse

datacontent raw = dataresponsecontent raw

datauser id = dataresponseuser id

確定有噗浪 ID 跟訊息

if dataplurk id and datacontent raw

selfreceive new rTextMessage(dataplurk id datacontent raw)

取得 Comet Server 完成開始第一次 Comet 連接

boton rdquochannel readyrdquo () -gt

botplurk selfdoPlurk

上一次 Comet 完成繼續 Polling

boton rdquonextPlurkrdquo ()-gt

botplurk selfdoPlurk

定時接受好友邀請

do botacceptFriends

bot = bot

83 建立 Robot跟 API 77

Nodejs中文電子書

終於完成 AdapterHubot專案裡面的 scripts資料夾內是互動部分不需要像 Adapter如此大費周章處理只新增檔案並且設計好對白之後就會回噗了我開發用的機器人在此大家可以去跟他玩玩[httpplurkcomelct9620 bot](httpplurkcomelct9620 bot)

84 原始資料提供

bull [製 作 一 個 Hubot 的 噗 浪 Adapter](http revo-skillfrosttw blog20120318create-a-hubot-plurk-adapter)

78 8 製作一個 Hubot的 Plurk Adapter

9

Nodejs好用工具介紹

91 logio

Real-time log monitoring in your browser

httplogioorg

nodejs (gt=0412 lt=0611)

npm config set unsafe-perm true

npm install -g --prefix=usrlocal logio

logio server start

httplocalhost8998

79

Nodejs中文電子書

80 9 Nodejs好用工具介紹

10

精選文章

101 Express

bull 使用 NodeJS + Express從 GETPOST Request取值 [邀稿中]

bull link2 [邀稿中]

bull link3 [邀稿中]

81

Nodejs中文電子書

82 10 精選文章

11

參考資源

111 Nodejs書籍

bull Programming Nodejs April 2012 (est) Travis Swicegood Pragmatic Bookshelf

bull e Little Book on CoffeeScript January 2012 Alex MacCaw OrsquoReilly Media

bull Node for Front-End Developers January 2012 Garann Means OrsquoReilly Media

bull Building Hypermedia APIs with HTML5 and Node November 2011 MikeAmundsen OrsquoReilly Media

bull Node Web Development August 2011 David Herron PacktPub

bull CoffeeScript Accelerated JavaScript Development July 2011 Trevor BurnhamPragmatic Bookshelf

bull Getting Started with GEO CouchDB and Nodejs July 2011 Mick omp-son OrsquoReilly Media

bull What Is Node July 2011 Brett McLaughlin OrsquoReilly Media

bull Node Up and Running May 2011 Tom Hughes-Croucher Mike WilsonOrsquoReilly Media

83

Nodejs中文電子書

bull Hands-on Nodejs e Nodejs introduction and API reference May 2011 Pe-dro Teixeira LeanPub

bull e Node Beginner Book Manuel Kiessling

bull Mastering Nodejs visionmedia

112 Nodejs影音教學

bull e Node Sessions e Best of OSCON 2011 August 2011 OrsquoReilly Media(Video)

bull Tom Hughes-Croucher on Node March 2011 OrsquoReilly Media (Video)

113 Nodejs教學網站

bull Node Tuts

114 Nodejs課程

bull Introduction To Nodejs Van Nguyen CodeLesson

84 11 參考資源

Page 38: Nodejs Wiki

Nodejs中文電子書

上面解說可以了解到在 nodejs當中所有的路徑都需要經過設定未經過設定的路由會讓瀏覽器無法取得任何資料導致錯誤頁面的發生底下將會解

說如何設定路由同時避免發生錯誤情形先前 nodejs程式需要增加一些修改才能讓使用者透過瀏覽器在不同路徑時有不同的結果根據剛才

的程式做如下的修改

var server

ip = rdquo127001rdquo

port = 1337

http = require(rsquohttprsquo)

url = require(rsquourlrsquo)

path

server = httpcreateServer(function (req res)

path = urlparse(requrl)

reswriteHead(200 rsquoContent-Typersquo rsquotextplainrsquo)

switch (pathpathname)

case rdquoindexrdquo

resend(rsquoI am indexnrsquo)

break

case rdquotestrdquo

resend(rsquothis is test pagenrsquo)

break

default

resend(rsquodefault pagenrsquo)

break

)

serverlisten(port ip)

consolelog(rdquoServer running at httprdquo + ip + rdquordquo + port)

程式做了片段的修改首先載入 url模組另外增加一個 path變數url模組就跟如同他的命名一般專門處理 url字串處理裡面提供了許多方法來解決路徑上的問題因為從瀏覽器發出的要求路徑可能會帶有多種需求

或者 GET參數組合等因此我們需要將路徑單純化取用路徑部分的資料即可例如使用者可能會送出 http1270011337testsend=1如果直接

32 4 Nodejs基礎

Nodejs中文電子書

信任 requrl就會收到結果為testsend=1所以需要透過 url模組的方法將路徑資料過濾

在這邊使用 urlparse的方法裡面帶入網址格式資料會回傳路徑資料為了後需方便使用將回傳的資料設定到 path變數當中在回傳的路徑資料裡面包含資訊如下圖

這邊只需要使用單純的路徑要求直接取用 pathpathname就可以達到我們的目的

最後要做路徑的判別在不同的路徑可以指定不同的輸出在範例中

有三個可能結果第一個從瀏覽器輸入index就會顯示 index結果test 就會呈現出 test頁面最後如果都不符合預期的輸入會直接顯示 default的頁面最後的預防可以讓瀏覽器不會出現非預期結果讓程式的可靠性提昇

底下為測試結果

42 nodejs http路徑建立 33

Nodejs中文電子書

43 nodejs檔案讀取

前面已經介紹如何使用路由(rount)做出不同的回應實際應用只有在瀏覽器只有輸出幾個文字資料總是不夠的在本章節中將介紹如何使

用檔案讀取輸出檔案資料讓使用者在前端瀏覽器也可以讀取到完整的

html css javascript檔案輸出

檔案管理最重要的部分就是 lsquoFile system lthttpnodejsorgdocslatestapifshtmlgtlsquo這個模組此模組可以針對檔案做管理監控讀取等行為裡面有許多預設的方法底下是檔案輸出的基本範例底下會有兩個檔案

第一個是靜態 html檔案

ltDOCTYPE htmlgt

lthtml xmlns=rdquohttpwwww3org1999xhtmlrdquo xmllang=rdquozh-CNrdquo lang=rdquozh-CNrdquogt

ltheadgt

ltmeta http-equiv=rdquoContent-Typerdquo content=rdquotexthtml charset=UTF-7rdquo gt

lttitlegtnodejs index html filelttitlegt

ltheadgt

ltbodygt

34 4 Nodejs基礎

Nodejs中文電子書

lth1gtnodejs index html filelth1gt

ltbodygt

lthtmlgt

另一個為 nodejs程式

var fs = require(rdquofsrdquo)

filename = rdquostaticindexhtmlrdquo

encode = rdquoutf8rdquo

fsreadFile(filename encode function(err file)

consolelog(file)

)

一開始直接載入 le system模組 載入名稱為 fs讀取檔案主要使

用的方法為 readFile裡面以三個參數路徑 ( le path) 編碼方式 (encoding)

回應函式 (callback)路徑必須要設定為靜態 html所在位置才能指定到正確的檔案靜態檔案的編碼方式也必須正確這邊使用靜態檔案的編

碼為 utf8如果編碼設定錯誤nodejs讀取出來檔案結果會使用 byte raw格式輸出如果錯誤編碼格式會導致輸出資料為 byte raw

回應函式中裡面會使用兩個變數error為錯誤資訊如果讀取的檔案不存在或者發生錯誤error數值會是 true如果成功讀取資料 error將會是 falsecontent則是檔案內容資料讀取後將會把資料全數丟到 content這個變數當中

最後程式的輸出結果畫面如下

43 nodejs檔案讀取 35

Nodejs中文電子書

44 nodejs http靜態檔案輸出

前面已經了解如何讀取本地端檔案接下來將配合 http伺服器路由讓每個路由都能夠輸出相對應的靜態 html檔案首先新增加幾個靜態 html檔案

ltDOCTYPE htmlgt

lthtml xmlns=rdquohttpwwww3org1999xhtmlrdquo xmllang=rdquozh-CNrdquo lang=rdquozh-CNrdquogt

ltheadgt

ltmeta http-equiv=rdquoContent-Typerdquo content=rdquotexthtml charset=UTF-7rdquo gt

lttitlegtnodejs index html filelttitlegt

ltheadgt

ltbodygt

lth1gtnodejs index html filelth1gt

ltbodygt

lthtmlgt

ltDOCTYPE htmlgt

lthtml xmlns=rdquohttpwwww3org1999xhtmlrdquo xmllang=rdquozh-CNrdquo lang=rdquozh-CNrdquogt

ltheadgt

ltmeta http-equiv=rdquoContent-Typerdquo content=rdquotexthtml charset=UTF-7rdquo gt

lttitlegtnodejs test html filelttitlegt

ltheadgt

ltbodygt

lth1gtnodejs test html filelth1gt

ltbodygt

lthtmlgt

ltDOCTYPE htmlgt

lthtml xmlns=rdquohttpwwww3org1999xhtmlrdquo xmllang=rdquozh-CNrdquo lang=rdquozh-CNrdquogt

ltheadgt

ltmeta http-equiv=rdquoContent-Typerdquo content=rdquotexthtml charset=UTF-7rdquo gt

lttitlegtnodejs static html filelttitlegt

ltheadgt

ltbodygt

lth1gtnodejs static html filelth1gt

ltbodygt

lthtmlgt

36 4 Nodejs基礎

Nodejs中文電子書

準備一個包含基本路由功能的 http伺服器

var server

ip = rdquo127001rdquo

port = 1337

http = require(rsquohttprsquo)

url = require(rsquourlrsquo)

server = httpcreateServer(function (req res)

var path = urlparse(requrl)

)

serverlisten(port ip)

consolelog(rdquoServer running at httprdquo + ip + rdquordquo + port)

加入 le system 模組使用 readFile 的功能將這一段程式放置於

createServer的回應函式中

fsreadFile(filePath encode function(err file)

)

readFile的回應函式裡面加入頁面輸出讓瀏覽器可以正確讀到檔案在這邊我們設定讀取的檔案為 html 靜態檔案所以 Content-type 設定為texthtml讀取到檔案的內容將會正確輸出成 html靜態檔案

fsreadFile(filePath encode function(err file)

reswriteHead(200 rsquoContent-Typersquo rsquotexthtmlrsquo)

reswrite(file)

resend()

)

到這邊為止基本的程式內容都已經完成剩下一些細節的調整首先

路徑上必須做調整目前的靜態檔案全部都放置於 static資料夾底下設定一個變數來記住資料夾位置

接著將瀏覽器發出要求路徑與資料夾組合讀取正確 html靜態檔案使用者有可能會輸入錯誤路徑所以在讀取檔案的時候要加入錯誤處理

同時回應 404伺服器無法正確回應的 http header格式

加入這些細節的修改一個基本的 http 靜態 html輸出伺服器就完成了完整程式碼如下

44 nodejs http靜態檔案輸出 37

Nodejs中文電子書

var server

ip = rdquo127001rdquo

port = 1337

http = require(rsquohttprsquo)

fs = require(rdquofsrdquo)

folderPath = rdquostaticrdquo

url = require(rsquourlrsquo)

path

filePath

encode = rdquoutf8rdquo

server = httpcreateServer(function (req res)

path = urlparse(requrl)

filePath = folderPath + pathpathname

fsreadFile(filePath encode function(err file)

if (err)

reswriteHead(404 rsquoContent-Typersquo rsquotextplainrsquo)

resend()

return

reswriteHead(200 rsquoContent-Typersquo rsquotextapplicationrsquo)

reswrite(file)

resend()

)

)

serverlisten(port ip)

consolelog(rdquoServer running at httprdquo + ip + rdquordquo + port)

45 nodejs http GET資料擷取

http伺服器中除了路由之外另一個最常使用的方法就是擷取 GET資料本單元將會介紹如何透過基本 http伺服器擷取瀏覽器傳來的要求擷取 GET資料

在 http協定中GET參數都是藉由 URL從瀏覽器發出要求送至伺服器

38 4 Nodejs基礎

Nodejs中文電子書

端基本的傳送網址格式可能如下

http127001testsend=1amptest=2

上面這段網址裡面的 GET參數就是 send而這個變數的數值就為 1如果想要在 http伺服器取得 GET資料需要在瀏覽器給予的要求 (request)做處理

首先需要載入 query string這個模組這個模組主要是用來將字串資料

過濾後轉換成javascript物件

qs = require(rsquoquerystringrsquo)

接著在第一階段利用 url模組過濾瀏覽器發出的 URL資料後將回應的物件裡面的 query這個變數是一個字串值資料過濾後如下

send=1amptest=2

透過 query string使用 parse這個方法將資料轉換成 javascript物件就表示 GET的資料已經被伺服器端正式擷取下來

path = urlparse(requrl)

parameter = qsparse(pathquery)

整個 nodejs http GET參數完整擷取程式碼如下

var server

ip = rdquo127001rdquo

port = 1337

http = require(rsquohttprsquo)

qs = require(rsquoquerystringrsquo)

url = require(rsquourlrsquo)

server = httpcreateServer(function (req res)

var path = urlparse(requrl)

parameter = qsparse(pathquery)

consoledir(parameter)

reswriteHead(200 rsquoContent-Typersquo rsquotextplainrsquo)

reswrite(rsquoBrowser test GET parameternrsquo)

resend()

)

45 nodejs http GET資料擷取 39

Nodejs中文電子書

serverlisten(port ip)

consolelog(rdquoServer running at httprdquo + ip + rdquordquo + port)

程式運作之後由瀏覽器輸入要求網址之後nodejs伺服器端回應資料為

Server running at http1270011337

send rsquo1rsquo test rsquo1rsquo

46 本章結語

前面所解說的部份一大部分主要是處理 http伺服器基本問題雖然在某些部分有牽扯到 http 伺服器基本運作原理主要還是希望可以藉由這些基本範例練習 nodejs練習回應函式與語法串接的特點習慣編寫javascript風格程式當然直接這樣開發 nodejs是非常辛苦的接下來在模組實戰開發的部份將會介紹特定的模組一步一步帶領各位從無到有進行

nodejs應用程式開發

40 4 Nodejs基礎

5

NPM套件管理工具

npm全名為 Node Package Manager是 Nodejs的套件(package)管理工具類似 Perl的 ppm或 PHP的 PEAR等安裝 npm後使用npm install

module name指令即可安裝新套件維護管理套件的工作會更加輕鬆

npm可以讓Nodejs的開發者直接利用擴充線上的套件庫(packagesregistry)加速軟體專案的開發npm提供很友善的搜尋功能可以快速找到安裝需要的套件當這些套件發行新版本時npm也可以協助開發者自動更新這些套件

npm不僅可用於安裝新的套件它也支援搜尋列出已安裝模組及更新的功能

51 安裝 NPM

Nodejs在 063版本開始內建 npm讀者安裝的版本若是此版本或更新的版本就可以略過以下安裝說明

若要檢查 npm是否正確安裝可以使用以下的指令

npm -v

41

Nodejs中文電子書

執行結果說明

若 npm正確安裝執行 npm -v將會看到類似 110-2的版本訊息

若讀者安裝的 Nodejs版本比較舊或是有興趣嘗試自己動手安裝 npm工具則可以參考以下的說明

安裝於Windows系統

Nodejs for Windows 於 062 版開始內建 npm使用 nodejsorg 官方提供的安裝程式不需要進一步的設定就可以立即使用 npm指令對於Windows的開發者來說大幅降低環境設定的問題與門檻

除了使用 Nodejs內建的 npm讀者也可以從 npm官方提供的以下網址

httpnpmjsorgdist

這是由 npm提供的 Fancy Windows Install版本請下載壓縮檔(例如npm-110-3zip)並將壓縮檔內容解壓縮至 Nodejs的安裝路徑(例如CProgram Filesnodejs)

解壓縮後在 Nodejs的安裝路徑下應該有以下的檔案及資料夾

bull npmcmd(檔案)

bull node modules(資料夾)

安裝於 Linux系統

Ubuntu Linux的使用者可以加入 NPM Unoffcial PPA這個 repository即可使用 apt-get完成 npm安裝

42 5 NPM套件管理工具

Nodejs中文電子書

Ubuntu Linux使用 apt-get安裝 npm

sudo apt-get install python-software-properties

sudo add-apt-repository ppagias-kay-leenpm

sudo apt-get update

sudo apt-get install npm

npm官方提供的安裝程式 installsh可以適用於大多數的 Linux系統使用這個安裝程式請先確認

1 系統已安裝 curl工具(請使用 curl --version查看版本訊息)

2 已安裝 Nodejs並且 PATH正確設置

3 Nodejs的版本必須大於 04x

以下為 npm提供的安裝指令

curl httpnpmjsorginstallsh | sh

安裝成功會看到如下訊息

installsh安裝成功的訊息

npm10105 homeUSERNAMElocalnodelibnode_modulesnpm

It worked

安裝於Mac OS X

建議採用與 Nodejs相同的方式進行 npm的安裝例如使用MacPorts安裝 Nodejs就同樣使用MacPorts安裝 npm這樣對日後的維護才會更方便容易

使用 MacPorts安裝 npm是本書比較建議的方式它可以讓 npm的安裝移除及更新工作自動化將會幫助開發者節省寶貴時間

51 安裝 NPM 43

Nodejs中文電子書

安裝MacPorts的提示

在MacPorts網站可以取得 OS X系統版本對應的安裝程式(例如 106或 107)httpwwwmacportsorg安裝過程會詢問系統管理者密碼使用預設的選項完成安裝即可安

裝MacPorts之後在終端機執行 port -v將會看到MacPorts的版本訊息

安裝 npm之前先更新MacPorts的套件清單以確保安裝的 npm是最新版本

sudo port -d selfupdate

接著安裝 npm

sudo port install npm

若讀者的 Nodejs並非使用MacPorts安裝則不建議使用MacPorts安裝npm因為 MacPorts會自動檢查並安裝相依套件而 npm相依 nodejs所以MacPorts也會一併將 nodejs套件安裝造成先前讀者使用其它方式安裝的 nodejs被覆蓋

讀者可以先使用 MacPorts安裝 curl(sudo port install curl)

再參考 Linux的 installsh安裝方式即可使用 npm官方提供的安裝程式

NPM安裝後測試

npm是指令列工具(command-line tool)使用時請先打開系統的文字終端機工具

測試 npm安裝與設定是否正確請輸入指令如下

npm -v

或是

npm --version

如果 npm已經正確安裝設定就會顯示版本訊息

44 5 NPM套件管理工具

Nodejs中文電子書

執行結果(範例)

110-2

52 使用 NPM安裝套件

npm目前擁有超過 6000種套件(packages)可以在 npm registry使用關鍵字搜尋套件

httpsearchnpmjsorg

舉例來說在關鍵字欄位輸入「coffee-script」下方的清單就會自動列出包含 coffee-script關鍵字的套件

接著我們回到終端機模式的操作npm的指令工具本身就可以完成套

件搜尋的任務

例如以下的指令同樣可以找出 coffee-script相關套件

npm search coffee-script

以下是搜尋結果的參考畫面

52 使用 NPM安裝套件 45

Nodejs中文電子書

找到需要的套件後(例如 express)即可使用以下指令安裝

npm install coffee-script

值得注意的一點是使用 npm install會將指定的套件安裝在工

作目錄(Working Directory)的 node modules資料夾下

以Windows為例如果執行 npm install的目錄位於

Cproject1

那麼 npm將會自動建立一個 node modules的子目錄(如果不存在)

Cproject1node modules

並且將下載的套件放置於這個子目錄例如

Cproject1node modulescoffee-script

這個設計讓專案可以個別管理相依的套件並且可以在專案佈署或發

行時將這些套件(位於 node modules)一併打包方便其它專案的使用者不必再重新下載套件

這個 npm install的預設安裝模式為 local(本地)只會變更當前專案的資料夾不會影響系統

另一種安裝模式稱為 global(全域)這種模式會將套件安裝到系統資

料夾也就是 npm安裝路徑的 node modules資料夾例如

CProgram Filesnodejsnode modules

46 5 NPM套件管理工具

Nodejs中文電子書

是否要使用全域安裝可以依照套件是否提供新指令來判斷舉例來說express 套件提供 express 這個指令而 coffee-script 則提供 coffee

指令

在 local 安 裝 模 式 中這 些 指 令 的 程 式 檔 案會 被 安 裝 到node modules的 bin這個隱藏資料夾下除非將bin的路徑加入 PATH環境變數否則要執行這些指令將會相當不便

為了方便指令的執行我們可以在 npm install 加上 -g 或 --

global參數啟用 global安裝模式例如

npm install -g coffee-script

npm install -g express

使用 global安裝模式需要注意執行權限的問題若權限不足可能會出現類似以下的錯誤訊息

npm ERR Error EACCES permission denied rsquorsquo

npm ERR

npm ERR Please try running this command again as rootAdministrator

要獲得足夠得執行權限請參考以下說明

bull Windows 7 或 2008 以上在「命令提示字元」的捷徑按右鍵選擇「以系統管理員身分執行」執行 npm指令時就會具有 Administrator身分

bull Mac OS X或 Linux系統可以使用 sudo指令例如

sudo npm install -g express

bull Linux系統可以使用 root權限登入或是以「sudo su -」切換成 root身分(使用 root權限操作系統相當危險因此並不建議使用這種方式)

若加上 -g參數使用 npm install -g coffee-script完成安裝

後就可以在終端機執行 coffee指令例如

coffee -v

52 使用 NPM安裝套件 47

Nodejs中文電子書

執行結果(範例)

CoffeeScript version 120

53 套件的更新及維護

除了前一節說明的 search 及 install 用法npm 還提供其他許多指令(commands)

使用 npm help可以查詢可用的指令

npm help

執行結果(部分)

where ltcommandgt is one of

adduser apihelp author bin bugs c cache completion

config deprecate docs edit explore faq find get

help help-search home i info init install la link

list ll ln login ls outdated owner pack prefix

prune publish r rb rebuild remove restart rm root

run-script s se search set show star start stop

submodule tag test un uninstall unlink unpublish

unstar up update version view whoami

使用 npm help command可以查詢指令的詳細用法例如

npm help list

接下來本節要介紹開發過程常用的 npm指令

使用 list可以列出已安裝套件

npm list

48 5 NPM套件管理工具

Nodejs中文電子書

執行結果(範例)

-- coffee-script120

- express256

- connect185

| -- formidable108

-- mime124

-- mkdirp007

-- qs041

檢視某個套件的詳細資訊例如

npm show express

升級所有套件(如果該套件已發佈更新版本)

npm update

升級指定的套件

npm update express

移除指定的套件

npm uninstall express

54 使用 packagejson

對於正式的 Nodejs專案可以建立一個命名為 packagejson的設

定檔(純文字格式)檔案內容參考範例如下

54 使用 packagejson 49

Nodejs中文電子書

packagejson(範例)

rdquonamerdquo rdquoapplication-namerdquo

rdquoversionrdquo rdquo001rdquo

rdquoprivaterdquo true

rdquodependenciesrdquo

rdquoexpressrdquo rdquo255rdquo

rdquocoffee-scriptrdquo rdquolatestrdquo

rdquomongooserdquo rdquogt= 253rdquo

其中 name與 version依照專案的需求設置

需要注意的是 dependencies的設定它用於指定專案相依的套件名

稱及版本

bull rdquoexpressrdquo rdquo255rdquo

代表此專案相依版本 255的 express套件

bull rdquocoffee-scriptrdquo rdquolatestrdquo

使用最新版的 coffee-script套件(每次更新都會檢查新版)

bull rdquomongooserdquo rdquogt= 253rdquo

使用版本大於 253的 mongoose套件

假設某個套件的新版可能造成專案無法正常運作就必須指定套件的

版本避免專案的程式碼來不及更新以相容新版套件通常在開發初期的

專案需要盡可能維持新套件的相容性(以取得套件的更新或修正)可以

用「gt=」設定最低相容的版本或是使用「latest」設定永遠保持最新

套件

50 5 NPM套件管理工具

6

Express介紹

在前面的 nodejs基礎當中介紹許多許多開設 http的使用方法及介紹以及許多基本的 nodejs基本應用

接下來要介紹一個套件稱為 express [Express](httpexpressjscom)這個套件主要幫忙解決許多 nodejs http server所需要的基本服務讓開發 httpservice變得更為容易不需要像之前需要透過層層模組(module)才有辦法開始編寫自己的程式

這個套件是由 TJ Holowaychuk 製作而成的套件裡面包含基本的路由處理 (route)http資料處理(GETPOSTPUT)另外還與樣板套件(jshtml template engine)搭配同時也可以處理許多複雜化的問題

61 Express安裝

安裝方式十分簡單只要透過之前介紹的 NPM就可以使用簡單的指令安裝指令如下

這邊建議需要將此套件安裝成為全域模組方便日後使用

51

Nodejs中文電子書

62 Express基本操作

express的使用也十分簡單先來建立一個基本的 hello world

var app = require(rsquoexpressrsquo)createServer()

port = 1337

applisten(port)

appget(rsquorsquo function(req res)

ressend(rsquohello worldrsquo)

)

consolelog(rsquostart express servernrsquo)

可以從上面的程式碼發現基本操作與 nodejs http的建立方式沒有太大差異主要差在當我們設定路由時可以直接透過 appget方式設定回應與接受方式

63 Express路由處理

Express對於 http服務上有許多包裝讓開發者使用及設定上更為方便例如有幾個路由設定那我們就統一藉由 appget來處理

Create http server

appget(rsquorsquo function(req res)

ressend(rsquohello worldrsquo)

)

appget(rsquotestrsquo function(req res)

ressend(rsquotest renderrsquo)

)

appget(rsquouserrsquo function(req res)

ressend(rsquouser pagersquo)

)

52 6 Express介紹

Nodejs中文電子書

如上面的程式碼所表示appget可以帶入兩個參數第一個是路徑名稱設定第二個為回應函式 (call back function)回應函式裡面就如同之前的 createServer方法裡面包含 requestresponse兩個物件可供使用使用者就可以透過瀏覽器輸入不同的 url切換到不同的頁面顯示不同的結果

路由設定上也有基本的配對方式讓使用者從瀏覽器輸入的網址可以

是一個變數只要符合型態就可以有對應的頁面產出例如

Create http server

appget(rsquouseridrsquo function(req res)

ressend(rsquouser rsquo + reqparamsid)

)

appget(rsquonumberrsquo function(req res)

ressend(rsquonumber rsquo + reqparamsnumber)

)

裡 面 使 用 到number 從 網 址 輸 入 之 後 就 可 以 直 接 使 用reqparamsnumber 取得所輸入的資料變成 url 參數使用當然前面也是可以加上路徑的設定userid在瀏覽器上路徑必須符合userxxx透

過 reqparamsid就可以取到 xxx這個字串值

另外express參數處理也提供了路由參數配對處理也可以透過正規表示法作為參數設定

var app = require(rsquoexpressrsquo)createServer()

port = 1337

applisten(port)

appget(^ip((d23)((d23))((d23))((d23))) function(req res)

ressend(reqparams)

)

上面程式碼可以發現後面路由設定的型態是正規表示法裡面設定

格式為ip之後必須要加上 ip型態才會符合資料格式同時取得 ip資料已經由正規表示法將資料做分群因此可以取得 ip的四個數字

此程式執行之後可以透過瀏覽器測試輸入網址為 localhost3000ip25525510010可以從頁面獲得資料

63 Express路由處理 53

Nodejs中文電子書

此章節全部範例程式碼如下

overview

author Caesar Chi

blog clonnblogspotcom

version 20120226

create server

var app = require(rsquoexpressrsquo)createServer()

port = 1337

applisten(port)

normal style

appget(rsquorsquo function(req res)

ressend(rsquohello worldrsquo)

)

appget(rsquotestrsquo function(req res)

ressend(rsquotest renderrsquo)

)

parameter style

appget(rsquouseridrsquo function(req res)

ressend(rsquouser rsquo + reqparamsid)

)

appget(rsquonumberrsquo function(req res)

ressend(rsquonumber rsquo + reqparamsnumber)

)

REGX style

appget(^ip((d23)((d23))((d23))) function(req res)

ressend(reqparams)

)

appget(rsquorsquo function(req res)

ressend(rsquoPage not foundrsquo 404)

54 6 Express介紹

Nodejs中文電子書

)

consolelog(rsquostart express servernrsquo)

64 Express middleware

Express 裡面有一個十分好用的應用概念稱為 middleware可以透過middleware做出複雜的效果同時上面也有介紹 next方法參數傳遞就是靠 middleware的概念來傳遞參數讓開發者可以明確的控制程式邏輯

create http server

appuse(expressbodyParser())

appuse(expressmethodOverride())

appuse(expresssession())

上面都是一種 middleware的使用方式透過 appuse方式裡面載入函式執行方法回應函式會包含三個基本參數responserequestnext其中next表示下一個 middleware執行函式同時會自動將預設三個參數繼續帶往下個函式執行底下有個實驗

上面的片段程式執行後開啟瀏覽器連結上 localhost1337會發現伺服器回應結果順序如下

first middle ware

second middle ware

execute middle ware

end middleware function

從上面的結果可以得知剛才設定的 middleware都生效了在 appuse設定的 middleware是所有 url皆會執行方法如果有指定特定方法就可以使用 appget的 middleware設定在 appget函式的第二個參數就可以帶入函式或者是匿名函式只要函式裡面最後會接受 request response next這三個參數同時也有正確指定 next函式的執行時機最後都會執行到最後一個方法當然開發者也可以評估程式邏輯要執行到哪一個階段讓邏輯

可以更為分明

64 Express middleware 55

Nodejs中文電子書

65 Express路由應用

在實際開發上可能會遇到需要使用參數等方式混和變數一起使用

express裡面提供了一個很棒的處理方法 appall這個方式可以先採用基本路由配對再將設定為每個不同的處理方式開發者可以透過這個方式簡

化自己的程式邏輯

overview

author

version 20120226

create server

var app = require(rsquoexpressrsquo)createServer()

port = 1337

users = [

name rsquoClonnrsquo

name rsquoChirsquo

]

applisten(port)

appall(rsquouseridoprsquo function(req res next)

requser = users[reqparamsid]

if (requser)

next()

else

next(new Error(rsquocannot find user rsquo + reqparamsid))

)

appget(rsquouseridrsquo function(req res)

ressend(rsquoviewing rsquo + requsername)

)

appget(rsquouserideditrsquo function(req res)

ressend(rsquoediting rsquo + requsername)

)

56 6 Express介紹

Nodejs中文電子書

appget(rsquouseriddeletersquo function(req res)

ressend(rsquodeleting rsquo + requsername)

)

appget(rsquorsquo function(req res)

ressend(rsquoPage not foundrsquo 404)

)

consolelog(rsquostart express servernrsquo)

內部宣告一組預設的使用者分別給予名稱設定藉由 appall這個方法可以先將路由雛形建立再接下來設定 appget的路徑格式只要符合格式就會分配進入對應的方法中像上面的程式當中如果使用者輸入路徑為user0除了執行 appall程式之後執行 next方法就會對應到路徑設定為userid的這個方法當中如果使用者輸入路徑為user0edit就會執行到useridedit的對應方法

66 Express GET應用範例

我們準備一個使用 GET方法傳送資料的表單

ltDOCTYPE htmlgt

lthtmlgt

ltheadgt

ltmeta http-equiv=rdquoContent-Typerdquo content=rdquotexthtml charset=utf8rdquogt

lttitlegtNodejs 菜鳥筆記 (1)lttitlegt

ltlink rel=rdquostylesheetrdquo href=rdquocssstylecssrdquo type=rdquotextcssrdquo media=rdquoallrdquo gt

ltheadgt

ltbodygt

lth1gtNodejs 菜鳥筆記-註冊lth1gt

ltform id=rdquosignuprdquo method=rdquoGETrdquo action=rdquohttplocalhost3000Signuprdquogt

ltlabelgt 使用者名稱ltlabelgtltinput type=rdquotextrdquo id=rdquousernamerdquo name=rdquousernamerdquo gtltbrgt

ltlabelgt 電子郵件ltlabelgtltinput type=rdquotextrdquo id=rdquoemailrdquo name=rdquoemailrdquo gtltbrgt

ltinput type=rdquosubmitrdquo value=rdquo 註冊我的帳號rdquo gtltbrgt

ltformgt

ltbodygt

lthtmlgt

66 Express GET應用範例 57

Nodejs中文電子書

這個表單沒有什麼特別的地方我們只需要看第 9 行form 使用的method是 GET然後 action是rdquohttplocalhost3000Signupldquo等一下我們要來撰寫Signup這個 URL Path的處理程式

處理 Signup行為

我們知道所謂的 GET方法會透過 URL來把表單的值給帶過去以上面的表單來說到時候 URL會以這樣的形式傳遞

httplocalhost3000Signupusername=xxxampemail=xxx

所以要能處理這樣的資料必須有以下功能

bull 解析 URL

bull 辨別動作是 Signup

bull 解析出 username和 email

一旦能取得 username和 email的值程式就能加以應用了

處理 Signup的程式碼雛形

load module

var url = require(rsquourlrsquo)

urlData = urlparse(requrltrue)

action = urlDatapathname

reswriteHead(200 rdquoContent-Typerdquordquotexthtml charset=utf-8rdquo)

if (action === rdquoSignuprdquo)

user = urlDataquery

resend(rdquolth1gtrdquo + userusername + rdquo 歡迎您的加入 lth1gtltpgt 我們已經將會員啟用信寄至rdquo + useremail + rdquoltpgtrdquo)

首先需要加載 url module它是用來協助我們解析 URL的模組接著使用 urlparse方法第一個傳入 url字串變數也就是 requrl另外第二個參數的用意是設為 ture則引進 querystring模組來協助處理預設是 false它影響到的是 urlDataquery設為 true會傳回物件不然就只是一般的字串urlparse會將字串內容整理成一個物件我們把它指定給 urlData

action變數作為記錄 pathname這是我們稍後要來判斷目前網頁的動作是什麼接著先將 html表頭資訊 (Header)準備好再來判斷路徑邏輯如

58 6 Express介紹

Nodejs中文電子書

果是 Signup這個動作就把 urlDataquery裡的資料指定給 user然後輸出userusername和 useremail把使用者從表單註冊的資料顯示於頁面中

最後進行程式測試啟動 nodejs主程式之後開啟瀏覽器就會看到表單填寫完畢按下送出就可以看到結果了

完整 nodejs程式碼如下

var http = require(rsquohttprsquo)

url = require(rsquourlrsquo)

fs = require(rdquofsrdquo)

server

server = httpcreateServer(function (reqres)

var urlData

encode = rdquoutf8rdquo

filePath = rdquoviewexpress_get_example_formhtmlrdquo

action

urlData = urlparse(requrltrue)

action = urlDatapathname

reswriteHead(200 rdquoContent-Typerdquordquotexthtml charset=utf-8rdquo)

if (action === rdquoSignuprdquo)

user = urlDataquery

resend(rdquolth1gtrdquo + userusername + rdquo 歡迎您的加入 lth1gtltpgt 我們已經將會員啟用信寄至rdquo + useremail + rdquoltpgtrdquo)

else

fsreadFile(filePath encode function(err file)

reswrite(file)

resend()

)

)

serverlisten(3000)

consolelog(rsquoServer 跑起來了現在時間是rsquo + new Date())

66 Express GET應用範例 59

Nodejs中文電子書

67 Express POST應用範例

一開始準備基本的 html表單傳送內容以 POST方式form的 action屬性設定為 POST其餘 html內容與前一個範例應用相同

ltDOCTYPE htmlgt

lthtmlgt

ltheadgt

ltmeta http-equiv=rdquoContent-Typerdquo content=rdquotexthtml charset=utf8rdquogt

lttitlegtNodejs 菜鳥筆記 (1)lttitlegt

ltlink rel=rdquostylesheetrdquo href=rdquocssstylecssrdquo type=rdquotextcssrdquo media=rdquoallrdquo gt

ltheadgt

ltbodygt

lth1gtNodejs 菜鳥筆記-註冊lth1gt

ltform id=rdquosignuprdquo method=rdquoPOSTrdquo action=rdquohttplocalhost3000Signuprdquogt

ltlabelgt 使用者名稱ltlabelgtltinput type=rdquotextrdquo id=rdquousernamerdquo name=rdquousernamerdquo gtltbrgt

ltlabelgt 電子郵件ltlabelgtltinput type=rdquotextrdquo id=rdquoemailrdquo name=rdquoemailrdquo gtltbrgt

ltinput type=rdquosubmitrdquo value=rdquo 註冊我的帳號rdquo gtltbrgt

ltformgt

ltbodygt

lthtmlgt

nodejs的程式處理邏輯與前面 GET範例類似部分程式碼如下

qs = require(rsquoquerystringrsquo)

if (action === rdquoSignuprdquo)

formData = rsquorsquo

reqon(rdquodatardquo function (data)

formData += data

)

reqon(rdquoendrdquo function ()

user = qsparse(formData)

resend(rdquolth1gtrdquo + userusername + rdquo 歡迎您的加入 lth1gtltpgt 我們已經將會員啟用信寄至rdquo + useremail + rdquoltpgtrdquo)

)

60 6 Express介紹

Nodejs中文電子書

主要加入了rsquoquerystringrsquo這個moduel方便我們等一下解析由表單 POST回來的資料另外加入一個 formData的變數用來搜集待等一下表單回傳的資料前面的 GET範例我們只從 req拿出 url的資料這次要在利用req身上的事件處理

JavaScript 在訂閱事件時使用 addEventListener而 nodejs 使用的則是on這邊加上了監聽 data的事件會在瀏覽器傳送資料到Web Server時被執行參數是它所接收到的資料型態是字串

接著再增加 end的事件當瀏覽器的請求事件結束時它就會動作

由於瀏覽器使用 POST在上傳資料時會將資料一塊塊地上傳因為我們在監聽 data事件時透過 formData變數將它累加起來lt不過由於我們

上傳的資料很少一次就結束不過如果日後需要傳的是資料比較大的檔

案這個累加動作就很重要

當資料傳完就進到 end 事件中會用到 qsparse 來解析 formDataformData的內容是字串內容是

username=wordsmithampemail=wordsmith40somewhere

而 qsparse可以幫我們把這個 querystring轉成物件的格式也就是

username=wordsmithampemail=wordsmith40somewhere

一旦轉成物件並指定給 user之後其他的事情就和 GET方法時操作的一樣寫 response的表頭將內容回傳並將 userusername和 useremail代入到內容中

修改完成後接著執行 nodejs程式啟動 web server開啟瀏覽器進入表單測試看看POST的方式能否順利運作

完整程式碼如下

var http = require(rsquohttprsquo)

url = require(rsquourlrsquo)

fs = require(rdquofsrdquo)

qs = require(rsquoquerystringrsquo)

server

server = httpcreateServer(function (reqres)

var urlData

67 Express POST應用範例 61

Nodejs中文電子書

encode = rdquoutf8rdquo

filePath = rdquoviewexpress_post_example_formhtmlrdquo

formData

action

urlData = urlparse(requrltrue)

action = urlDatapathname

reswriteHead(200 rdquoContent-Typerdquordquotexthtml charset=utf-8rdquo)

if (action === rdquoSignuprdquo)

formData = rsquorsquo

reqon(rdquodatardquo function (data)

formData += data

)

reqon(rdquoendrdquo function ()

user = qsparse(formData)

resend(rdquolth1gtrdquo + userusername + rdquo 歡迎您的加入 lth1gtltpgt 我們已經將會員啟用信寄至rdquo + useremail + rdquoltpgtrdquo)

)

else

fsreadFile(filePath encode function(err file)

reswrite(file)

resend()

)

)

serverlisten(3000)

consolelog(rsquoServer 跑起來了現在時間是rsquo + new Date())

62 6 Express介紹

Nodejs中文電子書

68 Express AJAX應用範例

在 Nodejs要使用 Ajax傳送資料並且與之互動在接受資料的部份沒有太大的差別client端不是用 GET就是用 POST來傳資料重點在處理完後用 JSON格式回傳當然 Ajax不見得只傳 JSON格式有時是回傳一段 HTML碼不過後者對伺服器來說基本上就和前兩篇沒有差別了所以我們還是以回傳 JSON做為這一回的主題

這一回其實大多數的工作都會落在前端 Ajax上面前端要負責發送與接收資料並在接收資料後撤掉原先發送資料的表單並將取得的資料

改成 HTML格式之後放上頁面

首先先準備 HTML靜態頁面資料

ltDOCTYPE htmlgt

lthtmlgt

ltheadgt

lttitlegtNodejs 菜鳥筆記 (3)lttitlegt

ltlink rel=rdquostylesheetrdquo href=rdquocssstylecssrdquo type=rdquotextcssrdquo media=rdquoallrdquo gt

ltscript src=rdquohttpsajaxgoogleapiscomajaxlibsjquery171jqueryminjsrdquogtltscriptgt

ltheadgt

ltbodygt

lth1gtNodejs 菜鳥筆記-註冊 (用 Ajax)lth1gt

ltform id=rdquosignuprdquo action=rdquoSignuprdquo method=rdquoPOSTrdquogt

ltlabelgt 使用者名稱ltlabelgtltinput type=rdquotextrdquo id=rdquousernamerdquo name=rdquousernamerdquo gtltbrgt

ltlabelgt 電子郵件ltlabelgtltinput type=rdquotextrdquo id=rdquoemailrdquo name=rdquoemailrdquo gtltbrgt

ltinput type=rdquosubmitrdquo value=rdquo 註冊我的帳號rdquo gtltbrgt

ltformgt

ltdiv id=rdquoresultrdquogt

ltdivgt

ltscript type=rdquotextjavascriptrdquogt

$(function()$(rsquosignup input[type=rdquosubmitrdquo]rsquo)click(function(e)

var user =

userusername = $(rdquousernamerdquo)val()useremail = $(rdquoemailrdquo)val()

$post(rdquoSignuprdquouserfunction(data)greet(data)

)

68 Express AJAX應用範例 63

Nodejs中文電子書

epreventDefault()

)

var greet = function(msg)

var resultNode = $(rsquoresultrsquo)greeting = $(rsquolth2gtHirsquo+msgusername+rsquo 你的會員 id 是rsquo+ msgid +rsquo 我們會將會員啟動信寄至rsquo+msgemail+rsquolth2gtrsquo)

$(rsquosignuprsquo)hide()resultNodehtml(rdquordquo)

resultNodeappend(greeting)

)

ltscriptgt

ltbodygt

lthtmlgt

HTML頁面上準備了一個表單用來傳送註冊資料接著直接引用了Google CDN來載入 jQuery用來幫我們處理 Ajax的工作這次要傳送和接收的工作很大的變動都在 HTML頁面上的 JavaScript當中我們要做的事有 (相關 jQuery處理這邊不多做贅述指提起主要功能解說)

bull 用 jQUery取得 submit按鈕綁定它的 click動作

bull 取得表單 username和 email的值存放在 user這個物件中

bull 用 jQuery的 $post方法將 user的資料傳到 Server

bull 一旦成功取得資料後透過 greet這個 function組成回報給 user的訊息

bull 清空原本給使用者填資料的表單

bull 將 Server回傳的 usernameemail和 id這 3個資料組成回應的訊息

bull 將訊息放到原本表單的位置

經過以上的處理後一個 Ajax的表單的基本功能已經完成

接著進行 nodejs主要程式的編輯部分程式碼如下

var fs = require(rdquofsrdquo)

64 6 Express介紹

Nodejs中文電子書

qs = require(rsquoquerystringrsquo)

if (action === rdquoSignuprdquo)

formData = rsquorsquo

reqon(rdquodatardquo function (data)

formData += data

)

reqon(rdquoendrdquo function ()

var msg

user = qsparse(formData)

userid = rdquo123456rdquo

msg = JSONstringify(user)

reswriteHead(200 rdquoContent-Typerdquordquoapplicationjson charset=utf-8rdquordquoContent-Lengthrdquomsglength)

resend(msg)

)

這裡的程式和前面 POST範例基本上大同小異差別在

bull 幫 user的資料加上 id隨意存放一些文字進去讓 Server回傳的資料多於 Client端傳上來的不然會覺得 Server都沒做事

bull 增加了 msg 這個變數存放將 user 物件 JSON 文字化的結果JSONstringify這個轉換函式是 V8引擎所提供的如果你好奇的話

bull 大重點來了我們要告訴 Client端這次回傳的資料格式是 JSON所在 Content-type和 Content-Length要提供給 Client

Server很輕鬆就完成任務了最後進行程式測試啟動 nodejs主程式之後開啟瀏覽器就會看到表單填寫完畢按下送出就可以看到結果了

最後 nodejs本篇範例程式碼如下

var http = require(rsquohttprsquo)

url = require(rsquourlrsquo)

fs = require(rdquofsrdquo)

qs = require(rsquoquerystringrsquo)

server

68 Express AJAX應用範例 65

Nodejs中文電子書

server = httpcreateServer(function (reqres)

var urlData

encode = rdquoutf8rdquo

filePath = rdquoviewexpress_ajax_example_formhtmlrdquo

formData

action

urlData = urlparse(requrltrue)

action = urlDatapathname

if (action === rdquoSignuprdquo)

formData = rsquorsquo

reqon(rdquodatardquo function (data)

formData += data

)

reqon(rdquoendrdquo function ()

var msg

user = qsparse(formData)

userid = rdquo123456rdquo

msg = JSONstringify(user)

reswriteHead(200 rdquoContent-TyperdquordquoapplicationjsonrdquordquoContent-Lengthrdquomsglength)

resend(msg)

)

else

fsreadFile(filePath encode function(err file)

reswriteHead(200 rdquoContent-Typerdquordquotexthtml charset=utf-8rdquo)

reswrite(file)

resend()

)

)

serverlisten(3000)

66 6 Express介紹

Nodejs中文電子書

consolelog(rsquoServer 跑起來了現在時間是rsquo + new Date())

69 原始資料提供

bull [NodeJS初學者筆記 (1)-用 GET傳送資料] (httpithelpithomecomtwquestion10087402)

bull [NodeJS初學者筆記 (2)-用 POST傳送資料] (httpithelpithomecomtwquestion10087489)

bull [NodeJS 初學者筆記 (3)-用 Ajax 傳送資料] (httpithelpithomecomtwquestion10087627)

69 原始資料提供 67

Nodejs中文電子書

68 6 Express介紹

7

CoffeeScript

bull Spine

bull Hem

bull Spineapp

Letrsquos Have a Cup of CoffeeScript

bull es5-shim ECMAScript 5 compatibility shims for legacy JavaScript engines

ESnext

node-iform - A middleware for node-validator httpsgithubcomguileennode-iform

69

Nodejs中文電子書

70 7 CoffeeScript

8

製作一個 Hubot的 Plurk Adapter

81 應用事項提醒

此應用範例以CoffeeScript編寫主要程式架構採用Github釋放的Hubot專案以下有幾個主要注意事項請讀者注意

bull Hubot 一開始的架構除了內建的兩個 Adapter 之外其餘都要以Nodejs的Module方式才能運作

bull Hubot的 binhubot裡面寫著 npm install所以不管你怎麼改原始碼也不能改變第一點的狀況

其實上述都在討論同一件事情NodeJS的模組而且模組不能設定相對路徑之類的來安裝一定要包裝成 npm相符和格式才有辦法正確執行

82 建立 Adapter

首先需要準備兩個檔案

bull packagejson

bull plurkcoffee

71

Nodejs中文電子書

有這兩個就足以變成 NodeJS的模組了在開始 Coding之前先來設定一下 packagejson相依性

rdquonamerdquo rdquohubot-plurkrdquo

rdquoversionrdquo rdquo011rdquo

rdquomainrdquo rdquoplurkrdquo

rdquodependenciesrdquo

rdquohubotrdquo rdquogt=205rdquo

rdquooauthrdquo rdquordquo

rdquocronrdquordquordquo

這邊會使用到NodeJS的 cron模組(Module)而登入 Plurk需要OAuth標準授權才行所以也需要加載 OAuth模組

注意Hubot在讀取非內建模組時會自動在前面加上 hubot-的前置

83 建立 Robot跟 API

本篇應用基本上程式碼大部分參考 Hubot - Twitter Adapter來製作主要差異只有在使用 OAuth這個模組Twitter有 Streaming可用而 Plurk則得用 Comet方式來達到即時讀取

plurkcoffee程式碼

Robot = require(rdquohubotrdquo)robot()

Adapter = require(rdquohubtordquo)adapter()

EventEmitter = require(rdquoeventsrdquo)EventEmitter

oauth = require(rdquooauthrdquo)

cronJob = require(rdquocronrdquo)CronJob

class Plurk exntends Adapter

class PlurkStreaming exnteds EventEmitter

72 8 製作一個 Hubot的 Plurk Adapter

Nodejs中文電子書

先弄個基本架構主要 Class皆參考 Twitter Adapter命名方式接著再將 Plurk這個 Class增加幾個Method基本上只要有 run send reply就夠了而 run用來做初始化的部分

class Plurk entends Adapter

send (plurk id strings⋯) -gt

reply (plurk id strings⋯) -gt

run -gt

看起來有點東西接著來處理主要的 Plurk API結合部分

class PlurkStreaming extends EventEmitter

consuctor (options) -gt

plurk (callback) -gt

觀察河道

getChannel -gt

取得 Comet 網址

reply (plurk id message) -gt

回噗

acceptFriends -gt

接受好友

get (path callback) -gt

GET 請求

post (path body callback)-gt

POST 請求(其實是裝飾)

request (method path body callback)-gt

主要的 OAuth 請求

comet (server callback)-gt

噗浪的 Comet 傳回是 JavaScript Callback 要另外處理後才會變成 JSON

然後把注意力集中到 constructor上先把建構子弄好

constructor (options) -gt

super()

if optionskey and optionssecret and optionstoken and optionstoken secret

key = optionskey

secret = optionssecret

token = optionstoken

token secret = optionstoken secret

83 建立 Robot跟 API 73

Nodejs中文電子書

建立 OAuth 連接

consumer = new oauthOAuth(

rdquohttpwwwplurkcomOAuthrequest tokenrdquo

rdquohttpwwwplurkcomOAuthaccess tokenrdquo

key

secret

rdquo10rdquo

rdquohttpwwwplurkcomOAuthauthorizerdquo

rdquoHMAC-SHA1rdquo

)

domain = rdquowwwplurkcomrdquo

初始化取得 Comet 網址

do getChannel

else

throw new Error(rdquo 參數不足需要 Key Secret Token Token Secretrdquo)

接著來處理 request這個 method

request (method path body callback) -gt

記錄一下這次的 Request

consolelog(rdquohttpdomainpathrdquo)

Callback 這邊先不丟進去要用另一種方式處理

request = consumerget(rdquohttpdomainpathrdquo token token secret null)

requeston rdquoresponserdquo (res) -gt

reson rdquodatardquo (chunk) -gt

parseResponse(chunk+rsquorsquo callback)

reson rdquoendrdquo (data) -gt

consolelog rdquoEnd Request pathrdquo

reson rdquoerrorrdquo (data) -gt

consolelog rdquoError rdquo + data

requestend()

處理資料

parseResponse = (data callback) -gt

if datalength gt 0

用 TryCatch 避免處理 JSON 出錯導致整個中斷

try

callback null JSONparse(data)

catch err

74 8 製作一個 Hubot的 Plurk Adapter

Nodejs中文電子書

consolelog(rdquoError Parse JSONrdquo + data err)

繼續執行

callback null data ||

大致上就是這樣上面程式的架構已經將整個 Hubot Plurk Adapter完成因為在測試時竟然因為噗浪 Lag而沒讀到完整的 Comet資料然後造成程式異常為了避免這個問題發生因此需要加上為了完美呈現需要再

加上 Comet的處理所以要使用到 EventEmitter的功能

comet (server callback) -gt

在 Callback 裡面會找不到自身所以設定區域變數

self =

記錄一下這次的 Request

consolelog(rdquo[Comet] serverrdquo)

Callback 這邊先不丟進去要用另一種方式處理

request = consumerget(rdquohttpdomainpathrdquo token token secret null)

requeston rdquoresponserdquo (res) -gt

reson rdquodatardquo (chunk) -gt

parseResponse(chunk+rsquorsquo callback)

reson rdquoendrdquo (data) -gt

consolelog rdquoEnd Request pathrdquo

請求結束發出事件通知可以進行下一次請求

selfemit rdquonextPlurkrdquo

reson rdquoerrorrdquo (data) -gt

consolelog rdquoError rdquo + data

requestend()

處理資料

parseResponse = (data callback) -gt

if datalength gt 0

用 trycatch 避免失敗中斷

try

去掉 JavaScript 的 Callback

data = datamatch(CometChannelscriptCallback((+))s)

jsonData = rdquordquo

if data

83 建立 Robot跟 API 75

Nodejs中文電子書

jsonData = JSONparse(data[1])

else

如果沒有任何 Match 嘗試直接 parse

jsonData = JSONparse(data)

catch err

consolelog(rdquo[Comet] Errorrdquo data err)

用 TryCatch 避免處理 JSON 出錯導致整個中斷

try

只傳入 json 的 data 部分

callback null jsonDatadata

catch err

consolelog(rdquo[Comet]Error Parse JSONrdquo + data err)

繼續執行

callback null data ||

後面的 get跟 post就簡單多了

get (path callback) -gt

request(rdquoGETrdquo path null callback)

post (path body callback) -gt

request(rdquoPOSTrdquo path body callback)

接著處理取的 Comet網址的 getChannel

getChannel -gt

self =

get rdquoAPPRealtimegetUserChannelrdquo (error data) -gt

if error

檢查是否有 comet server

if datacomet server

selfchannel = datacomet server

如果沒有 Channel Ready 就嘗試連接會失敗

selfemit(rsquochannel readyrsquo)

那麼先來處理 Plurk Adaper好處理的部份

send (plruk id strings⋯)-gt

跟 Reply 一樣直接交給 reply 做

reply plurk id strings⋯

76 8 製作一個 Hubot的 Plurk Adapter

Nodejs中文電子書

reply (plurk id strings⋯) -gt

stringsforEach (message) =gt

botreply(plruk id message)

接著把 run處理好就可以上線運作摟

run -gt

self =

options =

key processenvHUBOT PLURK KEY

secret processenvHUBOT PLURK SECRET

token processenvHUBOT PLURK TOKEN

token secret processenvHUBOT PLURK TOKEN SECRET

創建剛剛的 API

bot = new PlurkStreaming(options)

依照 Twitter 的 new RobotTextMessage 會沒有反應所以參考 hubot-minecraft 的方式

r = robotconstructor

處理噗浪河道訊息

doPlurk = (data)-gt

檢查是否為回噗

if dataresponse

datacontent raw = dataresponsecontent raw

datauser id = dataresponseuser id

確定有噗浪 ID 跟訊息

if dataplurk id and datacontent raw

selfreceive new rTextMessage(dataplurk id datacontent raw)

取得 Comet Server 完成開始第一次 Comet 連接

boton rdquochannel readyrdquo () -gt

botplurk selfdoPlurk

上一次 Comet 完成繼續 Polling

boton rdquonextPlurkrdquo ()-gt

botplurk selfdoPlurk

定時接受好友邀請

do botacceptFriends

bot = bot

83 建立 Robot跟 API 77

Nodejs中文電子書

終於完成 AdapterHubot專案裡面的 scripts資料夾內是互動部分不需要像 Adapter如此大費周章處理只新增檔案並且設計好對白之後就會回噗了我開發用的機器人在此大家可以去跟他玩玩[httpplurkcomelct9620 bot](httpplurkcomelct9620 bot)

84 原始資料提供

bull [製 作 一 個 Hubot 的 噗 浪 Adapter](http revo-skillfrosttw blog20120318create-a-hubot-plurk-adapter)

78 8 製作一個 Hubot的 Plurk Adapter

9

Nodejs好用工具介紹

91 logio

Real-time log monitoring in your browser

httplogioorg

nodejs (gt=0412 lt=0611)

npm config set unsafe-perm true

npm install -g --prefix=usrlocal logio

logio server start

httplocalhost8998

79

Nodejs中文電子書

80 9 Nodejs好用工具介紹

10

精選文章

101 Express

bull 使用 NodeJS + Express從 GETPOST Request取值 [邀稿中]

bull link2 [邀稿中]

bull link3 [邀稿中]

81

Nodejs中文電子書

82 10 精選文章

11

參考資源

111 Nodejs書籍

bull Programming Nodejs April 2012 (est) Travis Swicegood Pragmatic Bookshelf

bull e Little Book on CoffeeScript January 2012 Alex MacCaw OrsquoReilly Media

bull Node for Front-End Developers January 2012 Garann Means OrsquoReilly Media

bull Building Hypermedia APIs with HTML5 and Node November 2011 MikeAmundsen OrsquoReilly Media

bull Node Web Development August 2011 David Herron PacktPub

bull CoffeeScript Accelerated JavaScript Development July 2011 Trevor BurnhamPragmatic Bookshelf

bull Getting Started with GEO CouchDB and Nodejs July 2011 Mick omp-son OrsquoReilly Media

bull What Is Node July 2011 Brett McLaughlin OrsquoReilly Media

bull Node Up and Running May 2011 Tom Hughes-Croucher Mike WilsonOrsquoReilly Media

83

Nodejs中文電子書

bull Hands-on Nodejs e Nodejs introduction and API reference May 2011 Pe-dro Teixeira LeanPub

bull e Node Beginner Book Manuel Kiessling

bull Mastering Nodejs visionmedia

112 Nodejs影音教學

bull e Node Sessions e Best of OSCON 2011 August 2011 OrsquoReilly Media(Video)

bull Tom Hughes-Croucher on Node March 2011 OrsquoReilly Media (Video)

113 Nodejs教學網站

bull Node Tuts

114 Nodejs課程

bull Introduction To Nodejs Van Nguyen CodeLesson

84 11 參考資源

Page 39: Nodejs Wiki

Nodejs中文電子書

信任 requrl就會收到結果為testsend=1所以需要透過 url模組的方法將路徑資料過濾

在這邊使用 urlparse的方法裡面帶入網址格式資料會回傳路徑資料為了後需方便使用將回傳的資料設定到 path變數當中在回傳的路徑資料裡面包含資訊如下圖

這邊只需要使用單純的路徑要求直接取用 pathpathname就可以達到我們的目的

最後要做路徑的判別在不同的路徑可以指定不同的輸出在範例中

有三個可能結果第一個從瀏覽器輸入index就會顯示 index結果test 就會呈現出 test頁面最後如果都不符合預期的輸入會直接顯示 default的頁面最後的預防可以讓瀏覽器不會出現非預期結果讓程式的可靠性提昇

底下為測試結果

42 nodejs http路徑建立 33

Nodejs中文電子書

43 nodejs檔案讀取

前面已經介紹如何使用路由(rount)做出不同的回應實際應用只有在瀏覽器只有輸出幾個文字資料總是不夠的在本章節中將介紹如何使

用檔案讀取輸出檔案資料讓使用者在前端瀏覽器也可以讀取到完整的

html css javascript檔案輸出

檔案管理最重要的部分就是 lsquoFile system lthttpnodejsorgdocslatestapifshtmlgtlsquo這個模組此模組可以針對檔案做管理監控讀取等行為裡面有許多預設的方法底下是檔案輸出的基本範例底下會有兩個檔案

第一個是靜態 html檔案

ltDOCTYPE htmlgt

lthtml xmlns=rdquohttpwwww3org1999xhtmlrdquo xmllang=rdquozh-CNrdquo lang=rdquozh-CNrdquogt

ltheadgt

ltmeta http-equiv=rdquoContent-Typerdquo content=rdquotexthtml charset=UTF-7rdquo gt

lttitlegtnodejs index html filelttitlegt

ltheadgt

ltbodygt

34 4 Nodejs基礎

Nodejs中文電子書

lth1gtnodejs index html filelth1gt

ltbodygt

lthtmlgt

另一個為 nodejs程式

var fs = require(rdquofsrdquo)

filename = rdquostaticindexhtmlrdquo

encode = rdquoutf8rdquo

fsreadFile(filename encode function(err file)

consolelog(file)

)

一開始直接載入 le system模組 載入名稱為 fs讀取檔案主要使

用的方法為 readFile裡面以三個參數路徑 ( le path) 編碼方式 (encoding)

回應函式 (callback)路徑必須要設定為靜態 html所在位置才能指定到正確的檔案靜態檔案的編碼方式也必須正確這邊使用靜態檔案的編

碼為 utf8如果編碼設定錯誤nodejs讀取出來檔案結果會使用 byte raw格式輸出如果錯誤編碼格式會導致輸出資料為 byte raw

回應函式中裡面會使用兩個變數error為錯誤資訊如果讀取的檔案不存在或者發生錯誤error數值會是 true如果成功讀取資料 error將會是 falsecontent則是檔案內容資料讀取後將會把資料全數丟到 content這個變數當中

最後程式的輸出結果畫面如下

43 nodejs檔案讀取 35

Nodejs中文電子書

44 nodejs http靜態檔案輸出

前面已經了解如何讀取本地端檔案接下來將配合 http伺服器路由讓每個路由都能夠輸出相對應的靜態 html檔案首先新增加幾個靜態 html檔案

ltDOCTYPE htmlgt

lthtml xmlns=rdquohttpwwww3org1999xhtmlrdquo xmllang=rdquozh-CNrdquo lang=rdquozh-CNrdquogt

ltheadgt

ltmeta http-equiv=rdquoContent-Typerdquo content=rdquotexthtml charset=UTF-7rdquo gt

lttitlegtnodejs index html filelttitlegt

ltheadgt

ltbodygt

lth1gtnodejs index html filelth1gt

ltbodygt

lthtmlgt

ltDOCTYPE htmlgt

lthtml xmlns=rdquohttpwwww3org1999xhtmlrdquo xmllang=rdquozh-CNrdquo lang=rdquozh-CNrdquogt

ltheadgt

ltmeta http-equiv=rdquoContent-Typerdquo content=rdquotexthtml charset=UTF-7rdquo gt

lttitlegtnodejs test html filelttitlegt

ltheadgt

ltbodygt

lth1gtnodejs test html filelth1gt

ltbodygt

lthtmlgt

ltDOCTYPE htmlgt

lthtml xmlns=rdquohttpwwww3org1999xhtmlrdquo xmllang=rdquozh-CNrdquo lang=rdquozh-CNrdquogt

ltheadgt

ltmeta http-equiv=rdquoContent-Typerdquo content=rdquotexthtml charset=UTF-7rdquo gt

lttitlegtnodejs static html filelttitlegt

ltheadgt

ltbodygt

lth1gtnodejs static html filelth1gt

ltbodygt

lthtmlgt

36 4 Nodejs基礎

Nodejs中文電子書

準備一個包含基本路由功能的 http伺服器

var server

ip = rdquo127001rdquo

port = 1337

http = require(rsquohttprsquo)

url = require(rsquourlrsquo)

server = httpcreateServer(function (req res)

var path = urlparse(requrl)

)

serverlisten(port ip)

consolelog(rdquoServer running at httprdquo + ip + rdquordquo + port)

加入 le system 模組使用 readFile 的功能將這一段程式放置於

createServer的回應函式中

fsreadFile(filePath encode function(err file)

)

readFile的回應函式裡面加入頁面輸出讓瀏覽器可以正確讀到檔案在這邊我們設定讀取的檔案為 html 靜態檔案所以 Content-type 設定為texthtml讀取到檔案的內容將會正確輸出成 html靜態檔案

fsreadFile(filePath encode function(err file)

reswriteHead(200 rsquoContent-Typersquo rsquotexthtmlrsquo)

reswrite(file)

resend()

)

到這邊為止基本的程式內容都已經完成剩下一些細節的調整首先

路徑上必須做調整目前的靜態檔案全部都放置於 static資料夾底下設定一個變數來記住資料夾位置

接著將瀏覽器發出要求路徑與資料夾組合讀取正確 html靜態檔案使用者有可能會輸入錯誤路徑所以在讀取檔案的時候要加入錯誤處理

同時回應 404伺服器無法正確回應的 http header格式

加入這些細節的修改一個基本的 http 靜態 html輸出伺服器就完成了完整程式碼如下

44 nodejs http靜態檔案輸出 37

Nodejs中文電子書

var server

ip = rdquo127001rdquo

port = 1337

http = require(rsquohttprsquo)

fs = require(rdquofsrdquo)

folderPath = rdquostaticrdquo

url = require(rsquourlrsquo)

path

filePath

encode = rdquoutf8rdquo

server = httpcreateServer(function (req res)

path = urlparse(requrl)

filePath = folderPath + pathpathname

fsreadFile(filePath encode function(err file)

if (err)

reswriteHead(404 rsquoContent-Typersquo rsquotextplainrsquo)

resend()

return

reswriteHead(200 rsquoContent-Typersquo rsquotextapplicationrsquo)

reswrite(file)

resend()

)

)

serverlisten(port ip)

consolelog(rdquoServer running at httprdquo + ip + rdquordquo + port)

45 nodejs http GET資料擷取

http伺服器中除了路由之外另一個最常使用的方法就是擷取 GET資料本單元將會介紹如何透過基本 http伺服器擷取瀏覽器傳來的要求擷取 GET資料

在 http協定中GET參數都是藉由 URL從瀏覽器發出要求送至伺服器

38 4 Nodejs基礎

Nodejs中文電子書

端基本的傳送網址格式可能如下

http127001testsend=1amptest=2

上面這段網址裡面的 GET參數就是 send而這個變數的數值就為 1如果想要在 http伺服器取得 GET資料需要在瀏覽器給予的要求 (request)做處理

首先需要載入 query string這個模組這個模組主要是用來將字串資料

過濾後轉換成javascript物件

qs = require(rsquoquerystringrsquo)

接著在第一階段利用 url模組過濾瀏覽器發出的 URL資料後將回應的物件裡面的 query這個變數是一個字串值資料過濾後如下

send=1amptest=2

透過 query string使用 parse這個方法將資料轉換成 javascript物件就表示 GET的資料已經被伺服器端正式擷取下來

path = urlparse(requrl)

parameter = qsparse(pathquery)

整個 nodejs http GET參數完整擷取程式碼如下

var server

ip = rdquo127001rdquo

port = 1337

http = require(rsquohttprsquo)

qs = require(rsquoquerystringrsquo)

url = require(rsquourlrsquo)

server = httpcreateServer(function (req res)

var path = urlparse(requrl)

parameter = qsparse(pathquery)

consoledir(parameter)

reswriteHead(200 rsquoContent-Typersquo rsquotextplainrsquo)

reswrite(rsquoBrowser test GET parameternrsquo)

resend()

)

45 nodejs http GET資料擷取 39

Nodejs中文電子書

serverlisten(port ip)

consolelog(rdquoServer running at httprdquo + ip + rdquordquo + port)

程式運作之後由瀏覽器輸入要求網址之後nodejs伺服器端回應資料為

Server running at http1270011337

send rsquo1rsquo test rsquo1rsquo

46 本章結語

前面所解說的部份一大部分主要是處理 http伺服器基本問題雖然在某些部分有牽扯到 http 伺服器基本運作原理主要還是希望可以藉由這些基本範例練習 nodejs練習回應函式與語法串接的特點習慣編寫javascript風格程式當然直接這樣開發 nodejs是非常辛苦的接下來在模組實戰開發的部份將會介紹特定的模組一步一步帶領各位從無到有進行

nodejs應用程式開發

40 4 Nodejs基礎

5

NPM套件管理工具

npm全名為 Node Package Manager是 Nodejs的套件(package)管理工具類似 Perl的 ppm或 PHP的 PEAR等安裝 npm後使用npm install

module name指令即可安裝新套件維護管理套件的工作會更加輕鬆

npm可以讓Nodejs的開發者直接利用擴充線上的套件庫(packagesregistry)加速軟體專案的開發npm提供很友善的搜尋功能可以快速找到安裝需要的套件當這些套件發行新版本時npm也可以協助開發者自動更新這些套件

npm不僅可用於安裝新的套件它也支援搜尋列出已安裝模組及更新的功能

51 安裝 NPM

Nodejs在 063版本開始內建 npm讀者安裝的版本若是此版本或更新的版本就可以略過以下安裝說明

若要檢查 npm是否正確安裝可以使用以下的指令

npm -v

41

Nodejs中文電子書

執行結果說明

若 npm正確安裝執行 npm -v將會看到類似 110-2的版本訊息

若讀者安裝的 Nodejs版本比較舊或是有興趣嘗試自己動手安裝 npm工具則可以參考以下的說明

安裝於Windows系統

Nodejs for Windows 於 062 版開始內建 npm使用 nodejsorg 官方提供的安裝程式不需要進一步的設定就可以立即使用 npm指令對於Windows的開發者來說大幅降低環境設定的問題與門檻

除了使用 Nodejs內建的 npm讀者也可以從 npm官方提供的以下網址

httpnpmjsorgdist

這是由 npm提供的 Fancy Windows Install版本請下載壓縮檔(例如npm-110-3zip)並將壓縮檔內容解壓縮至 Nodejs的安裝路徑(例如CProgram Filesnodejs)

解壓縮後在 Nodejs的安裝路徑下應該有以下的檔案及資料夾

bull npmcmd(檔案)

bull node modules(資料夾)

安裝於 Linux系統

Ubuntu Linux的使用者可以加入 NPM Unoffcial PPA這個 repository即可使用 apt-get完成 npm安裝

42 5 NPM套件管理工具

Nodejs中文電子書

Ubuntu Linux使用 apt-get安裝 npm

sudo apt-get install python-software-properties

sudo add-apt-repository ppagias-kay-leenpm

sudo apt-get update

sudo apt-get install npm

npm官方提供的安裝程式 installsh可以適用於大多數的 Linux系統使用這個安裝程式請先確認

1 系統已安裝 curl工具(請使用 curl --version查看版本訊息)

2 已安裝 Nodejs並且 PATH正確設置

3 Nodejs的版本必須大於 04x

以下為 npm提供的安裝指令

curl httpnpmjsorginstallsh | sh

安裝成功會看到如下訊息

installsh安裝成功的訊息

npm10105 homeUSERNAMElocalnodelibnode_modulesnpm

It worked

安裝於Mac OS X

建議採用與 Nodejs相同的方式進行 npm的安裝例如使用MacPorts安裝 Nodejs就同樣使用MacPorts安裝 npm這樣對日後的維護才會更方便容易

使用 MacPorts安裝 npm是本書比較建議的方式它可以讓 npm的安裝移除及更新工作自動化將會幫助開發者節省寶貴時間

51 安裝 NPM 43

Nodejs中文電子書

安裝MacPorts的提示

在MacPorts網站可以取得 OS X系統版本對應的安裝程式(例如 106或 107)httpwwwmacportsorg安裝過程會詢問系統管理者密碼使用預設的選項完成安裝即可安

裝MacPorts之後在終端機執行 port -v將會看到MacPorts的版本訊息

安裝 npm之前先更新MacPorts的套件清單以確保安裝的 npm是最新版本

sudo port -d selfupdate

接著安裝 npm

sudo port install npm

若讀者的 Nodejs並非使用MacPorts安裝則不建議使用MacPorts安裝npm因為 MacPorts會自動檢查並安裝相依套件而 npm相依 nodejs所以MacPorts也會一併將 nodejs套件安裝造成先前讀者使用其它方式安裝的 nodejs被覆蓋

讀者可以先使用 MacPorts安裝 curl(sudo port install curl)

再參考 Linux的 installsh安裝方式即可使用 npm官方提供的安裝程式

NPM安裝後測試

npm是指令列工具(command-line tool)使用時請先打開系統的文字終端機工具

測試 npm安裝與設定是否正確請輸入指令如下

npm -v

或是

npm --version

如果 npm已經正確安裝設定就會顯示版本訊息

44 5 NPM套件管理工具

Nodejs中文電子書

執行結果(範例)

110-2

52 使用 NPM安裝套件

npm目前擁有超過 6000種套件(packages)可以在 npm registry使用關鍵字搜尋套件

httpsearchnpmjsorg

舉例來說在關鍵字欄位輸入「coffee-script」下方的清單就會自動列出包含 coffee-script關鍵字的套件

接著我們回到終端機模式的操作npm的指令工具本身就可以完成套

件搜尋的任務

例如以下的指令同樣可以找出 coffee-script相關套件

npm search coffee-script

以下是搜尋結果的參考畫面

52 使用 NPM安裝套件 45

Nodejs中文電子書

找到需要的套件後(例如 express)即可使用以下指令安裝

npm install coffee-script

值得注意的一點是使用 npm install會將指定的套件安裝在工

作目錄(Working Directory)的 node modules資料夾下

以Windows為例如果執行 npm install的目錄位於

Cproject1

那麼 npm將會自動建立一個 node modules的子目錄(如果不存在)

Cproject1node modules

並且將下載的套件放置於這個子目錄例如

Cproject1node modulescoffee-script

這個設計讓專案可以個別管理相依的套件並且可以在專案佈署或發

行時將這些套件(位於 node modules)一併打包方便其它專案的使用者不必再重新下載套件

這個 npm install的預設安裝模式為 local(本地)只會變更當前專案的資料夾不會影響系統

另一種安裝模式稱為 global(全域)這種模式會將套件安裝到系統資

料夾也就是 npm安裝路徑的 node modules資料夾例如

CProgram Filesnodejsnode modules

46 5 NPM套件管理工具

Nodejs中文電子書

是否要使用全域安裝可以依照套件是否提供新指令來判斷舉例來說express 套件提供 express 這個指令而 coffee-script 則提供 coffee

指令

在 local 安 裝 模 式 中這 些 指 令 的 程 式 檔 案會 被 安 裝 到node modules的 bin這個隱藏資料夾下除非將bin的路徑加入 PATH環境變數否則要執行這些指令將會相當不便

為了方便指令的執行我們可以在 npm install 加上 -g 或 --

global參數啟用 global安裝模式例如

npm install -g coffee-script

npm install -g express

使用 global安裝模式需要注意執行權限的問題若權限不足可能會出現類似以下的錯誤訊息

npm ERR Error EACCES permission denied rsquorsquo

npm ERR

npm ERR Please try running this command again as rootAdministrator

要獲得足夠得執行權限請參考以下說明

bull Windows 7 或 2008 以上在「命令提示字元」的捷徑按右鍵選擇「以系統管理員身分執行」執行 npm指令時就會具有 Administrator身分

bull Mac OS X或 Linux系統可以使用 sudo指令例如

sudo npm install -g express

bull Linux系統可以使用 root權限登入或是以「sudo su -」切換成 root身分(使用 root權限操作系統相當危險因此並不建議使用這種方式)

若加上 -g參數使用 npm install -g coffee-script完成安裝

後就可以在終端機執行 coffee指令例如

coffee -v

52 使用 NPM安裝套件 47

Nodejs中文電子書

執行結果(範例)

CoffeeScript version 120

53 套件的更新及維護

除了前一節說明的 search 及 install 用法npm 還提供其他許多指令(commands)

使用 npm help可以查詢可用的指令

npm help

執行結果(部分)

where ltcommandgt is one of

adduser apihelp author bin bugs c cache completion

config deprecate docs edit explore faq find get

help help-search home i info init install la link

list ll ln login ls outdated owner pack prefix

prune publish r rb rebuild remove restart rm root

run-script s se search set show star start stop

submodule tag test un uninstall unlink unpublish

unstar up update version view whoami

使用 npm help command可以查詢指令的詳細用法例如

npm help list

接下來本節要介紹開發過程常用的 npm指令

使用 list可以列出已安裝套件

npm list

48 5 NPM套件管理工具

Nodejs中文電子書

執行結果(範例)

-- coffee-script120

- express256

- connect185

| -- formidable108

-- mime124

-- mkdirp007

-- qs041

檢視某個套件的詳細資訊例如

npm show express

升級所有套件(如果該套件已發佈更新版本)

npm update

升級指定的套件

npm update express

移除指定的套件

npm uninstall express

54 使用 packagejson

對於正式的 Nodejs專案可以建立一個命名為 packagejson的設

定檔(純文字格式)檔案內容參考範例如下

54 使用 packagejson 49

Nodejs中文電子書

packagejson(範例)

rdquonamerdquo rdquoapplication-namerdquo

rdquoversionrdquo rdquo001rdquo

rdquoprivaterdquo true

rdquodependenciesrdquo

rdquoexpressrdquo rdquo255rdquo

rdquocoffee-scriptrdquo rdquolatestrdquo

rdquomongooserdquo rdquogt= 253rdquo

其中 name與 version依照專案的需求設置

需要注意的是 dependencies的設定它用於指定專案相依的套件名

稱及版本

bull rdquoexpressrdquo rdquo255rdquo

代表此專案相依版本 255的 express套件

bull rdquocoffee-scriptrdquo rdquolatestrdquo

使用最新版的 coffee-script套件(每次更新都會檢查新版)

bull rdquomongooserdquo rdquogt= 253rdquo

使用版本大於 253的 mongoose套件

假設某個套件的新版可能造成專案無法正常運作就必須指定套件的

版本避免專案的程式碼來不及更新以相容新版套件通常在開發初期的

專案需要盡可能維持新套件的相容性(以取得套件的更新或修正)可以

用「gt=」設定最低相容的版本或是使用「latest」設定永遠保持最新

套件

50 5 NPM套件管理工具

6

Express介紹

在前面的 nodejs基礎當中介紹許多許多開設 http的使用方法及介紹以及許多基本的 nodejs基本應用

接下來要介紹一個套件稱為 express [Express](httpexpressjscom)這個套件主要幫忙解決許多 nodejs http server所需要的基本服務讓開發 httpservice變得更為容易不需要像之前需要透過層層模組(module)才有辦法開始編寫自己的程式

這個套件是由 TJ Holowaychuk 製作而成的套件裡面包含基本的路由處理 (route)http資料處理(GETPOSTPUT)另外還與樣板套件(jshtml template engine)搭配同時也可以處理許多複雜化的問題

61 Express安裝

安裝方式十分簡單只要透過之前介紹的 NPM就可以使用簡單的指令安裝指令如下

這邊建議需要將此套件安裝成為全域模組方便日後使用

51

Nodejs中文電子書

62 Express基本操作

express的使用也十分簡單先來建立一個基本的 hello world

var app = require(rsquoexpressrsquo)createServer()

port = 1337

applisten(port)

appget(rsquorsquo function(req res)

ressend(rsquohello worldrsquo)

)

consolelog(rsquostart express servernrsquo)

可以從上面的程式碼發現基本操作與 nodejs http的建立方式沒有太大差異主要差在當我們設定路由時可以直接透過 appget方式設定回應與接受方式

63 Express路由處理

Express對於 http服務上有許多包裝讓開發者使用及設定上更為方便例如有幾個路由設定那我們就統一藉由 appget來處理

Create http server

appget(rsquorsquo function(req res)

ressend(rsquohello worldrsquo)

)

appget(rsquotestrsquo function(req res)

ressend(rsquotest renderrsquo)

)

appget(rsquouserrsquo function(req res)

ressend(rsquouser pagersquo)

)

52 6 Express介紹

Nodejs中文電子書

如上面的程式碼所表示appget可以帶入兩個參數第一個是路徑名稱設定第二個為回應函式 (call back function)回應函式裡面就如同之前的 createServer方法裡面包含 requestresponse兩個物件可供使用使用者就可以透過瀏覽器輸入不同的 url切換到不同的頁面顯示不同的結果

路由設定上也有基本的配對方式讓使用者從瀏覽器輸入的網址可以

是一個變數只要符合型態就可以有對應的頁面產出例如

Create http server

appget(rsquouseridrsquo function(req res)

ressend(rsquouser rsquo + reqparamsid)

)

appget(rsquonumberrsquo function(req res)

ressend(rsquonumber rsquo + reqparamsnumber)

)

裡 面 使 用 到number 從 網 址 輸 入 之 後 就 可 以 直 接 使 用reqparamsnumber 取得所輸入的資料變成 url 參數使用當然前面也是可以加上路徑的設定userid在瀏覽器上路徑必須符合userxxx透

過 reqparamsid就可以取到 xxx這個字串值

另外express參數處理也提供了路由參數配對處理也可以透過正規表示法作為參數設定

var app = require(rsquoexpressrsquo)createServer()

port = 1337

applisten(port)

appget(^ip((d23)((d23))((d23))((d23))) function(req res)

ressend(reqparams)

)

上面程式碼可以發現後面路由設定的型態是正規表示法裡面設定

格式為ip之後必須要加上 ip型態才會符合資料格式同時取得 ip資料已經由正規表示法將資料做分群因此可以取得 ip的四個數字

此程式執行之後可以透過瀏覽器測試輸入網址為 localhost3000ip25525510010可以從頁面獲得資料

63 Express路由處理 53

Nodejs中文電子書

此章節全部範例程式碼如下

overview

author Caesar Chi

blog clonnblogspotcom

version 20120226

create server

var app = require(rsquoexpressrsquo)createServer()

port = 1337

applisten(port)

normal style

appget(rsquorsquo function(req res)

ressend(rsquohello worldrsquo)

)

appget(rsquotestrsquo function(req res)

ressend(rsquotest renderrsquo)

)

parameter style

appget(rsquouseridrsquo function(req res)

ressend(rsquouser rsquo + reqparamsid)

)

appget(rsquonumberrsquo function(req res)

ressend(rsquonumber rsquo + reqparamsnumber)

)

REGX style

appget(^ip((d23)((d23))((d23))) function(req res)

ressend(reqparams)

)

appget(rsquorsquo function(req res)

ressend(rsquoPage not foundrsquo 404)

54 6 Express介紹

Nodejs中文電子書

)

consolelog(rsquostart express servernrsquo)

64 Express middleware

Express 裡面有一個十分好用的應用概念稱為 middleware可以透過middleware做出複雜的效果同時上面也有介紹 next方法參數傳遞就是靠 middleware的概念來傳遞參數讓開發者可以明確的控制程式邏輯

create http server

appuse(expressbodyParser())

appuse(expressmethodOverride())

appuse(expresssession())

上面都是一種 middleware的使用方式透過 appuse方式裡面載入函式執行方法回應函式會包含三個基本參數responserequestnext其中next表示下一個 middleware執行函式同時會自動將預設三個參數繼續帶往下個函式執行底下有個實驗

上面的片段程式執行後開啟瀏覽器連結上 localhost1337會發現伺服器回應結果順序如下

first middle ware

second middle ware

execute middle ware

end middleware function

從上面的結果可以得知剛才設定的 middleware都生效了在 appuse設定的 middleware是所有 url皆會執行方法如果有指定特定方法就可以使用 appget的 middleware設定在 appget函式的第二個參數就可以帶入函式或者是匿名函式只要函式裡面最後會接受 request response next這三個參數同時也有正確指定 next函式的執行時機最後都會執行到最後一個方法當然開發者也可以評估程式邏輯要執行到哪一個階段讓邏輯

可以更為分明

64 Express middleware 55

Nodejs中文電子書

65 Express路由應用

在實際開發上可能會遇到需要使用參數等方式混和變數一起使用

express裡面提供了一個很棒的處理方法 appall這個方式可以先採用基本路由配對再將設定為每個不同的處理方式開發者可以透過這個方式簡

化自己的程式邏輯

overview

author

version 20120226

create server

var app = require(rsquoexpressrsquo)createServer()

port = 1337

users = [

name rsquoClonnrsquo

name rsquoChirsquo

]

applisten(port)

appall(rsquouseridoprsquo function(req res next)

requser = users[reqparamsid]

if (requser)

next()

else

next(new Error(rsquocannot find user rsquo + reqparamsid))

)

appget(rsquouseridrsquo function(req res)

ressend(rsquoviewing rsquo + requsername)

)

appget(rsquouserideditrsquo function(req res)

ressend(rsquoediting rsquo + requsername)

)

56 6 Express介紹

Nodejs中文電子書

appget(rsquouseriddeletersquo function(req res)

ressend(rsquodeleting rsquo + requsername)

)

appget(rsquorsquo function(req res)

ressend(rsquoPage not foundrsquo 404)

)

consolelog(rsquostart express servernrsquo)

內部宣告一組預設的使用者分別給予名稱設定藉由 appall這個方法可以先將路由雛形建立再接下來設定 appget的路徑格式只要符合格式就會分配進入對應的方法中像上面的程式當中如果使用者輸入路徑為user0除了執行 appall程式之後執行 next方法就會對應到路徑設定為userid的這個方法當中如果使用者輸入路徑為user0edit就會執行到useridedit的對應方法

66 Express GET應用範例

我們準備一個使用 GET方法傳送資料的表單

ltDOCTYPE htmlgt

lthtmlgt

ltheadgt

ltmeta http-equiv=rdquoContent-Typerdquo content=rdquotexthtml charset=utf8rdquogt

lttitlegtNodejs 菜鳥筆記 (1)lttitlegt

ltlink rel=rdquostylesheetrdquo href=rdquocssstylecssrdquo type=rdquotextcssrdquo media=rdquoallrdquo gt

ltheadgt

ltbodygt

lth1gtNodejs 菜鳥筆記-註冊lth1gt

ltform id=rdquosignuprdquo method=rdquoGETrdquo action=rdquohttplocalhost3000Signuprdquogt

ltlabelgt 使用者名稱ltlabelgtltinput type=rdquotextrdquo id=rdquousernamerdquo name=rdquousernamerdquo gtltbrgt

ltlabelgt 電子郵件ltlabelgtltinput type=rdquotextrdquo id=rdquoemailrdquo name=rdquoemailrdquo gtltbrgt

ltinput type=rdquosubmitrdquo value=rdquo 註冊我的帳號rdquo gtltbrgt

ltformgt

ltbodygt

lthtmlgt

66 Express GET應用範例 57

Nodejs中文電子書

這個表單沒有什麼特別的地方我們只需要看第 9 行form 使用的method是 GET然後 action是rdquohttplocalhost3000Signupldquo等一下我們要來撰寫Signup這個 URL Path的處理程式

處理 Signup行為

我們知道所謂的 GET方法會透過 URL來把表單的值給帶過去以上面的表單來說到時候 URL會以這樣的形式傳遞

httplocalhost3000Signupusername=xxxampemail=xxx

所以要能處理這樣的資料必須有以下功能

bull 解析 URL

bull 辨別動作是 Signup

bull 解析出 username和 email

一旦能取得 username和 email的值程式就能加以應用了

處理 Signup的程式碼雛形

load module

var url = require(rsquourlrsquo)

urlData = urlparse(requrltrue)

action = urlDatapathname

reswriteHead(200 rdquoContent-Typerdquordquotexthtml charset=utf-8rdquo)

if (action === rdquoSignuprdquo)

user = urlDataquery

resend(rdquolth1gtrdquo + userusername + rdquo 歡迎您的加入 lth1gtltpgt 我們已經將會員啟用信寄至rdquo + useremail + rdquoltpgtrdquo)

首先需要加載 url module它是用來協助我們解析 URL的模組接著使用 urlparse方法第一個傳入 url字串變數也就是 requrl另外第二個參數的用意是設為 ture則引進 querystring模組來協助處理預設是 false它影響到的是 urlDataquery設為 true會傳回物件不然就只是一般的字串urlparse會將字串內容整理成一個物件我們把它指定給 urlData

action變數作為記錄 pathname這是我們稍後要來判斷目前網頁的動作是什麼接著先將 html表頭資訊 (Header)準備好再來判斷路徑邏輯如

58 6 Express介紹

Nodejs中文電子書

果是 Signup這個動作就把 urlDataquery裡的資料指定給 user然後輸出userusername和 useremail把使用者從表單註冊的資料顯示於頁面中

最後進行程式測試啟動 nodejs主程式之後開啟瀏覽器就會看到表單填寫完畢按下送出就可以看到結果了

完整 nodejs程式碼如下

var http = require(rsquohttprsquo)

url = require(rsquourlrsquo)

fs = require(rdquofsrdquo)

server

server = httpcreateServer(function (reqres)

var urlData

encode = rdquoutf8rdquo

filePath = rdquoviewexpress_get_example_formhtmlrdquo

action

urlData = urlparse(requrltrue)

action = urlDatapathname

reswriteHead(200 rdquoContent-Typerdquordquotexthtml charset=utf-8rdquo)

if (action === rdquoSignuprdquo)

user = urlDataquery

resend(rdquolth1gtrdquo + userusername + rdquo 歡迎您的加入 lth1gtltpgt 我們已經將會員啟用信寄至rdquo + useremail + rdquoltpgtrdquo)

else

fsreadFile(filePath encode function(err file)

reswrite(file)

resend()

)

)

serverlisten(3000)

consolelog(rsquoServer 跑起來了現在時間是rsquo + new Date())

66 Express GET應用範例 59

Nodejs中文電子書

67 Express POST應用範例

一開始準備基本的 html表單傳送內容以 POST方式form的 action屬性設定為 POST其餘 html內容與前一個範例應用相同

ltDOCTYPE htmlgt

lthtmlgt

ltheadgt

ltmeta http-equiv=rdquoContent-Typerdquo content=rdquotexthtml charset=utf8rdquogt

lttitlegtNodejs 菜鳥筆記 (1)lttitlegt

ltlink rel=rdquostylesheetrdquo href=rdquocssstylecssrdquo type=rdquotextcssrdquo media=rdquoallrdquo gt

ltheadgt

ltbodygt

lth1gtNodejs 菜鳥筆記-註冊lth1gt

ltform id=rdquosignuprdquo method=rdquoPOSTrdquo action=rdquohttplocalhost3000Signuprdquogt

ltlabelgt 使用者名稱ltlabelgtltinput type=rdquotextrdquo id=rdquousernamerdquo name=rdquousernamerdquo gtltbrgt

ltlabelgt 電子郵件ltlabelgtltinput type=rdquotextrdquo id=rdquoemailrdquo name=rdquoemailrdquo gtltbrgt

ltinput type=rdquosubmitrdquo value=rdquo 註冊我的帳號rdquo gtltbrgt

ltformgt

ltbodygt

lthtmlgt

nodejs的程式處理邏輯與前面 GET範例類似部分程式碼如下

qs = require(rsquoquerystringrsquo)

if (action === rdquoSignuprdquo)

formData = rsquorsquo

reqon(rdquodatardquo function (data)

formData += data

)

reqon(rdquoendrdquo function ()

user = qsparse(formData)

resend(rdquolth1gtrdquo + userusername + rdquo 歡迎您的加入 lth1gtltpgt 我們已經將會員啟用信寄至rdquo + useremail + rdquoltpgtrdquo)

)

60 6 Express介紹

Nodejs中文電子書

主要加入了rsquoquerystringrsquo這個moduel方便我們等一下解析由表單 POST回來的資料另外加入一個 formData的變數用來搜集待等一下表單回傳的資料前面的 GET範例我們只從 req拿出 url的資料這次要在利用req身上的事件處理

JavaScript 在訂閱事件時使用 addEventListener而 nodejs 使用的則是on這邊加上了監聽 data的事件會在瀏覽器傳送資料到Web Server時被執行參數是它所接收到的資料型態是字串

接著再增加 end的事件當瀏覽器的請求事件結束時它就會動作

由於瀏覽器使用 POST在上傳資料時會將資料一塊塊地上傳因為我們在監聽 data事件時透過 formData變數將它累加起來lt不過由於我們

上傳的資料很少一次就結束不過如果日後需要傳的是資料比較大的檔

案這個累加動作就很重要

當資料傳完就進到 end 事件中會用到 qsparse 來解析 formDataformData的內容是字串內容是

username=wordsmithampemail=wordsmith40somewhere

而 qsparse可以幫我們把這個 querystring轉成物件的格式也就是

username=wordsmithampemail=wordsmith40somewhere

一旦轉成物件並指定給 user之後其他的事情就和 GET方法時操作的一樣寫 response的表頭將內容回傳並將 userusername和 useremail代入到內容中

修改完成後接著執行 nodejs程式啟動 web server開啟瀏覽器進入表單測試看看POST的方式能否順利運作

完整程式碼如下

var http = require(rsquohttprsquo)

url = require(rsquourlrsquo)

fs = require(rdquofsrdquo)

qs = require(rsquoquerystringrsquo)

server

server = httpcreateServer(function (reqres)

var urlData

67 Express POST應用範例 61

Nodejs中文電子書

encode = rdquoutf8rdquo

filePath = rdquoviewexpress_post_example_formhtmlrdquo

formData

action

urlData = urlparse(requrltrue)

action = urlDatapathname

reswriteHead(200 rdquoContent-Typerdquordquotexthtml charset=utf-8rdquo)

if (action === rdquoSignuprdquo)

formData = rsquorsquo

reqon(rdquodatardquo function (data)

formData += data

)

reqon(rdquoendrdquo function ()

user = qsparse(formData)

resend(rdquolth1gtrdquo + userusername + rdquo 歡迎您的加入 lth1gtltpgt 我們已經將會員啟用信寄至rdquo + useremail + rdquoltpgtrdquo)

)

else

fsreadFile(filePath encode function(err file)

reswrite(file)

resend()

)

)

serverlisten(3000)

consolelog(rsquoServer 跑起來了現在時間是rsquo + new Date())

62 6 Express介紹

Nodejs中文電子書

68 Express AJAX應用範例

在 Nodejs要使用 Ajax傳送資料並且與之互動在接受資料的部份沒有太大的差別client端不是用 GET就是用 POST來傳資料重點在處理完後用 JSON格式回傳當然 Ajax不見得只傳 JSON格式有時是回傳一段 HTML碼不過後者對伺服器來說基本上就和前兩篇沒有差別了所以我們還是以回傳 JSON做為這一回的主題

這一回其實大多數的工作都會落在前端 Ajax上面前端要負責發送與接收資料並在接收資料後撤掉原先發送資料的表單並將取得的資料

改成 HTML格式之後放上頁面

首先先準備 HTML靜態頁面資料

ltDOCTYPE htmlgt

lthtmlgt

ltheadgt

lttitlegtNodejs 菜鳥筆記 (3)lttitlegt

ltlink rel=rdquostylesheetrdquo href=rdquocssstylecssrdquo type=rdquotextcssrdquo media=rdquoallrdquo gt

ltscript src=rdquohttpsajaxgoogleapiscomajaxlibsjquery171jqueryminjsrdquogtltscriptgt

ltheadgt

ltbodygt

lth1gtNodejs 菜鳥筆記-註冊 (用 Ajax)lth1gt

ltform id=rdquosignuprdquo action=rdquoSignuprdquo method=rdquoPOSTrdquogt

ltlabelgt 使用者名稱ltlabelgtltinput type=rdquotextrdquo id=rdquousernamerdquo name=rdquousernamerdquo gtltbrgt

ltlabelgt 電子郵件ltlabelgtltinput type=rdquotextrdquo id=rdquoemailrdquo name=rdquoemailrdquo gtltbrgt

ltinput type=rdquosubmitrdquo value=rdquo 註冊我的帳號rdquo gtltbrgt

ltformgt

ltdiv id=rdquoresultrdquogt

ltdivgt

ltscript type=rdquotextjavascriptrdquogt

$(function()$(rsquosignup input[type=rdquosubmitrdquo]rsquo)click(function(e)

var user =

userusername = $(rdquousernamerdquo)val()useremail = $(rdquoemailrdquo)val()

$post(rdquoSignuprdquouserfunction(data)greet(data)

)

68 Express AJAX應用範例 63

Nodejs中文電子書

epreventDefault()

)

var greet = function(msg)

var resultNode = $(rsquoresultrsquo)greeting = $(rsquolth2gtHirsquo+msgusername+rsquo 你的會員 id 是rsquo+ msgid +rsquo 我們會將會員啟動信寄至rsquo+msgemail+rsquolth2gtrsquo)

$(rsquosignuprsquo)hide()resultNodehtml(rdquordquo)

resultNodeappend(greeting)

)

ltscriptgt

ltbodygt

lthtmlgt

HTML頁面上準備了一個表單用來傳送註冊資料接著直接引用了Google CDN來載入 jQuery用來幫我們處理 Ajax的工作這次要傳送和接收的工作很大的變動都在 HTML頁面上的 JavaScript當中我們要做的事有 (相關 jQuery處理這邊不多做贅述指提起主要功能解說)

bull 用 jQUery取得 submit按鈕綁定它的 click動作

bull 取得表單 username和 email的值存放在 user這個物件中

bull 用 jQuery的 $post方法將 user的資料傳到 Server

bull 一旦成功取得資料後透過 greet這個 function組成回報給 user的訊息

bull 清空原本給使用者填資料的表單

bull 將 Server回傳的 usernameemail和 id這 3個資料組成回應的訊息

bull 將訊息放到原本表單的位置

經過以上的處理後一個 Ajax的表單的基本功能已經完成

接著進行 nodejs主要程式的編輯部分程式碼如下

var fs = require(rdquofsrdquo)

64 6 Express介紹

Nodejs中文電子書

qs = require(rsquoquerystringrsquo)

if (action === rdquoSignuprdquo)

formData = rsquorsquo

reqon(rdquodatardquo function (data)

formData += data

)

reqon(rdquoendrdquo function ()

var msg

user = qsparse(formData)

userid = rdquo123456rdquo

msg = JSONstringify(user)

reswriteHead(200 rdquoContent-Typerdquordquoapplicationjson charset=utf-8rdquordquoContent-Lengthrdquomsglength)

resend(msg)

)

這裡的程式和前面 POST範例基本上大同小異差別在

bull 幫 user的資料加上 id隨意存放一些文字進去讓 Server回傳的資料多於 Client端傳上來的不然會覺得 Server都沒做事

bull 增加了 msg 這個變數存放將 user 物件 JSON 文字化的結果JSONstringify這個轉換函式是 V8引擎所提供的如果你好奇的話

bull 大重點來了我們要告訴 Client端這次回傳的資料格式是 JSON所在 Content-type和 Content-Length要提供給 Client

Server很輕鬆就完成任務了最後進行程式測試啟動 nodejs主程式之後開啟瀏覽器就會看到表單填寫完畢按下送出就可以看到結果了

最後 nodejs本篇範例程式碼如下

var http = require(rsquohttprsquo)

url = require(rsquourlrsquo)

fs = require(rdquofsrdquo)

qs = require(rsquoquerystringrsquo)

server

68 Express AJAX應用範例 65

Nodejs中文電子書

server = httpcreateServer(function (reqres)

var urlData

encode = rdquoutf8rdquo

filePath = rdquoviewexpress_ajax_example_formhtmlrdquo

formData

action

urlData = urlparse(requrltrue)

action = urlDatapathname

if (action === rdquoSignuprdquo)

formData = rsquorsquo

reqon(rdquodatardquo function (data)

formData += data

)

reqon(rdquoendrdquo function ()

var msg

user = qsparse(formData)

userid = rdquo123456rdquo

msg = JSONstringify(user)

reswriteHead(200 rdquoContent-TyperdquordquoapplicationjsonrdquordquoContent-Lengthrdquomsglength)

resend(msg)

)

else

fsreadFile(filePath encode function(err file)

reswriteHead(200 rdquoContent-Typerdquordquotexthtml charset=utf-8rdquo)

reswrite(file)

resend()

)

)

serverlisten(3000)

66 6 Express介紹

Nodejs中文電子書

consolelog(rsquoServer 跑起來了現在時間是rsquo + new Date())

69 原始資料提供

bull [NodeJS初學者筆記 (1)-用 GET傳送資料] (httpithelpithomecomtwquestion10087402)

bull [NodeJS初學者筆記 (2)-用 POST傳送資料] (httpithelpithomecomtwquestion10087489)

bull [NodeJS 初學者筆記 (3)-用 Ajax 傳送資料] (httpithelpithomecomtwquestion10087627)

69 原始資料提供 67

Nodejs中文電子書

68 6 Express介紹

7

CoffeeScript

bull Spine

bull Hem

bull Spineapp

Letrsquos Have a Cup of CoffeeScript

bull es5-shim ECMAScript 5 compatibility shims for legacy JavaScript engines

ESnext

node-iform - A middleware for node-validator httpsgithubcomguileennode-iform

69

Nodejs中文電子書

70 7 CoffeeScript

8

製作一個 Hubot的 Plurk Adapter

81 應用事項提醒

此應用範例以CoffeeScript編寫主要程式架構採用Github釋放的Hubot專案以下有幾個主要注意事項請讀者注意

bull Hubot 一開始的架構除了內建的兩個 Adapter 之外其餘都要以Nodejs的Module方式才能運作

bull Hubot的 binhubot裡面寫著 npm install所以不管你怎麼改原始碼也不能改變第一點的狀況

其實上述都在討論同一件事情NodeJS的模組而且模組不能設定相對路徑之類的來安裝一定要包裝成 npm相符和格式才有辦法正確執行

82 建立 Adapter

首先需要準備兩個檔案

bull packagejson

bull plurkcoffee

71

Nodejs中文電子書

有這兩個就足以變成 NodeJS的模組了在開始 Coding之前先來設定一下 packagejson相依性

rdquonamerdquo rdquohubot-plurkrdquo

rdquoversionrdquo rdquo011rdquo

rdquomainrdquo rdquoplurkrdquo

rdquodependenciesrdquo

rdquohubotrdquo rdquogt=205rdquo

rdquooauthrdquo rdquordquo

rdquocronrdquordquordquo

這邊會使用到NodeJS的 cron模組(Module)而登入 Plurk需要OAuth標準授權才行所以也需要加載 OAuth模組

注意Hubot在讀取非內建模組時會自動在前面加上 hubot-的前置

83 建立 Robot跟 API

本篇應用基本上程式碼大部分參考 Hubot - Twitter Adapter來製作主要差異只有在使用 OAuth這個模組Twitter有 Streaming可用而 Plurk則得用 Comet方式來達到即時讀取

plurkcoffee程式碼

Robot = require(rdquohubotrdquo)robot()

Adapter = require(rdquohubtordquo)adapter()

EventEmitter = require(rdquoeventsrdquo)EventEmitter

oauth = require(rdquooauthrdquo)

cronJob = require(rdquocronrdquo)CronJob

class Plurk exntends Adapter

class PlurkStreaming exnteds EventEmitter

72 8 製作一個 Hubot的 Plurk Adapter

Nodejs中文電子書

先弄個基本架構主要 Class皆參考 Twitter Adapter命名方式接著再將 Plurk這個 Class增加幾個Method基本上只要有 run send reply就夠了而 run用來做初始化的部分

class Plurk entends Adapter

send (plurk id strings⋯) -gt

reply (plurk id strings⋯) -gt

run -gt

看起來有點東西接著來處理主要的 Plurk API結合部分

class PlurkStreaming extends EventEmitter

consuctor (options) -gt

plurk (callback) -gt

觀察河道

getChannel -gt

取得 Comet 網址

reply (plurk id message) -gt

回噗

acceptFriends -gt

接受好友

get (path callback) -gt

GET 請求

post (path body callback)-gt

POST 請求(其實是裝飾)

request (method path body callback)-gt

主要的 OAuth 請求

comet (server callback)-gt

噗浪的 Comet 傳回是 JavaScript Callback 要另外處理後才會變成 JSON

然後把注意力集中到 constructor上先把建構子弄好

constructor (options) -gt

super()

if optionskey and optionssecret and optionstoken and optionstoken secret

key = optionskey

secret = optionssecret

token = optionstoken

token secret = optionstoken secret

83 建立 Robot跟 API 73

Nodejs中文電子書

建立 OAuth 連接

consumer = new oauthOAuth(

rdquohttpwwwplurkcomOAuthrequest tokenrdquo

rdquohttpwwwplurkcomOAuthaccess tokenrdquo

key

secret

rdquo10rdquo

rdquohttpwwwplurkcomOAuthauthorizerdquo

rdquoHMAC-SHA1rdquo

)

domain = rdquowwwplurkcomrdquo

初始化取得 Comet 網址

do getChannel

else

throw new Error(rdquo 參數不足需要 Key Secret Token Token Secretrdquo)

接著來處理 request這個 method

request (method path body callback) -gt

記錄一下這次的 Request

consolelog(rdquohttpdomainpathrdquo)

Callback 這邊先不丟進去要用另一種方式處理

request = consumerget(rdquohttpdomainpathrdquo token token secret null)

requeston rdquoresponserdquo (res) -gt

reson rdquodatardquo (chunk) -gt

parseResponse(chunk+rsquorsquo callback)

reson rdquoendrdquo (data) -gt

consolelog rdquoEnd Request pathrdquo

reson rdquoerrorrdquo (data) -gt

consolelog rdquoError rdquo + data

requestend()

處理資料

parseResponse = (data callback) -gt

if datalength gt 0

用 TryCatch 避免處理 JSON 出錯導致整個中斷

try

callback null JSONparse(data)

catch err

74 8 製作一個 Hubot的 Plurk Adapter

Nodejs中文電子書

consolelog(rdquoError Parse JSONrdquo + data err)

繼續執行

callback null data ||

大致上就是這樣上面程式的架構已經將整個 Hubot Plurk Adapter完成因為在測試時竟然因為噗浪 Lag而沒讀到完整的 Comet資料然後造成程式異常為了避免這個問題發生因此需要加上為了完美呈現需要再

加上 Comet的處理所以要使用到 EventEmitter的功能

comet (server callback) -gt

在 Callback 裡面會找不到自身所以設定區域變數

self =

記錄一下這次的 Request

consolelog(rdquo[Comet] serverrdquo)

Callback 這邊先不丟進去要用另一種方式處理

request = consumerget(rdquohttpdomainpathrdquo token token secret null)

requeston rdquoresponserdquo (res) -gt

reson rdquodatardquo (chunk) -gt

parseResponse(chunk+rsquorsquo callback)

reson rdquoendrdquo (data) -gt

consolelog rdquoEnd Request pathrdquo

請求結束發出事件通知可以進行下一次請求

selfemit rdquonextPlurkrdquo

reson rdquoerrorrdquo (data) -gt

consolelog rdquoError rdquo + data

requestend()

處理資料

parseResponse = (data callback) -gt

if datalength gt 0

用 trycatch 避免失敗中斷

try

去掉 JavaScript 的 Callback

data = datamatch(CometChannelscriptCallback((+))s)

jsonData = rdquordquo

if data

83 建立 Robot跟 API 75

Nodejs中文電子書

jsonData = JSONparse(data[1])

else

如果沒有任何 Match 嘗試直接 parse

jsonData = JSONparse(data)

catch err

consolelog(rdquo[Comet] Errorrdquo data err)

用 TryCatch 避免處理 JSON 出錯導致整個中斷

try

只傳入 json 的 data 部分

callback null jsonDatadata

catch err

consolelog(rdquo[Comet]Error Parse JSONrdquo + data err)

繼續執行

callback null data ||

後面的 get跟 post就簡單多了

get (path callback) -gt

request(rdquoGETrdquo path null callback)

post (path body callback) -gt

request(rdquoPOSTrdquo path body callback)

接著處理取的 Comet網址的 getChannel

getChannel -gt

self =

get rdquoAPPRealtimegetUserChannelrdquo (error data) -gt

if error

檢查是否有 comet server

if datacomet server

selfchannel = datacomet server

如果沒有 Channel Ready 就嘗試連接會失敗

selfemit(rsquochannel readyrsquo)

那麼先來處理 Plurk Adaper好處理的部份

send (plruk id strings⋯)-gt

跟 Reply 一樣直接交給 reply 做

reply plurk id strings⋯

76 8 製作一個 Hubot的 Plurk Adapter

Nodejs中文電子書

reply (plurk id strings⋯) -gt

stringsforEach (message) =gt

botreply(plruk id message)

接著把 run處理好就可以上線運作摟

run -gt

self =

options =

key processenvHUBOT PLURK KEY

secret processenvHUBOT PLURK SECRET

token processenvHUBOT PLURK TOKEN

token secret processenvHUBOT PLURK TOKEN SECRET

創建剛剛的 API

bot = new PlurkStreaming(options)

依照 Twitter 的 new RobotTextMessage 會沒有反應所以參考 hubot-minecraft 的方式

r = robotconstructor

處理噗浪河道訊息

doPlurk = (data)-gt

檢查是否為回噗

if dataresponse

datacontent raw = dataresponsecontent raw

datauser id = dataresponseuser id

確定有噗浪 ID 跟訊息

if dataplurk id and datacontent raw

selfreceive new rTextMessage(dataplurk id datacontent raw)

取得 Comet Server 完成開始第一次 Comet 連接

boton rdquochannel readyrdquo () -gt

botplurk selfdoPlurk

上一次 Comet 完成繼續 Polling

boton rdquonextPlurkrdquo ()-gt

botplurk selfdoPlurk

定時接受好友邀請

do botacceptFriends

bot = bot

83 建立 Robot跟 API 77

Nodejs中文電子書

終於完成 AdapterHubot專案裡面的 scripts資料夾內是互動部分不需要像 Adapter如此大費周章處理只新增檔案並且設計好對白之後就會回噗了我開發用的機器人在此大家可以去跟他玩玩[httpplurkcomelct9620 bot](httpplurkcomelct9620 bot)

84 原始資料提供

bull [製 作 一 個 Hubot 的 噗 浪 Adapter](http revo-skillfrosttw blog20120318create-a-hubot-plurk-adapter)

78 8 製作一個 Hubot的 Plurk Adapter

9

Nodejs好用工具介紹

91 logio

Real-time log monitoring in your browser

httplogioorg

nodejs (gt=0412 lt=0611)

npm config set unsafe-perm true

npm install -g --prefix=usrlocal logio

logio server start

httplocalhost8998

79

Nodejs中文電子書

80 9 Nodejs好用工具介紹

10

精選文章

101 Express

bull 使用 NodeJS + Express從 GETPOST Request取值 [邀稿中]

bull link2 [邀稿中]

bull link3 [邀稿中]

81

Nodejs中文電子書

82 10 精選文章

11

參考資源

111 Nodejs書籍

bull Programming Nodejs April 2012 (est) Travis Swicegood Pragmatic Bookshelf

bull e Little Book on CoffeeScript January 2012 Alex MacCaw OrsquoReilly Media

bull Node for Front-End Developers January 2012 Garann Means OrsquoReilly Media

bull Building Hypermedia APIs with HTML5 and Node November 2011 MikeAmundsen OrsquoReilly Media

bull Node Web Development August 2011 David Herron PacktPub

bull CoffeeScript Accelerated JavaScript Development July 2011 Trevor BurnhamPragmatic Bookshelf

bull Getting Started with GEO CouchDB and Nodejs July 2011 Mick omp-son OrsquoReilly Media

bull What Is Node July 2011 Brett McLaughlin OrsquoReilly Media

bull Node Up and Running May 2011 Tom Hughes-Croucher Mike WilsonOrsquoReilly Media

83

Nodejs中文電子書

bull Hands-on Nodejs e Nodejs introduction and API reference May 2011 Pe-dro Teixeira LeanPub

bull e Node Beginner Book Manuel Kiessling

bull Mastering Nodejs visionmedia

112 Nodejs影音教學

bull e Node Sessions e Best of OSCON 2011 August 2011 OrsquoReilly Media(Video)

bull Tom Hughes-Croucher on Node March 2011 OrsquoReilly Media (Video)

113 Nodejs教學網站

bull Node Tuts

114 Nodejs課程

bull Introduction To Nodejs Van Nguyen CodeLesson

84 11 參考資源

Page 40: Nodejs Wiki

Nodejs中文電子書

43 nodejs檔案讀取

前面已經介紹如何使用路由(rount)做出不同的回應實際應用只有在瀏覽器只有輸出幾個文字資料總是不夠的在本章節中將介紹如何使

用檔案讀取輸出檔案資料讓使用者在前端瀏覽器也可以讀取到完整的

html css javascript檔案輸出

檔案管理最重要的部分就是 lsquoFile system lthttpnodejsorgdocslatestapifshtmlgtlsquo這個模組此模組可以針對檔案做管理監控讀取等行為裡面有許多預設的方法底下是檔案輸出的基本範例底下會有兩個檔案

第一個是靜態 html檔案

ltDOCTYPE htmlgt

lthtml xmlns=rdquohttpwwww3org1999xhtmlrdquo xmllang=rdquozh-CNrdquo lang=rdquozh-CNrdquogt

ltheadgt

ltmeta http-equiv=rdquoContent-Typerdquo content=rdquotexthtml charset=UTF-7rdquo gt

lttitlegtnodejs index html filelttitlegt

ltheadgt

ltbodygt

34 4 Nodejs基礎

Nodejs中文電子書

lth1gtnodejs index html filelth1gt

ltbodygt

lthtmlgt

另一個為 nodejs程式

var fs = require(rdquofsrdquo)

filename = rdquostaticindexhtmlrdquo

encode = rdquoutf8rdquo

fsreadFile(filename encode function(err file)

consolelog(file)

)

一開始直接載入 le system模組 載入名稱為 fs讀取檔案主要使

用的方法為 readFile裡面以三個參數路徑 ( le path) 編碼方式 (encoding)

回應函式 (callback)路徑必須要設定為靜態 html所在位置才能指定到正確的檔案靜態檔案的編碼方式也必須正確這邊使用靜態檔案的編

碼為 utf8如果編碼設定錯誤nodejs讀取出來檔案結果會使用 byte raw格式輸出如果錯誤編碼格式會導致輸出資料為 byte raw

回應函式中裡面會使用兩個變數error為錯誤資訊如果讀取的檔案不存在或者發生錯誤error數值會是 true如果成功讀取資料 error將會是 falsecontent則是檔案內容資料讀取後將會把資料全數丟到 content這個變數當中

最後程式的輸出結果畫面如下

43 nodejs檔案讀取 35

Nodejs中文電子書

44 nodejs http靜態檔案輸出

前面已經了解如何讀取本地端檔案接下來將配合 http伺服器路由讓每個路由都能夠輸出相對應的靜態 html檔案首先新增加幾個靜態 html檔案

ltDOCTYPE htmlgt

lthtml xmlns=rdquohttpwwww3org1999xhtmlrdquo xmllang=rdquozh-CNrdquo lang=rdquozh-CNrdquogt

ltheadgt

ltmeta http-equiv=rdquoContent-Typerdquo content=rdquotexthtml charset=UTF-7rdquo gt

lttitlegtnodejs index html filelttitlegt

ltheadgt

ltbodygt

lth1gtnodejs index html filelth1gt

ltbodygt

lthtmlgt

ltDOCTYPE htmlgt

lthtml xmlns=rdquohttpwwww3org1999xhtmlrdquo xmllang=rdquozh-CNrdquo lang=rdquozh-CNrdquogt

ltheadgt

ltmeta http-equiv=rdquoContent-Typerdquo content=rdquotexthtml charset=UTF-7rdquo gt

lttitlegtnodejs test html filelttitlegt

ltheadgt

ltbodygt

lth1gtnodejs test html filelth1gt

ltbodygt

lthtmlgt

ltDOCTYPE htmlgt

lthtml xmlns=rdquohttpwwww3org1999xhtmlrdquo xmllang=rdquozh-CNrdquo lang=rdquozh-CNrdquogt

ltheadgt

ltmeta http-equiv=rdquoContent-Typerdquo content=rdquotexthtml charset=UTF-7rdquo gt

lttitlegtnodejs static html filelttitlegt

ltheadgt

ltbodygt

lth1gtnodejs static html filelth1gt

ltbodygt

lthtmlgt

36 4 Nodejs基礎

Nodejs中文電子書

準備一個包含基本路由功能的 http伺服器

var server

ip = rdquo127001rdquo

port = 1337

http = require(rsquohttprsquo)

url = require(rsquourlrsquo)

server = httpcreateServer(function (req res)

var path = urlparse(requrl)

)

serverlisten(port ip)

consolelog(rdquoServer running at httprdquo + ip + rdquordquo + port)

加入 le system 模組使用 readFile 的功能將這一段程式放置於

createServer的回應函式中

fsreadFile(filePath encode function(err file)

)

readFile的回應函式裡面加入頁面輸出讓瀏覽器可以正確讀到檔案在這邊我們設定讀取的檔案為 html 靜態檔案所以 Content-type 設定為texthtml讀取到檔案的內容將會正確輸出成 html靜態檔案

fsreadFile(filePath encode function(err file)

reswriteHead(200 rsquoContent-Typersquo rsquotexthtmlrsquo)

reswrite(file)

resend()

)

到這邊為止基本的程式內容都已經完成剩下一些細節的調整首先

路徑上必須做調整目前的靜態檔案全部都放置於 static資料夾底下設定一個變數來記住資料夾位置

接著將瀏覽器發出要求路徑與資料夾組合讀取正確 html靜態檔案使用者有可能會輸入錯誤路徑所以在讀取檔案的時候要加入錯誤處理

同時回應 404伺服器無法正確回應的 http header格式

加入這些細節的修改一個基本的 http 靜態 html輸出伺服器就完成了完整程式碼如下

44 nodejs http靜態檔案輸出 37

Nodejs中文電子書

var server

ip = rdquo127001rdquo

port = 1337

http = require(rsquohttprsquo)

fs = require(rdquofsrdquo)

folderPath = rdquostaticrdquo

url = require(rsquourlrsquo)

path

filePath

encode = rdquoutf8rdquo

server = httpcreateServer(function (req res)

path = urlparse(requrl)

filePath = folderPath + pathpathname

fsreadFile(filePath encode function(err file)

if (err)

reswriteHead(404 rsquoContent-Typersquo rsquotextplainrsquo)

resend()

return

reswriteHead(200 rsquoContent-Typersquo rsquotextapplicationrsquo)

reswrite(file)

resend()

)

)

serverlisten(port ip)

consolelog(rdquoServer running at httprdquo + ip + rdquordquo + port)

45 nodejs http GET資料擷取

http伺服器中除了路由之外另一個最常使用的方法就是擷取 GET資料本單元將會介紹如何透過基本 http伺服器擷取瀏覽器傳來的要求擷取 GET資料

在 http協定中GET參數都是藉由 URL從瀏覽器發出要求送至伺服器

38 4 Nodejs基礎

Nodejs中文電子書

端基本的傳送網址格式可能如下

http127001testsend=1amptest=2

上面這段網址裡面的 GET參數就是 send而這個變數的數值就為 1如果想要在 http伺服器取得 GET資料需要在瀏覽器給予的要求 (request)做處理

首先需要載入 query string這個模組這個模組主要是用來將字串資料

過濾後轉換成javascript物件

qs = require(rsquoquerystringrsquo)

接著在第一階段利用 url模組過濾瀏覽器發出的 URL資料後將回應的物件裡面的 query這個變數是一個字串值資料過濾後如下

send=1amptest=2

透過 query string使用 parse這個方法將資料轉換成 javascript物件就表示 GET的資料已經被伺服器端正式擷取下來

path = urlparse(requrl)

parameter = qsparse(pathquery)

整個 nodejs http GET參數完整擷取程式碼如下

var server

ip = rdquo127001rdquo

port = 1337

http = require(rsquohttprsquo)

qs = require(rsquoquerystringrsquo)

url = require(rsquourlrsquo)

server = httpcreateServer(function (req res)

var path = urlparse(requrl)

parameter = qsparse(pathquery)

consoledir(parameter)

reswriteHead(200 rsquoContent-Typersquo rsquotextplainrsquo)

reswrite(rsquoBrowser test GET parameternrsquo)

resend()

)

45 nodejs http GET資料擷取 39

Nodejs中文電子書

serverlisten(port ip)

consolelog(rdquoServer running at httprdquo + ip + rdquordquo + port)

程式運作之後由瀏覽器輸入要求網址之後nodejs伺服器端回應資料為

Server running at http1270011337

send rsquo1rsquo test rsquo1rsquo

46 本章結語

前面所解說的部份一大部分主要是處理 http伺服器基本問題雖然在某些部分有牽扯到 http 伺服器基本運作原理主要還是希望可以藉由這些基本範例練習 nodejs練習回應函式與語法串接的特點習慣編寫javascript風格程式當然直接這樣開發 nodejs是非常辛苦的接下來在模組實戰開發的部份將會介紹特定的模組一步一步帶領各位從無到有進行

nodejs應用程式開發

40 4 Nodejs基礎

5

NPM套件管理工具

npm全名為 Node Package Manager是 Nodejs的套件(package)管理工具類似 Perl的 ppm或 PHP的 PEAR等安裝 npm後使用npm install

module name指令即可安裝新套件維護管理套件的工作會更加輕鬆

npm可以讓Nodejs的開發者直接利用擴充線上的套件庫(packagesregistry)加速軟體專案的開發npm提供很友善的搜尋功能可以快速找到安裝需要的套件當這些套件發行新版本時npm也可以協助開發者自動更新這些套件

npm不僅可用於安裝新的套件它也支援搜尋列出已安裝模組及更新的功能

51 安裝 NPM

Nodejs在 063版本開始內建 npm讀者安裝的版本若是此版本或更新的版本就可以略過以下安裝說明

若要檢查 npm是否正確安裝可以使用以下的指令

npm -v

41

Nodejs中文電子書

執行結果說明

若 npm正確安裝執行 npm -v將會看到類似 110-2的版本訊息

若讀者安裝的 Nodejs版本比較舊或是有興趣嘗試自己動手安裝 npm工具則可以參考以下的說明

安裝於Windows系統

Nodejs for Windows 於 062 版開始內建 npm使用 nodejsorg 官方提供的安裝程式不需要進一步的設定就可以立即使用 npm指令對於Windows的開發者來說大幅降低環境設定的問題與門檻

除了使用 Nodejs內建的 npm讀者也可以從 npm官方提供的以下網址

httpnpmjsorgdist

這是由 npm提供的 Fancy Windows Install版本請下載壓縮檔(例如npm-110-3zip)並將壓縮檔內容解壓縮至 Nodejs的安裝路徑(例如CProgram Filesnodejs)

解壓縮後在 Nodejs的安裝路徑下應該有以下的檔案及資料夾

bull npmcmd(檔案)

bull node modules(資料夾)

安裝於 Linux系統

Ubuntu Linux的使用者可以加入 NPM Unoffcial PPA這個 repository即可使用 apt-get完成 npm安裝

42 5 NPM套件管理工具

Nodejs中文電子書

Ubuntu Linux使用 apt-get安裝 npm

sudo apt-get install python-software-properties

sudo add-apt-repository ppagias-kay-leenpm

sudo apt-get update

sudo apt-get install npm

npm官方提供的安裝程式 installsh可以適用於大多數的 Linux系統使用這個安裝程式請先確認

1 系統已安裝 curl工具(請使用 curl --version查看版本訊息)

2 已安裝 Nodejs並且 PATH正確設置

3 Nodejs的版本必須大於 04x

以下為 npm提供的安裝指令

curl httpnpmjsorginstallsh | sh

安裝成功會看到如下訊息

installsh安裝成功的訊息

npm10105 homeUSERNAMElocalnodelibnode_modulesnpm

It worked

安裝於Mac OS X

建議採用與 Nodejs相同的方式進行 npm的安裝例如使用MacPorts安裝 Nodejs就同樣使用MacPorts安裝 npm這樣對日後的維護才會更方便容易

使用 MacPorts安裝 npm是本書比較建議的方式它可以讓 npm的安裝移除及更新工作自動化將會幫助開發者節省寶貴時間

51 安裝 NPM 43

Nodejs中文電子書

安裝MacPorts的提示

在MacPorts網站可以取得 OS X系統版本對應的安裝程式(例如 106或 107)httpwwwmacportsorg安裝過程會詢問系統管理者密碼使用預設的選項完成安裝即可安

裝MacPorts之後在終端機執行 port -v將會看到MacPorts的版本訊息

安裝 npm之前先更新MacPorts的套件清單以確保安裝的 npm是最新版本

sudo port -d selfupdate

接著安裝 npm

sudo port install npm

若讀者的 Nodejs並非使用MacPorts安裝則不建議使用MacPorts安裝npm因為 MacPorts會自動檢查並安裝相依套件而 npm相依 nodejs所以MacPorts也會一併將 nodejs套件安裝造成先前讀者使用其它方式安裝的 nodejs被覆蓋

讀者可以先使用 MacPorts安裝 curl(sudo port install curl)

再參考 Linux的 installsh安裝方式即可使用 npm官方提供的安裝程式

NPM安裝後測試

npm是指令列工具(command-line tool)使用時請先打開系統的文字終端機工具

測試 npm安裝與設定是否正確請輸入指令如下

npm -v

或是

npm --version

如果 npm已經正確安裝設定就會顯示版本訊息

44 5 NPM套件管理工具

Nodejs中文電子書

執行結果(範例)

110-2

52 使用 NPM安裝套件

npm目前擁有超過 6000種套件(packages)可以在 npm registry使用關鍵字搜尋套件

httpsearchnpmjsorg

舉例來說在關鍵字欄位輸入「coffee-script」下方的清單就會自動列出包含 coffee-script關鍵字的套件

接著我們回到終端機模式的操作npm的指令工具本身就可以完成套

件搜尋的任務

例如以下的指令同樣可以找出 coffee-script相關套件

npm search coffee-script

以下是搜尋結果的參考畫面

52 使用 NPM安裝套件 45

Nodejs中文電子書

找到需要的套件後(例如 express)即可使用以下指令安裝

npm install coffee-script

值得注意的一點是使用 npm install會將指定的套件安裝在工

作目錄(Working Directory)的 node modules資料夾下

以Windows為例如果執行 npm install的目錄位於

Cproject1

那麼 npm將會自動建立一個 node modules的子目錄(如果不存在)

Cproject1node modules

並且將下載的套件放置於這個子目錄例如

Cproject1node modulescoffee-script

這個設計讓專案可以個別管理相依的套件並且可以在專案佈署或發

行時將這些套件(位於 node modules)一併打包方便其它專案的使用者不必再重新下載套件

這個 npm install的預設安裝模式為 local(本地)只會變更當前專案的資料夾不會影響系統

另一種安裝模式稱為 global(全域)這種模式會將套件安裝到系統資

料夾也就是 npm安裝路徑的 node modules資料夾例如

CProgram Filesnodejsnode modules

46 5 NPM套件管理工具

Nodejs中文電子書

是否要使用全域安裝可以依照套件是否提供新指令來判斷舉例來說express 套件提供 express 這個指令而 coffee-script 則提供 coffee

指令

在 local 安 裝 模 式 中這 些 指 令 的 程 式 檔 案會 被 安 裝 到node modules的 bin這個隱藏資料夾下除非將bin的路徑加入 PATH環境變數否則要執行這些指令將會相當不便

為了方便指令的執行我們可以在 npm install 加上 -g 或 --

global參數啟用 global安裝模式例如

npm install -g coffee-script

npm install -g express

使用 global安裝模式需要注意執行權限的問題若權限不足可能會出現類似以下的錯誤訊息

npm ERR Error EACCES permission denied rsquorsquo

npm ERR

npm ERR Please try running this command again as rootAdministrator

要獲得足夠得執行權限請參考以下說明

bull Windows 7 或 2008 以上在「命令提示字元」的捷徑按右鍵選擇「以系統管理員身分執行」執行 npm指令時就會具有 Administrator身分

bull Mac OS X或 Linux系統可以使用 sudo指令例如

sudo npm install -g express

bull Linux系統可以使用 root權限登入或是以「sudo su -」切換成 root身分(使用 root權限操作系統相當危險因此並不建議使用這種方式)

若加上 -g參數使用 npm install -g coffee-script完成安裝

後就可以在終端機執行 coffee指令例如

coffee -v

52 使用 NPM安裝套件 47

Nodejs中文電子書

執行結果(範例)

CoffeeScript version 120

53 套件的更新及維護

除了前一節說明的 search 及 install 用法npm 還提供其他許多指令(commands)

使用 npm help可以查詢可用的指令

npm help

執行結果(部分)

where ltcommandgt is one of

adduser apihelp author bin bugs c cache completion

config deprecate docs edit explore faq find get

help help-search home i info init install la link

list ll ln login ls outdated owner pack prefix

prune publish r rb rebuild remove restart rm root

run-script s se search set show star start stop

submodule tag test un uninstall unlink unpublish

unstar up update version view whoami

使用 npm help command可以查詢指令的詳細用法例如

npm help list

接下來本節要介紹開發過程常用的 npm指令

使用 list可以列出已安裝套件

npm list

48 5 NPM套件管理工具

Nodejs中文電子書

執行結果(範例)

-- coffee-script120

- express256

- connect185

| -- formidable108

-- mime124

-- mkdirp007

-- qs041

檢視某個套件的詳細資訊例如

npm show express

升級所有套件(如果該套件已發佈更新版本)

npm update

升級指定的套件

npm update express

移除指定的套件

npm uninstall express

54 使用 packagejson

對於正式的 Nodejs專案可以建立一個命名為 packagejson的設

定檔(純文字格式)檔案內容參考範例如下

54 使用 packagejson 49

Nodejs中文電子書

packagejson(範例)

rdquonamerdquo rdquoapplication-namerdquo

rdquoversionrdquo rdquo001rdquo

rdquoprivaterdquo true

rdquodependenciesrdquo

rdquoexpressrdquo rdquo255rdquo

rdquocoffee-scriptrdquo rdquolatestrdquo

rdquomongooserdquo rdquogt= 253rdquo

其中 name與 version依照專案的需求設置

需要注意的是 dependencies的設定它用於指定專案相依的套件名

稱及版本

bull rdquoexpressrdquo rdquo255rdquo

代表此專案相依版本 255的 express套件

bull rdquocoffee-scriptrdquo rdquolatestrdquo

使用最新版的 coffee-script套件(每次更新都會檢查新版)

bull rdquomongooserdquo rdquogt= 253rdquo

使用版本大於 253的 mongoose套件

假設某個套件的新版可能造成專案無法正常運作就必須指定套件的

版本避免專案的程式碼來不及更新以相容新版套件通常在開發初期的

專案需要盡可能維持新套件的相容性(以取得套件的更新或修正)可以

用「gt=」設定最低相容的版本或是使用「latest」設定永遠保持最新

套件

50 5 NPM套件管理工具

6

Express介紹

在前面的 nodejs基礎當中介紹許多許多開設 http的使用方法及介紹以及許多基本的 nodejs基本應用

接下來要介紹一個套件稱為 express [Express](httpexpressjscom)這個套件主要幫忙解決許多 nodejs http server所需要的基本服務讓開發 httpservice變得更為容易不需要像之前需要透過層層模組(module)才有辦法開始編寫自己的程式

這個套件是由 TJ Holowaychuk 製作而成的套件裡面包含基本的路由處理 (route)http資料處理(GETPOSTPUT)另外還與樣板套件(jshtml template engine)搭配同時也可以處理許多複雜化的問題

61 Express安裝

安裝方式十分簡單只要透過之前介紹的 NPM就可以使用簡單的指令安裝指令如下

這邊建議需要將此套件安裝成為全域模組方便日後使用

51

Nodejs中文電子書

62 Express基本操作

express的使用也十分簡單先來建立一個基本的 hello world

var app = require(rsquoexpressrsquo)createServer()

port = 1337

applisten(port)

appget(rsquorsquo function(req res)

ressend(rsquohello worldrsquo)

)

consolelog(rsquostart express servernrsquo)

可以從上面的程式碼發現基本操作與 nodejs http的建立方式沒有太大差異主要差在當我們設定路由時可以直接透過 appget方式設定回應與接受方式

63 Express路由處理

Express對於 http服務上有許多包裝讓開發者使用及設定上更為方便例如有幾個路由設定那我們就統一藉由 appget來處理

Create http server

appget(rsquorsquo function(req res)

ressend(rsquohello worldrsquo)

)

appget(rsquotestrsquo function(req res)

ressend(rsquotest renderrsquo)

)

appget(rsquouserrsquo function(req res)

ressend(rsquouser pagersquo)

)

52 6 Express介紹

Nodejs中文電子書

如上面的程式碼所表示appget可以帶入兩個參數第一個是路徑名稱設定第二個為回應函式 (call back function)回應函式裡面就如同之前的 createServer方法裡面包含 requestresponse兩個物件可供使用使用者就可以透過瀏覽器輸入不同的 url切換到不同的頁面顯示不同的結果

路由設定上也有基本的配對方式讓使用者從瀏覽器輸入的網址可以

是一個變數只要符合型態就可以有對應的頁面產出例如

Create http server

appget(rsquouseridrsquo function(req res)

ressend(rsquouser rsquo + reqparamsid)

)

appget(rsquonumberrsquo function(req res)

ressend(rsquonumber rsquo + reqparamsnumber)

)

裡 面 使 用 到number 從 網 址 輸 入 之 後 就 可 以 直 接 使 用reqparamsnumber 取得所輸入的資料變成 url 參數使用當然前面也是可以加上路徑的設定userid在瀏覽器上路徑必須符合userxxx透

過 reqparamsid就可以取到 xxx這個字串值

另外express參數處理也提供了路由參數配對處理也可以透過正規表示法作為參數設定

var app = require(rsquoexpressrsquo)createServer()

port = 1337

applisten(port)

appget(^ip((d23)((d23))((d23))((d23))) function(req res)

ressend(reqparams)

)

上面程式碼可以發現後面路由設定的型態是正規表示法裡面設定

格式為ip之後必須要加上 ip型態才會符合資料格式同時取得 ip資料已經由正規表示法將資料做分群因此可以取得 ip的四個數字

此程式執行之後可以透過瀏覽器測試輸入網址為 localhost3000ip25525510010可以從頁面獲得資料

63 Express路由處理 53

Nodejs中文電子書

此章節全部範例程式碼如下

overview

author Caesar Chi

blog clonnblogspotcom

version 20120226

create server

var app = require(rsquoexpressrsquo)createServer()

port = 1337

applisten(port)

normal style

appget(rsquorsquo function(req res)

ressend(rsquohello worldrsquo)

)

appget(rsquotestrsquo function(req res)

ressend(rsquotest renderrsquo)

)

parameter style

appget(rsquouseridrsquo function(req res)

ressend(rsquouser rsquo + reqparamsid)

)

appget(rsquonumberrsquo function(req res)

ressend(rsquonumber rsquo + reqparamsnumber)

)

REGX style

appget(^ip((d23)((d23))((d23))) function(req res)

ressend(reqparams)

)

appget(rsquorsquo function(req res)

ressend(rsquoPage not foundrsquo 404)

54 6 Express介紹

Nodejs中文電子書

)

consolelog(rsquostart express servernrsquo)

64 Express middleware

Express 裡面有一個十分好用的應用概念稱為 middleware可以透過middleware做出複雜的效果同時上面也有介紹 next方法參數傳遞就是靠 middleware的概念來傳遞參數讓開發者可以明確的控制程式邏輯

create http server

appuse(expressbodyParser())

appuse(expressmethodOverride())

appuse(expresssession())

上面都是一種 middleware的使用方式透過 appuse方式裡面載入函式執行方法回應函式會包含三個基本參數responserequestnext其中next表示下一個 middleware執行函式同時會自動將預設三個參數繼續帶往下個函式執行底下有個實驗

上面的片段程式執行後開啟瀏覽器連結上 localhost1337會發現伺服器回應結果順序如下

first middle ware

second middle ware

execute middle ware

end middleware function

從上面的結果可以得知剛才設定的 middleware都生效了在 appuse設定的 middleware是所有 url皆會執行方法如果有指定特定方法就可以使用 appget的 middleware設定在 appget函式的第二個參數就可以帶入函式或者是匿名函式只要函式裡面最後會接受 request response next這三個參數同時也有正確指定 next函式的執行時機最後都會執行到最後一個方法當然開發者也可以評估程式邏輯要執行到哪一個階段讓邏輯

可以更為分明

64 Express middleware 55

Nodejs中文電子書

65 Express路由應用

在實際開發上可能會遇到需要使用參數等方式混和變數一起使用

express裡面提供了一個很棒的處理方法 appall這個方式可以先採用基本路由配對再將設定為每個不同的處理方式開發者可以透過這個方式簡

化自己的程式邏輯

overview

author

version 20120226

create server

var app = require(rsquoexpressrsquo)createServer()

port = 1337

users = [

name rsquoClonnrsquo

name rsquoChirsquo

]

applisten(port)

appall(rsquouseridoprsquo function(req res next)

requser = users[reqparamsid]

if (requser)

next()

else

next(new Error(rsquocannot find user rsquo + reqparamsid))

)

appget(rsquouseridrsquo function(req res)

ressend(rsquoviewing rsquo + requsername)

)

appget(rsquouserideditrsquo function(req res)

ressend(rsquoediting rsquo + requsername)

)

56 6 Express介紹

Nodejs中文電子書

appget(rsquouseriddeletersquo function(req res)

ressend(rsquodeleting rsquo + requsername)

)

appget(rsquorsquo function(req res)

ressend(rsquoPage not foundrsquo 404)

)

consolelog(rsquostart express servernrsquo)

內部宣告一組預設的使用者分別給予名稱設定藉由 appall這個方法可以先將路由雛形建立再接下來設定 appget的路徑格式只要符合格式就會分配進入對應的方法中像上面的程式當中如果使用者輸入路徑為user0除了執行 appall程式之後執行 next方法就會對應到路徑設定為userid的這個方法當中如果使用者輸入路徑為user0edit就會執行到useridedit的對應方法

66 Express GET應用範例

我們準備一個使用 GET方法傳送資料的表單

ltDOCTYPE htmlgt

lthtmlgt

ltheadgt

ltmeta http-equiv=rdquoContent-Typerdquo content=rdquotexthtml charset=utf8rdquogt

lttitlegtNodejs 菜鳥筆記 (1)lttitlegt

ltlink rel=rdquostylesheetrdquo href=rdquocssstylecssrdquo type=rdquotextcssrdquo media=rdquoallrdquo gt

ltheadgt

ltbodygt

lth1gtNodejs 菜鳥筆記-註冊lth1gt

ltform id=rdquosignuprdquo method=rdquoGETrdquo action=rdquohttplocalhost3000Signuprdquogt

ltlabelgt 使用者名稱ltlabelgtltinput type=rdquotextrdquo id=rdquousernamerdquo name=rdquousernamerdquo gtltbrgt

ltlabelgt 電子郵件ltlabelgtltinput type=rdquotextrdquo id=rdquoemailrdquo name=rdquoemailrdquo gtltbrgt

ltinput type=rdquosubmitrdquo value=rdquo 註冊我的帳號rdquo gtltbrgt

ltformgt

ltbodygt

lthtmlgt

66 Express GET應用範例 57

Nodejs中文電子書

這個表單沒有什麼特別的地方我們只需要看第 9 行form 使用的method是 GET然後 action是rdquohttplocalhost3000Signupldquo等一下我們要來撰寫Signup這個 URL Path的處理程式

處理 Signup行為

我們知道所謂的 GET方法會透過 URL來把表單的值給帶過去以上面的表單來說到時候 URL會以這樣的形式傳遞

httplocalhost3000Signupusername=xxxampemail=xxx

所以要能處理這樣的資料必須有以下功能

bull 解析 URL

bull 辨別動作是 Signup

bull 解析出 username和 email

一旦能取得 username和 email的值程式就能加以應用了

處理 Signup的程式碼雛形

load module

var url = require(rsquourlrsquo)

urlData = urlparse(requrltrue)

action = urlDatapathname

reswriteHead(200 rdquoContent-Typerdquordquotexthtml charset=utf-8rdquo)

if (action === rdquoSignuprdquo)

user = urlDataquery

resend(rdquolth1gtrdquo + userusername + rdquo 歡迎您的加入 lth1gtltpgt 我們已經將會員啟用信寄至rdquo + useremail + rdquoltpgtrdquo)

首先需要加載 url module它是用來協助我們解析 URL的模組接著使用 urlparse方法第一個傳入 url字串變數也就是 requrl另外第二個參數的用意是設為 ture則引進 querystring模組來協助處理預設是 false它影響到的是 urlDataquery設為 true會傳回物件不然就只是一般的字串urlparse會將字串內容整理成一個物件我們把它指定給 urlData

action變數作為記錄 pathname這是我們稍後要來判斷目前網頁的動作是什麼接著先將 html表頭資訊 (Header)準備好再來判斷路徑邏輯如

58 6 Express介紹

Nodejs中文電子書

果是 Signup這個動作就把 urlDataquery裡的資料指定給 user然後輸出userusername和 useremail把使用者從表單註冊的資料顯示於頁面中

最後進行程式測試啟動 nodejs主程式之後開啟瀏覽器就會看到表單填寫完畢按下送出就可以看到結果了

完整 nodejs程式碼如下

var http = require(rsquohttprsquo)

url = require(rsquourlrsquo)

fs = require(rdquofsrdquo)

server

server = httpcreateServer(function (reqres)

var urlData

encode = rdquoutf8rdquo

filePath = rdquoviewexpress_get_example_formhtmlrdquo

action

urlData = urlparse(requrltrue)

action = urlDatapathname

reswriteHead(200 rdquoContent-Typerdquordquotexthtml charset=utf-8rdquo)

if (action === rdquoSignuprdquo)

user = urlDataquery

resend(rdquolth1gtrdquo + userusername + rdquo 歡迎您的加入 lth1gtltpgt 我們已經將會員啟用信寄至rdquo + useremail + rdquoltpgtrdquo)

else

fsreadFile(filePath encode function(err file)

reswrite(file)

resend()

)

)

serverlisten(3000)

consolelog(rsquoServer 跑起來了現在時間是rsquo + new Date())

66 Express GET應用範例 59

Nodejs中文電子書

67 Express POST應用範例

一開始準備基本的 html表單傳送內容以 POST方式form的 action屬性設定為 POST其餘 html內容與前一個範例應用相同

ltDOCTYPE htmlgt

lthtmlgt

ltheadgt

ltmeta http-equiv=rdquoContent-Typerdquo content=rdquotexthtml charset=utf8rdquogt

lttitlegtNodejs 菜鳥筆記 (1)lttitlegt

ltlink rel=rdquostylesheetrdquo href=rdquocssstylecssrdquo type=rdquotextcssrdquo media=rdquoallrdquo gt

ltheadgt

ltbodygt

lth1gtNodejs 菜鳥筆記-註冊lth1gt

ltform id=rdquosignuprdquo method=rdquoPOSTrdquo action=rdquohttplocalhost3000Signuprdquogt

ltlabelgt 使用者名稱ltlabelgtltinput type=rdquotextrdquo id=rdquousernamerdquo name=rdquousernamerdquo gtltbrgt

ltlabelgt 電子郵件ltlabelgtltinput type=rdquotextrdquo id=rdquoemailrdquo name=rdquoemailrdquo gtltbrgt

ltinput type=rdquosubmitrdquo value=rdquo 註冊我的帳號rdquo gtltbrgt

ltformgt

ltbodygt

lthtmlgt

nodejs的程式處理邏輯與前面 GET範例類似部分程式碼如下

qs = require(rsquoquerystringrsquo)

if (action === rdquoSignuprdquo)

formData = rsquorsquo

reqon(rdquodatardquo function (data)

formData += data

)

reqon(rdquoendrdquo function ()

user = qsparse(formData)

resend(rdquolth1gtrdquo + userusername + rdquo 歡迎您的加入 lth1gtltpgt 我們已經將會員啟用信寄至rdquo + useremail + rdquoltpgtrdquo)

)

60 6 Express介紹

Nodejs中文電子書

主要加入了rsquoquerystringrsquo這個moduel方便我們等一下解析由表單 POST回來的資料另外加入一個 formData的變數用來搜集待等一下表單回傳的資料前面的 GET範例我們只從 req拿出 url的資料這次要在利用req身上的事件處理

JavaScript 在訂閱事件時使用 addEventListener而 nodejs 使用的則是on這邊加上了監聽 data的事件會在瀏覽器傳送資料到Web Server時被執行參數是它所接收到的資料型態是字串

接著再增加 end的事件當瀏覽器的請求事件結束時它就會動作

由於瀏覽器使用 POST在上傳資料時會將資料一塊塊地上傳因為我們在監聽 data事件時透過 formData變數將它累加起來lt不過由於我們

上傳的資料很少一次就結束不過如果日後需要傳的是資料比較大的檔

案這個累加動作就很重要

當資料傳完就進到 end 事件中會用到 qsparse 來解析 formDataformData的內容是字串內容是

username=wordsmithampemail=wordsmith40somewhere

而 qsparse可以幫我們把這個 querystring轉成物件的格式也就是

username=wordsmithampemail=wordsmith40somewhere

一旦轉成物件並指定給 user之後其他的事情就和 GET方法時操作的一樣寫 response的表頭將內容回傳並將 userusername和 useremail代入到內容中

修改完成後接著執行 nodejs程式啟動 web server開啟瀏覽器進入表單測試看看POST的方式能否順利運作

完整程式碼如下

var http = require(rsquohttprsquo)

url = require(rsquourlrsquo)

fs = require(rdquofsrdquo)

qs = require(rsquoquerystringrsquo)

server

server = httpcreateServer(function (reqres)

var urlData

67 Express POST應用範例 61

Nodejs中文電子書

encode = rdquoutf8rdquo

filePath = rdquoviewexpress_post_example_formhtmlrdquo

formData

action

urlData = urlparse(requrltrue)

action = urlDatapathname

reswriteHead(200 rdquoContent-Typerdquordquotexthtml charset=utf-8rdquo)

if (action === rdquoSignuprdquo)

formData = rsquorsquo

reqon(rdquodatardquo function (data)

formData += data

)

reqon(rdquoendrdquo function ()

user = qsparse(formData)

resend(rdquolth1gtrdquo + userusername + rdquo 歡迎您的加入 lth1gtltpgt 我們已經將會員啟用信寄至rdquo + useremail + rdquoltpgtrdquo)

)

else

fsreadFile(filePath encode function(err file)

reswrite(file)

resend()

)

)

serverlisten(3000)

consolelog(rsquoServer 跑起來了現在時間是rsquo + new Date())

62 6 Express介紹

Nodejs中文電子書

68 Express AJAX應用範例

在 Nodejs要使用 Ajax傳送資料並且與之互動在接受資料的部份沒有太大的差別client端不是用 GET就是用 POST來傳資料重點在處理完後用 JSON格式回傳當然 Ajax不見得只傳 JSON格式有時是回傳一段 HTML碼不過後者對伺服器來說基本上就和前兩篇沒有差別了所以我們還是以回傳 JSON做為這一回的主題

這一回其實大多數的工作都會落在前端 Ajax上面前端要負責發送與接收資料並在接收資料後撤掉原先發送資料的表單並將取得的資料

改成 HTML格式之後放上頁面

首先先準備 HTML靜態頁面資料

ltDOCTYPE htmlgt

lthtmlgt

ltheadgt

lttitlegtNodejs 菜鳥筆記 (3)lttitlegt

ltlink rel=rdquostylesheetrdquo href=rdquocssstylecssrdquo type=rdquotextcssrdquo media=rdquoallrdquo gt

ltscript src=rdquohttpsajaxgoogleapiscomajaxlibsjquery171jqueryminjsrdquogtltscriptgt

ltheadgt

ltbodygt

lth1gtNodejs 菜鳥筆記-註冊 (用 Ajax)lth1gt

ltform id=rdquosignuprdquo action=rdquoSignuprdquo method=rdquoPOSTrdquogt

ltlabelgt 使用者名稱ltlabelgtltinput type=rdquotextrdquo id=rdquousernamerdquo name=rdquousernamerdquo gtltbrgt

ltlabelgt 電子郵件ltlabelgtltinput type=rdquotextrdquo id=rdquoemailrdquo name=rdquoemailrdquo gtltbrgt

ltinput type=rdquosubmitrdquo value=rdquo 註冊我的帳號rdquo gtltbrgt

ltformgt

ltdiv id=rdquoresultrdquogt

ltdivgt

ltscript type=rdquotextjavascriptrdquogt

$(function()$(rsquosignup input[type=rdquosubmitrdquo]rsquo)click(function(e)

var user =

userusername = $(rdquousernamerdquo)val()useremail = $(rdquoemailrdquo)val()

$post(rdquoSignuprdquouserfunction(data)greet(data)

)

68 Express AJAX應用範例 63

Nodejs中文電子書

epreventDefault()

)

var greet = function(msg)

var resultNode = $(rsquoresultrsquo)greeting = $(rsquolth2gtHirsquo+msgusername+rsquo 你的會員 id 是rsquo+ msgid +rsquo 我們會將會員啟動信寄至rsquo+msgemail+rsquolth2gtrsquo)

$(rsquosignuprsquo)hide()resultNodehtml(rdquordquo)

resultNodeappend(greeting)

)

ltscriptgt

ltbodygt

lthtmlgt

HTML頁面上準備了一個表單用來傳送註冊資料接著直接引用了Google CDN來載入 jQuery用來幫我們處理 Ajax的工作這次要傳送和接收的工作很大的變動都在 HTML頁面上的 JavaScript當中我們要做的事有 (相關 jQuery處理這邊不多做贅述指提起主要功能解說)

bull 用 jQUery取得 submit按鈕綁定它的 click動作

bull 取得表單 username和 email的值存放在 user這個物件中

bull 用 jQuery的 $post方法將 user的資料傳到 Server

bull 一旦成功取得資料後透過 greet這個 function組成回報給 user的訊息

bull 清空原本給使用者填資料的表單

bull 將 Server回傳的 usernameemail和 id這 3個資料組成回應的訊息

bull 將訊息放到原本表單的位置

經過以上的處理後一個 Ajax的表單的基本功能已經完成

接著進行 nodejs主要程式的編輯部分程式碼如下

var fs = require(rdquofsrdquo)

64 6 Express介紹

Nodejs中文電子書

qs = require(rsquoquerystringrsquo)

if (action === rdquoSignuprdquo)

formData = rsquorsquo

reqon(rdquodatardquo function (data)

formData += data

)

reqon(rdquoendrdquo function ()

var msg

user = qsparse(formData)

userid = rdquo123456rdquo

msg = JSONstringify(user)

reswriteHead(200 rdquoContent-Typerdquordquoapplicationjson charset=utf-8rdquordquoContent-Lengthrdquomsglength)

resend(msg)

)

這裡的程式和前面 POST範例基本上大同小異差別在

bull 幫 user的資料加上 id隨意存放一些文字進去讓 Server回傳的資料多於 Client端傳上來的不然會覺得 Server都沒做事

bull 增加了 msg 這個變數存放將 user 物件 JSON 文字化的結果JSONstringify這個轉換函式是 V8引擎所提供的如果你好奇的話

bull 大重點來了我們要告訴 Client端這次回傳的資料格式是 JSON所在 Content-type和 Content-Length要提供給 Client

Server很輕鬆就完成任務了最後進行程式測試啟動 nodejs主程式之後開啟瀏覽器就會看到表單填寫完畢按下送出就可以看到結果了

最後 nodejs本篇範例程式碼如下

var http = require(rsquohttprsquo)

url = require(rsquourlrsquo)

fs = require(rdquofsrdquo)

qs = require(rsquoquerystringrsquo)

server

68 Express AJAX應用範例 65

Nodejs中文電子書

server = httpcreateServer(function (reqres)

var urlData

encode = rdquoutf8rdquo

filePath = rdquoviewexpress_ajax_example_formhtmlrdquo

formData

action

urlData = urlparse(requrltrue)

action = urlDatapathname

if (action === rdquoSignuprdquo)

formData = rsquorsquo

reqon(rdquodatardquo function (data)

formData += data

)

reqon(rdquoendrdquo function ()

var msg

user = qsparse(formData)

userid = rdquo123456rdquo

msg = JSONstringify(user)

reswriteHead(200 rdquoContent-TyperdquordquoapplicationjsonrdquordquoContent-Lengthrdquomsglength)

resend(msg)

)

else

fsreadFile(filePath encode function(err file)

reswriteHead(200 rdquoContent-Typerdquordquotexthtml charset=utf-8rdquo)

reswrite(file)

resend()

)

)

serverlisten(3000)

66 6 Express介紹

Nodejs中文電子書

consolelog(rsquoServer 跑起來了現在時間是rsquo + new Date())

69 原始資料提供

bull [NodeJS初學者筆記 (1)-用 GET傳送資料] (httpithelpithomecomtwquestion10087402)

bull [NodeJS初學者筆記 (2)-用 POST傳送資料] (httpithelpithomecomtwquestion10087489)

bull [NodeJS 初學者筆記 (3)-用 Ajax 傳送資料] (httpithelpithomecomtwquestion10087627)

69 原始資料提供 67

Nodejs中文電子書

68 6 Express介紹

7

CoffeeScript

bull Spine

bull Hem

bull Spineapp

Letrsquos Have a Cup of CoffeeScript

bull es5-shim ECMAScript 5 compatibility shims for legacy JavaScript engines

ESnext

node-iform - A middleware for node-validator httpsgithubcomguileennode-iform

69

Nodejs中文電子書

70 7 CoffeeScript

8

製作一個 Hubot的 Plurk Adapter

81 應用事項提醒

此應用範例以CoffeeScript編寫主要程式架構採用Github釋放的Hubot專案以下有幾個主要注意事項請讀者注意

bull Hubot 一開始的架構除了內建的兩個 Adapter 之外其餘都要以Nodejs的Module方式才能運作

bull Hubot的 binhubot裡面寫著 npm install所以不管你怎麼改原始碼也不能改變第一點的狀況

其實上述都在討論同一件事情NodeJS的模組而且模組不能設定相對路徑之類的來安裝一定要包裝成 npm相符和格式才有辦法正確執行

82 建立 Adapter

首先需要準備兩個檔案

bull packagejson

bull plurkcoffee

71

Nodejs中文電子書

有這兩個就足以變成 NodeJS的模組了在開始 Coding之前先來設定一下 packagejson相依性

rdquonamerdquo rdquohubot-plurkrdquo

rdquoversionrdquo rdquo011rdquo

rdquomainrdquo rdquoplurkrdquo

rdquodependenciesrdquo

rdquohubotrdquo rdquogt=205rdquo

rdquooauthrdquo rdquordquo

rdquocronrdquordquordquo

這邊會使用到NodeJS的 cron模組(Module)而登入 Plurk需要OAuth標準授權才行所以也需要加載 OAuth模組

注意Hubot在讀取非內建模組時會自動在前面加上 hubot-的前置

83 建立 Robot跟 API

本篇應用基本上程式碼大部分參考 Hubot - Twitter Adapter來製作主要差異只有在使用 OAuth這個模組Twitter有 Streaming可用而 Plurk則得用 Comet方式來達到即時讀取

plurkcoffee程式碼

Robot = require(rdquohubotrdquo)robot()

Adapter = require(rdquohubtordquo)adapter()

EventEmitter = require(rdquoeventsrdquo)EventEmitter

oauth = require(rdquooauthrdquo)

cronJob = require(rdquocronrdquo)CronJob

class Plurk exntends Adapter

class PlurkStreaming exnteds EventEmitter

72 8 製作一個 Hubot的 Plurk Adapter

Nodejs中文電子書

先弄個基本架構主要 Class皆參考 Twitter Adapter命名方式接著再將 Plurk這個 Class增加幾個Method基本上只要有 run send reply就夠了而 run用來做初始化的部分

class Plurk entends Adapter

send (plurk id strings⋯) -gt

reply (plurk id strings⋯) -gt

run -gt

看起來有點東西接著來處理主要的 Plurk API結合部分

class PlurkStreaming extends EventEmitter

consuctor (options) -gt

plurk (callback) -gt

觀察河道

getChannel -gt

取得 Comet 網址

reply (plurk id message) -gt

回噗

acceptFriends -gt

接受好友

get (path callback) -gt

GET 請求

post (path body callback)-gt

POST 請求(其實是裝飾)

request (method path body callback)-gt

主要的 OAuth 請求

comet (server callback)-gt

噗浪的 Comet 傳回是 JavaScript Callback 要另外處理後才會變成 JSON

然後把注意力集中到 constructor上先把建構子弄好

constructor (options) -gt

super()

if optionskey and optionssecret and optionstoken and optionstoken secret

key = optionskey

secret = optionssecret

token = optionstoken

token secret = optionstoken secret

83 建立 Robot跟 API 73

Nodejs中文電子書

建立 OAuth 連接

consumer = new oauthOAuth(

rdquohttpwwwplurkcomOAuthrequest tokenrdquo

rdquohttpwwwplurkcomOAuthaccess tokenrdquo

key

secret

rdquo10rdquo

rdquohttpwwwplurkcomOAuthauthorizerdquo

rdquoHMAC-SHA1rdquo

)

domain = rdquowwwplurkcomrdquo

初始化取得 Comet 網址

do getChannel

else

throw new Error(rdquo 參數不足需要 Key Secret Token Token Secretrdquo)

接著來處理 request這個 method

request (method path body callback) -gt

記錄一下這次的 Request

consolelog(rdquohttpdomainpathrdquo)

Callback 這邊先不丟進去要用另一種方式處理

request = consumerget(rdquohttpdomainpathrdquo token token secret null)

requeston rdquoresponserdquo (res) -gt

reson rdquodatardquo (chunk) -gt

parseResponse(chunk+rsquorsquo callback)

reson rdquoendrdquo (data) -gt

consolelog rdquoEnd Request pathrdquo

reson rdquoerrorrdquo (data) -gt

consolelog rdquoError rdquo + data

requestend()

處理資料

parseResponse = (data callback) -gt

if datalength gt 0

用 TryCatch 避免處理 JSON 出錯導致整個中斷

try

callback null JSONparse(data)

catch err

74 8 製作一個 Hubot的 Plurk Adapter

Nodejs中文電子書

consolelog(rdquoError Parse JSONrdquo + data err)

繼續執行

callback null data ||

大致上就是這樣上面程式的架構已經將整個 Hubot Plurk Adapter完成因為在測試時竟然因為噗浪 Lag而沒讀到完整的 Comet資料然後造成程式異常為了避免這個問題發生因此需要加上為了完美呈現需要再

加上 Comet的處理所以要使用到 EventEmitter的功能

comet (server callback) -gt

在 Callback 裡面會找不到自身所以設定區域變數

self =

記錄一下這次的 Request

consolelog(rdquo[Comet] serverrdquo)

Callback 這邊先不丟進去要用另一種方式處理

request = consumerget(rdquohttpdomainpathrdquo token token secret null)

requeston rdquoresponserdquo (res) -gt

reson rdquodatardquo (chunk) -gt

parseResponse(chunk+rsquorsquo callback)

reson rdquoendrdquo (data) -gt

consolelog rdquoEnd Request pathrdquo

請求結束發出事件通知可以進行下一次請求

selfemit rdquonextPlurkrdquo

reson rdquoerrorrdquo (data) -gt

consolelog rdquoError rdquo + data

requestend()

處理資料

parseResponse = (data callback) -gt

if datalength gt 0

用 trycatch 避免失敗中斷

try

去掉 JavaScript 的 Callback

data = datamatch(CometChannelscriptCallback((+))s)

jsonData = rdquordquo

if data

83 建立 Robot跟 API 75

Nodejs中文電子書

jsonData = JSONparse(data[1])

else

如果沒有任何 Match 嘗試直接 parse

jsonData = JSONparse(data)

catch err

consolelog(rdquo[Comet] Errorrdquo data err)

用 TryCatch 避免處理 JSON 出錯導致整個中斷

try

只傳入 json 的 data 部分

callback null jsonDatadata

catch err

consolelog(rdquo[Comet]Error Parse JSONrdquo + data err)

繼續執行

callback null data ||

後面的 get跟 post就簡單多了

get (path callback) -gt

request(rdquoGETrdquo path null callback)

post (path body callback) -gt

request(rdquoPOSTrdquo path body callback)

接著處理取的 Comet網址的 getChannel

getChannel -gt

self =

get rdquoAPPRealtimegetUserChannelrdquo (error data) -gt

if error

檢查是否有 comet server

if datacomet server

selfchannel = datacomet server

如果沒有 Channel Ready 就嘗試連接會失敗

selfemit(rsquochannel readyrsquo)

那麼先來處理 Plurk Adaper好處理的部份

send (plruk id strings⋯)-gt

跟 Reply 一樣直接交給 reply 做

reply plurk id strings⋯

76 8 製作一個 Hubot的 Plurk Adapter

Nodejs中文電子書

reply (plurk id strings⋯) -gt

stringsforEach (message) =gt

botreply(plruk id message)

接著把 run處理好就可以上線運作摟

run -gt

self =

options =

key processenvHUBOT PLURK KEY

secret processenvHUBOT PLURK SECRET

token processenvHUBOT PLURK TOKEN

token secret processenvHUBOT PLURK TOKEN SECRET

創建剛剛的 API

bot = new PlurkStreaming(options)

依照 Twitter 的 new RobotTextMessage 會沒有反應所以參考 hubot-minecraft 的方式

r = robotconstructor

處理噗浪河道訊息

doPlurk = (data)-gt

檢查是否為回噗

if dataresponse

datacontent raw = dataresponsecontent raw

datauser id = dataresponseuser id

確定有噗浪 ID 跟訊息

if dataplurk id and datacontent raw

selfreceive new rTextMessage(dataplurk id datacontent raw)

取得 Comet Server 完成開始第一次 Comet 連接

boton rdquochannel readyrdquo () -gt

botplurk selfdoPlurk

上一次 Comet 完成繼續 Polling

boton rdquonextPlurkrdquo ()-gt

botplurk selfdoPlurk

定時接受好友邀請

do botacceptFriends

bot = bot

83 建立 Robot跟 API 77

Nodejs中文電子書

終於完成 AdapterHubot專案裡面的 scripts資料夾內是互動部分不需要像 Adapter如此大費周章處理只新增檔案並且設計好對白之後就會回噗了我開發用的機器人在此大家可以去跟他玩玩[httpplurkcomelct9620 bot](httpplurkcomelct9620 bot)

84 原始資料提供

bull [製 作 一 個 Hubot 的 噗 浪 Adapter](http revo-skillfrosttw blog20120318create-a-hubot-plurk-adapter)

78 8 製作一個 Hubot的 Plurk Adapter

9

Nodejs好用工具介紹

91 logio

Real-time log monitoring in your browser

httplogioorg

nodejs (gt=0412 lt=0611)

npm config set unsafe-perm true

npm install -g --prefix=usrlocal logio

logio server start

httplocalhost8998

79

Nodejs中文電子書

80 9 Nodejs好用工具介紹

10

精選文章

101 Express

bull 使用 NodeJS + Express從 GETPOST Request取值 [邀稿中]

bull link2 [邀稿中]

bull link3 [邀稿中]

81

Nodejs中文電子書

82 10 精選文章

11

參考資源

111 Nodejs書籍

bull Programming Nodejs April 2012 (est) Travis Swicegood Pragmatic Bookshelf

bull e Little Book on CoffeeScript January 2012 Alex MacCaw OrsquoReilly Media

bull Node for Front-End Developers January 2012 Garann Means OrsquoReilly Media

bull Building Hypermedia APIs with HTML5 and Node November 2011 MikeAmundsen OrsquoReilly Media

bull Node Web Development August 2011 David Herron PacktPub

bull CoffeeScript Accelerated JavaScript Development July 2011 Trevor BurnhamPragmatic Bookshelf

bull Getting Started with GEO CouchDB and Nodejs July 2011 Mick omp-son OrsquoReilly Media

bull What Is Node July 2011 Brett McLaughlin OrsquoReilly Media

bull Node Up and Running May 2011 Tom Hughes-Croucher Mike WilsonOrsquoReilly Media

83

Nodejs中文電子書

bull Hands-on Nodejs e Nodejs introduction and API reference May 2011 Pe-dro Teixeira LeanPub

bull e Node Beginner Book Manuel Kiessling

bull Mastering Nodejs visionmedia

112 Nodejs影音教學

bull e Node Sessions e Best of OSCON 2011 August 2011 OrsquoReilly Media(Video)

bull Tom Hughes-Croucher on Node March 2011 OrsquoReilly Media (Video)

113 Nodejs教學網站

bull Node Tuts

114 Nodejs課程

bull Introduction To Nodejs Van Nguyen CodeLesson

84 11 參考資源

Page 41: Nodejs Wiki

Nodejs中文電子書

lth1gtnodejs index html filelth1gt

ltbodygt

lthtmlgt

另一個為 nodejs程式

var fs = require(rdquofsrdquo)

filename = rdquostaticindexhtmlrdquo

encode = rdquoutf8rdquo

fsreadFile(filename encode function(err file)

consolelog(file)

)

一開始直接載入 le system模組 載入名稱為 fs讀取檔案主要使

用的方法為 readFile裡面以三個參數路徑 ( le path) 編碼方式 (encoding)

回應函式 (callback)路徑必須要設定為靜態 html所在位置才能指定到正確的檔案靜態檔案的編碼方式也必須正確這邊使用靜態檔案的編

碼為 utf8如果編碼設定錯誤nodejs讀取出來檔案結果會使用 byte raw格式輸出如果錯誤編碼格式會導致輸出資料為 byte raw

回應函式中裡面會使用兩個變數error為錯誤資訊如果讀取的檔案不存在或者發生錯誤error數值會是 true如果成功讀取資料 error將會是 falsecontent則是檔案內容資料讀取後將會把資料全數丟到 content這個變數當中

最後程式的輸出結果畫面如下

43 nodejs檔案讀取 35

Nodejs中文電子書

44 nodejs http靜態檔案輸出

前面已經了解如何讀取本地端檔案接下來將配合 http伺服器路由讓每個路由都能夠輸出相對應的靜態 html檔案首先新增加幾個靜態 html檔案

ltDOCTYPE htmlgt

lthtml xmlns=rdquohttpwwww3org1999xhtmlrdquo xmllang=rdquozh-CNrdquo lang=rdquozh-CNrdquogt

ltheadgt

ltmeta http-equiv=rdquoContent-Typerdquo content=rdquotexthtml charset=UTF-7rdquo gt

lttitlegtnodejs index html filelttitlegt

ltheadgt

ltbodygt

lth1gtnodejs index html filelth1gt

ltbodygt

lthtmlgt

ltDOCTYPE htmlgt

lthtml xmlns=rdquohttpwwww3org1999xhtmlrdquo xmllang=rdquozh-CNrdquo lang=rdquozh-CNrdquogt

ltheadgt

ltmeta http-equiv=rdquoContent-Typerdquo content=rdquotexthtml charset=UTF-7rdquo gt

lttitlegtnodejs test html filelttitlegt

ltheadgt

ltbodygt

lth1gtnodejs test html filelth1gt

ltbodygt

lthtmlgt

ltDOCTYPE htmlgt

lthtml xmlns=rdquohttpwwww3org1999xhtmlrdquo xmllang=rdquozh-CNrdquo lang=rdquozh-CNrdquogt

ltheadgt

ltmeta http-equiv=rdquoContent-Typerdquo content=rdquotexthtml charset=UTF-7rdquo gt

lttitlegtnodejs static html filelttitlegt

ltheadgt

ltbodygt

lth1gtnodejs static html filelth1gt

ltbodygt

lthtmlgt

36 4 Nodejs基礎

Nodejs中文電子書

準備一個包含基本路由功能的 http伺服器

var server

ip = rdquo127001rdquo

port = 1337

http = require(rsquohttprsquo)

url = require(rsquourlrsquo)

server = httpcreateServer(function (req res)

var path = urlparse(requrl)

)

serverlisten(port ip)

consolelog(rdquoServer running at httprdquo + ip + rdquordquo + port)

加入 le system 模組使用 readFile 的功能將這一段程式放置於

createServer的回應函式中

fsreadFile(filePath encode function(err file)

)

readFile的回應函式裡面加入頁面輸出讓瀏覽器可以正確讀到檔案在這邊我們設定讀取的檔案為 html 靜態檔案所以 Content-type 設定為texthtml讀取到檔案的內容將會正確輸出成 html靜態檔案

fsreadFile(filePath encode function(err file)

reswriteHead(200 rsquoContent-Typersquo rsquotexthtmlrsquo)

reswrite(file)

resend()

)

到這邊為止基本的程式內容都已經完成剩下一些細節的調整首先

路徑上必須做調整目前的靜態檔案全部都放置於 static資料夾底下設定一個變數來記住資料夾位置

接著將瀏覽器發出要求路徑與資料夾組合讀取正確 html靜態檔案使用者有可能會輸入錯誤路徑所以在讀取檔案的時候要加入錯誤處理

同時回應 404伺服器無法正確回應的 http header格式

加入這些細節的修改一個基本的 http 靜態 html輸出伺服器就完成了完整程式碼如下

44 nodejs http靜態檔案輸出 37

Nodejs中文電子書

var server

ip = rdquo127001rdquo

port = 1337

http = require(rsquohttprsquo)

fs = require(rdquofsrdquo)

folderPath = rdquostaticrdquo

url = require(rsquourlrsquo)

path

filePath

encode = rdquoutf8rdquo

server = httpcreateServer(function (req res)

path = urlparse(requrl)

filePath = folderPath + pathpathname

fsreadFile(filePath encode function(err file)

if (err)

reswriteHead(404 rsquoContent-Typersquo rsquotextplainrsquo)

resend()

return

reswriteHead(200 rsquoContent-Typersquo rsquotextapplicationrsquo)

reswrite(file)

resend()

)

)

serverlisten(port ip)

consolelog(rdquoServer running at httprdquo + ip + rdquordquo + port)

45 nodejs http GET資料擷取

http伺服器中除了路由之外另一個最常使用的方法就是擷取 GET資料本單元將會介紹如何透過基本 http伺服器擷取瀏覽器傳來的要求擷取 GET資料

在 http協定中GET參數都是藉由 URL從瀏覽器發出要求送至伺服器

38 4 Nodejs基礎

Nodejs中文電子書

端基本的傳送網址格式可能如下

http127001testsend=1amptest=2

上面這段網址裡面的 GET參數就是 send而這個變數的數值就為 1如果想要在 http伺服器取得 GET資料需要在瀏覽器給予的要求 (request)做處理

首先需要載入 query string這個模組這個模組主要是用來將字串資料

過濾後轉換成javascript物件

qs = require(rsquoquerystringrsquo)

接著在第一階段利用 url模組過濾瀏覽器發出的 URL資料後將回應的物件裡面的 query這個變數是一個字串值資料過濾後如下

send=1amptest=2

透過 query string使用 parse這個方法將資料轉換成 javascript物件就表示 GET的資料已經被伺服器端正式擷取下來

path = urlparse(requrl)

parameter = qsparse(pathquery)

整個 nodejs http GET參數完整擷取程式碼如下

var server

ip = rdquo127001rdquo

port = 1337

http = require(rsquohttprsquo)

qs = require(rsquoquerystringrsquo)

url = require(rsquourlrsquo)

server = httpcreateServer(function (req res)

var path = urlparse(requrl)

parameter = qsparse(pathquery)

consoledir(parameter)

reswriteHead(200 rsquoContent-Typersquo rsquotextplainrsquo)

reswrite(rsquoBrowser test GET parameternrsquo)

resend()

)

45 nodejs http GET資料擷取 39

Nodejs中文電子書

serverlisten(port ip)

consolelog(rdquoServer running at httprdquo + ip + rdquordquo + port)

程式運作之後由瀏覽器輸入要求網址之後nodejs伺服器端回應資料為

Server running at http1270011337

send rsquo1rsquo test rsquo1rsquo

46 本章結語

前面所解說的部份一大部分主要是處理 http伺服器基本問題雖然在某些部分有牽扯到 http 伺服器基本運作原理主要還是希望可以藉由這些基本範例練習 nodejs練習回應函式與語法串接的特點習慣編寫javascript風格程式當然直接這樣開發 nodejs是非常辛苦的接下來在模組實戰開發的部份將會介紹特定的模組一步一步帶領各位從無到有進行

nodejs應用程式開發

40 4 Nodejs基礎

5

NPM套件管理工具

npm全名為 Node Package Manager是 Nodejs的套件(package)管理工具類似 Perl的 ppm或 PHP的 PEAR等安裝 npm後使用npm install

module name指令即可安裝新套件維護管理套件的工作會更加輕鬆

npm可以讓Nodejs的開發者直接利用擴充線上的套件庫(packagesregistry)加速軟體專案的開發npm提供很友善的搜尋功能可以快速找到安裝需要的套件當這些套件發行新版本時npm也可以協助開發者自動更新這些套件

npm不僅可用於安裝新的套件它也支援搜尋列出已安裝模組及更新的功能

51 安裝 NPM

Nodejs在 063版本開始內建 npm讀者安裝的版本若是此版本或更新的版本就可以略過以下安裝說明

若要檢查 npm是否正確安裝可以使用以下的指令

npm -v

41

Nodejs中文電子書

執行結果說明

若 npm正確安裝執行 npm -v將會看到類似 110-2的版本訊息

若讀者安裝的 Nodejs版本比較舊或是有興趣嘗試自己動手安裝 npm工具則可以參考以下的說明

安裝於Windows系統

Nodejs for Windows 於 062 版開始內建 npm使用 nodejsorg 官方提供的安裝程式不需要進一步的設定就可以立即使用 npm指令對於Windows的開發者來說大幅降低環境設定的問題與門檻

除了使用 Nodejs內建的 npm讀者也可以從 npm官方提供的以下網址

httpnpmjsorgdist

這是由 npm提供的 Fancy Windows Install版本請下載壓縮檔(例如npm-110-3zip)並將壓縮檔內容解壓縮至 Nodejs的安裝路徑(例如CProgram Filesnodejs)

解壓縮後在 Nodejs的安裝路徑下應該有以下的檔案及資料夾

bull npmcmd(檔案)

bull node modules(資料夾)

安裝於 Linux系統

Ubuntu Linux的使用者可以加入 NPM Unoffcial PPA這個 repository即可使用 apt-get完成 npm安裝

42 5 NPM套件管理工具

Nodejs中文電子書

Ubuntu Linux使用 apt-get安裝 npm

sudo apt-get install python-software-properties

sudo add-apt-repository ppagias-kay-leenpm

sudo apt-get update

sudo apt-get install npm

npm官方提供的安裝程式 installsh可以適用於大多數的 Linux系統使用這個安裝程式請先確認

1 系統已安裝 curl工具(請使用 curl --version查看版本訊息)

2 已安裝 Nodejs並且 PATH正確設置

3 Nodejs的版本必須大於 04x

以下為 npm提供的安裝指令

curl httpnpmjsorginstallsh | sh

安裝成功會看到如下訊息

installsh安裝成功的訊息

npm10105 homeUSERNAMElocalnodelibnode_modulesnpm

It worked

安裝於Mac OS X

建議採用與 Nodejs相同的方式進行 npm的安裝例如使用MacPorts安裝 Nodejs就同樣使用MacPorts安裝 npm這樣對日後的維護才會更方便容易

使用 MacPorts安裝 npm是本書比較建議的方式它可以讓 npm的安裝移除及更新工作自動化將會幫助開發者節省寶貴時間

51 安裝 NPM 43

Nodejs中文電子書

安裝MacPorts的提示

在MacPorts網站可以取得 OS X系統版本對應的安裝程式(例如 106或 107)httpwwwmacportsorg安裝過程會詢問系統管理者密碼使用預設的選項完成安裝即可安

裝MacPorts之後在終端機執行 port -v將會看到MacPorts的版本訊息

安裝 npm之前先更新MacPorts的套件清單以確保安裝的 npm是最新版本

sudo port -d selfupdate

接著安裝 npm

sudo port install npm

若讀者的 Nodejs並非使用MacPorts安裝則不建議使用MacPorts安裝npm因為 MacPorts會自動檢查並安裝相依套件而 npm相依 nodejs所以MacPorts也會一併將 nodejs套件安裝造成先前讀者使用其它方式安裝的 nodejs被覆蓋

讀者可以先使用 MacPorts安裝 curl(sudo port install curl)

再參考 Linux的 installsh安裝方式即可使用 npm官方提供的安裝程式

NPM安裝後測試

npm是指令列工具(command-line tool)使用時請先打開系統的文字終端機工具

測試 npm安裝與設定是否正確請輸入指令如下

npm -v

或是

npm --version

如果 npm已經正確安裝設定就會顯示版本訊息

44 5 NPM套件管理工具

Nodejs中文電子書

執行結果(範例)

110-2

52 使用 NPM安裝套件

npm目前擁有超過 6000種套件(packages)可以在 npm registry使用關鍵字搜尋套件

httpsearchnpmjsorg

舉例來說在關鍵字欄位輸入「coffee-script」下方的清單就會自動列出包含 coffee-script關鍵字的套件

接著我們回到終端機模式的操作npm的指令工具本身就可以完成套

件搜尋的任務

例如以下的指令同樣可以找出 coffee-script相關套件

npm search coffee-script

以下是搜尋結果的參考畫面

52 使用 NPM安裝套件 45

Nodejs中文電子書

找到需要的套件後(例如 express)即可使用以下指令安裝

npm install coffee-script

值得注意的一點是使用 npm install會將指定的套件安裝在工

作目錄(Working Directory)的 node modules資料夾下

以Windows為例如果執行 npm install的目錄位於

Cproject1

那麼 npm將會自動建立一個 node modules的子目錄(如果不存在)

Cproject1node modules

並且將下載的套件放置於這個子目錄例如

Cproject1node modulescoffee-script

這個設計讓專案可以個別管理相依的套件並且可以在專案佈署或發

行時將這些套件(位於 node modules)一併打包方便其它專案的使用者不必再重新下載套件

這個 npm install的預設安裝模式為 local(本地)只會變更當前專案的資料夾不會影響系統

另一種安裝模式稱為 global(全域)這種模式會將套件安裝到系統資

料夾也就是 npm安裝路徑的 node modules資料夾例如

CProgram Filesnodejsnode modules

46 5 NPM套件管理工具

Nodejs中文電子書

是否要使用全域安裝可以依照套件是否提供新指令來判斷舉例來說express 套件提供 express 這個指令而 coffee-script 則提供 coffee

指令

在 local 安 裝 模 式 中這 些 指 令 的 程 式 檔 案會 被 安 裝 到node modules的 bin這個隱藏資料夾下除非將bin的路徑加入 PATH環境變數否則要執行這些指令將會相當不便

為了方便指令的執行我們可以在 npm install 加上 -g 或 --

global參數啟用 global安裝模式例如

npm install -g coffee-script

npm install -g express

使用 global安裝模式需要注意執行權限的問題若權限不足可能會出現類似以下的錯誤訊息

npm ERR Error EACCES permission denied rsquorsquo

npm ERR

npm ERR Please try running this command again as rootAdministrator

要獲得足夠得執行權限請參考以下說明

bull Windows 7 或 2008 以上在「命令提示字元」的捷徑按右鍵選擇「以系統管理員身分執行」執行 npm指令時就會具有 Administrator身分

bull Mac OS X或 Linux系統可以使用 sudo指令例如

sudo npm install -g express

bull Linux系統可以使用 root權限登入或是以「sudo su -」切換成 root身分(使用 root權限操作系統相當危險因此並不建議使用這種方式)

若加上 -g參數使用 npm install -g coffee-script完成安裝

後就可以在終端機執行 coffee指令例如

coffee -v

52 使用 NPM安裝套件 47

Nodejs中文電子書

執行結果(範例)

CoffeeScript version 120

53 套件的更新及維護

除了前一節說明的 search 及 install 用法npm 還提供其他許多指令(commands)

使用 npm help可以查詢可用的指令

npm help

執行結果(部分)

where ltcommandgt is one of

adduser apihelp author bin bugs c cache completion

config deprecate docs edit explore faq find get

help help-search home i info init install la link

list ll ln login ls outdated owner pack prefix

prune publish r rb rebuild remove restart rm root

run-script s se search set show star start stop

submodule tag test un uninstall unlink unpublish

unstar up update version view whoami

使用 npm help command可以查詢指令的詳細用法例如

npm help list

接下來本節要介紹開發過程常用的 npm指令

使用 list可以列出已安裝套件

npm list

48 5 NPM套件管理工具

Nodejs中文電子書

執行結果(範例)

-- coffee-script120

- express256

- connect185

| -- formidable108

-- mime124

-- mkdirp007

-- qs041

檢視某個套件的詳細資訊例如

npm show express

升級所有套件(如果該套件已發佈更新版本)

npm update

升級指定的套件

npm update express

移除指定的套件

npm uninstall express

54 使用 packagejson

對於正式的 Nodejs專案可以建立一個命名為 packagejson的設

定檔(純文字格式)檔案內容參考範例如下

54 使用 packagejson 49

Nodejs中文電子書

packagejson(範例)

rdquonamerdquo rdquoapplication-namerdquo

rdquoversionrdquo rdquo001rdquo

rdquoprivaterdquo true

rdquodependenciesrdquo

rdquoexpressrdquo rdquo255rdquo

rdquocoffee-scriptrdquo rdquolatestrdquo

rdquomongooserdquo rdquogt= 253rdquo

其中 name與 version依照專案的需求設置

需要注意的是 dependencies的設定它用於指定專案相依的套件名

稱及版本

bull rdquoexpressrdquo rdquo255rdquo

代表此專案相依版本 255的 express套件

bull rdquocoffee-scriptrdquo rdquolatestrdquo

使用最新版的 coffee-script套件(每次更新都會檢查新版)

bull rdquomongooserdquo rdquogt= 253rdquo

使用版本大於 253的 mongoose套件

假設某個套件的新版可能造成專案無法正常運作就必須指定套件的

版本避免專案的程式碼來不及更新以相容新版套件通常在開發初期的

專案需要盡可能維持新套件的相容性(以取得套件的更新或修正)可以

用「gt=」設定最低相容的版本或是使用「latest」設定永遠保持最新

套件

50 5 NPM套件管理工具

6

Express介紹

在前面的 nodejs基礎當中介紹許多許多開設 http的使用方法及介紹以及許多基本的 nodejs基本應用

接下來要介紹一個套件稱為 express [Express](httpexpressjscom)這個套件主要幫忙解決許多 nodejs http server所需要的基本服務讓開發 httpservice變得更為容易不需要像之前需要透過層層模組(module)才有辦法開始編寫自己的程式

這個套件是由 TJ Holowaychuk 製作而成的套件裡面包含基本的路由處理 (route)http資料處理(GETPOSTPUT)另外還與樣板套件(jshtml template engine)搭配同時也可以處理許多複雜化的問題

61 Express安裝

安裝方式十分簡單只要透過之前介紹的 NPM就可以使用簡單的指令安裝指令如下

這邊建議需要將此套件安裝成為全域模組方便日後使用

51

Nodejs中文電子書

62 Express基本操作

express的使用也十分簡單先來建立一個基本的 hello world

var app = require(rsquoexpressrsquo)createServer()

port = 1337

applisten(port)

appget(rsquorsquo function(req res)

ressend(rsquohello worldrsquo)

)

consolelog(rsquostart express servernrsquo)

可以從上面的程式碼發現基本操作與 nodejs http的建立方式沒有太大差異主要差在當我們設定路由時可以直接透過 appget方式設定回應與接受方式

63 Express路由處理

Express對於 http服務上有許多包裝讓開發者使用及設定上更為方便例如有幾個路由設定那我們就統一藉由 appget來處理

Create http server

appget(rsquorsquo function(req res)

ressend(rsquohello worldrsquo)

)

appget(rsquotestrsquo function(req res)

ressend(rsquotest renderrsquo)

)

appget(rsquouserrsquo function(req res)

ressend(rsquouser pagersquo)

)

52 6 Express介紹

Nodejs中文電子書

如上面的程式碼所表示appget可以帶入兩個參數第一個是路徑名稱設定第二個為回應函式 (call back function)回應函式裡面就如同之前的 createServer方法裡面包含 requestresponse兩個物件可供使用使用者就可以透過瀏覽器輸入不同的 url切換到不同的頁面顯示不同的結果

路由設定上也有基本的配對方式讓使用者從瀏覽器輸入的網址可以

是一個變數只要符合型態就可以有對應的頁面產出例如

Create http server

appget(rsquouseridrsquo function(req res)

ressend(rsquouser rsquo + reqparamsid)

)

appget(rsquonumberrsquo function(req res)

ressend(rsquonumber rsquo + reqparamsnumber)

)

裡 面 使 用 到number 從 網 址 輸 入 之 後 就 可 以 直 接 使 用reqparamsnumber 取得所輸入的資料變成 url 參數使用當然前面也是可以加上路徑的設定userid在瀏覽器上路徑必須符合userxxx透

過 reqparamsid就可以取到 xxx這個字串值

另外express參數處理也提供了路由參數配對處理也可以透過正規表示法作為參數設定

var app = require(rsquoexpressrsquo)createServer()

port = 1337

applisten(port)

appget(^ip((d23)((d23))((d23))((d23))) function(req res)

ressend(reqparams)

)

上面程式碼可以發現後面路由設定的型態是正規表示法裡面設定

格式為ip之後必須要加上 ip型態才會符合資料格式同時取得 ip資料已經由正規表示法將資料做分群因此可以取得 ip的四個數字

此程式執行之後可以透過瀏覽器測試輸入網址為 localhost3000ip25525510010可以從頁面獲得資料

63 Express路由處理 53

Nodejs中文電子書

此章節全部範例程式碼如下

overview

author Caesar Chi

blog clonnblogspotcom

version 20120226

create server

var app = require(rsquoexpressrsquo)createServer()

port = 1337

applisten(port)

normal style

appget(rsquorsquo function(req res)

ressend(rsquohello worldrsquo)

)

appget(rsquotestrsquo function(req res)

ressend(rsquotest renderrsquo)

)

parameter style

appget(rsquouseridrsquo function(req res)

ressend(rsquouser rsquo + reqparamsid)

)

appget(rsquonumberrsquo function(req res)

ressend(rsquonumber rsquo + reqparamsnumber)

)

REGX style

appget(^ip((d23)((d23))((d23))) function(req res)

ressend(reqparams)

)

appget(rsquorsquo function(req res)

ressend(rsquoPage not foundrsquo 404)

54 6 Express介紹

Nodejs中文電子書

)

consolelog(rsquostart express servernrsquo)

64 Express middleware

Express 裡面有一個十分好用的應用概念稱為 middleware可以透過middleware做出複雜的效果同時上面也有介紹 next方法參數傳遞就是靠 middleware的概念來傳遞參數讓開發者可以明確的控制程式邏輯

create http server

appuse(expressbodyParser())

appuse(expressmethodOverride())

appuse(expresssession())

上面都是一種 middleware的使用方式透過 appuse方式裡面載入函式執行方法回應函式會包含三個基本參數responserequestnext其中next表示下一個 middleware執行函式同時會自動將預設三個參數繼續帶往下個函式執行底下有個實驗

上面的片段程式執行後開啟瀏覽器連結上 localhost1337會發現伺服器回應結果順序如下

first middle ware

second middle ware

execute middle ware

end middleware function

從上面的結果可以得知剛才設定的 middleware都生效了在 appuse設定的 middleware是所有 url皆會執行方法如果有指定特定方法就可以使用 appget的 middleware設定在 appget函式的第二個參數就可以帶入函式或者是匿名函式只要函式裡面最後會接受 request response next這三個參數同時也有正確指定 next函式的執行時機最後都會執行到最後一個方法當然開發者也可以評估程式邏輯要執行到哪一個階段讓邏輯

可以更為分明

64 Express middleware 55

Nodejs中文電子書

65 Express路由應用

在實際開發上可能會遇到需要使用參數等方式混和變數一起使用

express裡面提供了一個很棒的處理方法 appall這個方式可以先採用基本路由配對再將設定為每個不同的處理方式開發者可以透過這個方式簡

化自己的程式邏輯

overview

author

version 20120226

create server

var app = require(rsquoexpressrsquo)createServer()

port = 1337

users = [

name rsquoClonnrsquo

name rsquoChirsquo

]

applisten(port)

appall(rsquouseridoprsquo function(req res next)

requser = users[reqparamsid]

if (requser)

next()

else

next(new Error(rsquocannot find user rsquo + reqparamsid))

)

appget(rsquouseridrsquo function(req res)

ressend(rsquoviewing rsquo + requsername)

)

appget(rsquouserideditrsquo function(req res)

ressend(rsquoediting rsquo + requsername)

)

56 6 Express介紹

Nodejs中文電子書

appget(rsquouseriddeletersquo function(req res)

ressend(rsquodeleting rsquo + requsername)

)

appget(rsquorsquo function(req res)

ressend(rsquoPage not foundrsquo 404)

)

consolelog(rsquostart express servernrsquo)

內部宣告一組預設的使用者分別給予名稱設定藉由 appall這個方法可以先將路由雛形建立再接下來設定 appget的路徑格式只要符合格式就會分配進入對應的方法中像上面的程式當中如果使用者輸入路徑為user0除了執行 appall程式之後執行 next方法就會對應到路徑設定為userid的這個方法當中如果使用者輸入路徑為user0edit就會執行到useridedit的對應方法

66 Express GET應用範例

我們準備一個使用 GET方法傳送資料的表單

ltDOCTYPE htmlgt

lthtmlgt

ltheadgt

ltmeta http-equiv=rdquoContent-Typerdquo content=rdquotexthtml charset=utf8rdquogt

lttitlegtNodejs 菜鳥筆記 (1)lttitlegt

ltlink rel=rdquostylesheetrdquo href=rdquocssstylecssrdquo type=rdquotextcssrdquo media=rdquoallrdquo gt

ltheadgt

ltbodygt

lth1gtNodejs 菜鳥筆記-註冊lth1gt

ltform id=rdquosignuprdquo method=rdquoGETrdquo action=rdquohttplocalhost3000Signuprdquogt

ltlabelgt 使用者名稱ltlabelgtltinput type=rdquotextrdquo id=rdquousernamerdquo name=rdquousernamerdquo gtltbrgt

ltlabelgt 電子郵件ltlabelgtltinput type=rdquotextrdquo id=rdquoemailrdquo name=rdquoemailrdquo gtltbrgt

ltinput type=rdquosubmitrdquo value=rdquo 註冊我的帳號rdquo gtltbrgt

ltformgt

ltbodygt

lthtmlgt

66 Express GET應用範例 57

Nodejs中文電子書

這個表單沒有什麼特別的地方我們只需要看第 9 行form 使用的method是 GET然後 action是rdquohttplocalhost3000Signupldquo等一下我們要來撰寫Signup這個 URL Path的處理程式

處理 Signup行為

我們知道所謂的 GET方法會透過 URL來把表單的值給帶過去以上面的表單來說到時候 URL會以這樣的形式傳遞

httplocalhost3000Signupusername=xxxampemail=xxx

所以要能處理這樣的資料必須有以下功能

bull 解析 URL

bull 辨別動作是 Signup

bull 解析出 username和 email

一旦能取得 username和 email的值程式就能加以應用了

處理 Signup的程式碼雛形

load module

var url = require(rsquourlrsquo)

urlData = urlparse(requrltrue)

action = urlDatapathname

reswriteHead(200 rdquoContent-Typerdquordquotexthtml charset=utf-8rdquo)

if (action === rdquoSignuprdquo)

user = urlDataquery

resend(rdquolth1gtrdquo + userusername + rdquo 歡迎您的加入 lth1gtltpgt 我們已經將會員啟用信寄至rdquo + useremail + rdquoltpgtrdquo)

首先需要加載 url module它是用來協助我們解析 URL的模組接著使用 urlparse方法第一個傳入 url字串變數也就是 requrl另外第二個參數的用意是設為 ture則引進 querystring模組來協助處理預設是 false它影響到的是 urlDataquery設為 true會傳回物件不然就只是一般的字串urlparse會將字串內容整理成一個物件我們把它指定給 urlData

action變數作為記錄 pathname這是我們稍後要來判斷目前網頁的動作是什麼接著先將 html表頭資訊 (Header)準備好再來判斷路徑邏輯如

58 6 Express介紹

Nodejs中文電子書

果是 Signup這個動作就把 urlDataquery裡的資料指定給 user然後輸出userusername和 useremail把使用者從表單註冊的資料顯示於頁面中

最後進行程式測試啟動 nodejs主程式之後開啟瀏覽器就會看到表單填寫完畢按下送出就可以看到結果了

完整 nodejs程式碼如下

var http = require(rsquohttprsquo)

url = require(rsquourlrsquo)

fs = require(rdquofsrdquo)

server

server = httpcreateServer(function (reqres)

var urlData

encode = rdquoutf8rdquo

filePath = rdquoviewexpress_get_example_formhtmlrdquo

action

urlData = urlparse(requrltrue)

action = urlDatapathname

reswriteHead(200 rdquoContent-Typerdquordquotexthtml charset=utf-8rdquo)

if (action === rdquoSignuprdquo)

user = urlDataquery

resend(rdquolth1gtrdquo + userusername + rdquo 歡迎您的加入 lth1gtltpgt 我們已經將會員啟用信寄至rdquo + useremail + rdquoltpgtrdquo)

else

fsreadFile(filePath encode function(err file)

reswrite(file)

resend()

)

)

serverlisten(3000)

consolelog(rsquoServer 跑起來了現在時間是rsquo + new Date())

66 Express GET應用範例 59

Nodejs中文電子書

67 Express POST應用範例

一開始準備基本的 html表單傳送內容以 POST方式form的 action屬性設定為 POST其餘 html內容與前一個範例應用相同

ltDOCTYPE htmlgt

lthtmlgt

ltheadgt

ltmeta http-equiv=rdquoContent-Typerdquo content=rdquotexthtml charset=utf8rdquogt

lttitlegtNodejs 菜鳥筆記 (1)lttitlegt

ltlink rel=rdquostylesheetrdquo href=rdquocssstylecssrdquo type=rdquotextcssrdquo media=rdquoallrdquo gt

ltheadgt

ltbodygt

lth1gtNodejs 菜鳥筆記-註冊lth1gt

ltform id=rdquosignuprdquo method=rdquoPOSTrdquo action=rdquohttplocalhost3000Signuprdquogt

ltlabelgt 使用者名稱ltlabelgtltinput type=rdquotextrdquo id=rdquousernamerdquo name=rdquousernamerdquo gtltbrgt

ltlabelgt 電子郵件ltlabelgtltinput type=rdquotextrdquo id=rdquoemailrdquo name=rdquoemailrdquo gtltbrgt

ltinput type=rdquosubmitrdquo value=rdquo 註冊我的帳號rdquo gtltbrgt

ltformgt

ltbodygt

lthtmlgt

nodejs的程式處理邏輯與前面 GET範例類似部分程式碼如下

qs = require(rsquoquerystringrsquo)

if (action === rdquoSignuprdquo)

formData = rsquorsquo

reqon(rdquodatardquo function (data)

formData += data

)

reqon(rdquoendrdquo function ()

user = qsparse(formData)

resend(rdquolth1gtrdquo + userusername + rdquo 歡迎您的加入 lth1gtltpgt 我們已經將會員啟用信寄至rdquo + useremail + rdquoltpgtrdquo)

)

60 6 Express介紹

Nodejs中文電子書

主要加入了rsquoquerystringrsquo這個moduel方便我們等一下解析由表單 POST回來的資料另外加入一個 formData的變數用來搜集待等一下表單回傳的資料前面的 GET範例我們只從 req拿出 url的資料這次要在利用req身上的事件處理

JavaScript 在訂閱事件時使用 addEventListener而 nodejs 使用的則是on這邊加上了監聽 data的事件會在瀏覽器傳送資料到Web Server時被執行參數是它所接收到的資料型態是字串

接著再增加 end的事件當瀏覽器的請求事件結束時它就會動作

由於瀏覽器使用 POST在上傳資料時會將資料一塊塊地上傳因為我們在監聽 data事件時透過 formData變數將它累加起來lt不過由於我們

上傳的資料很少一次就結束不過如果日後需要傳的是資料比較大的檔

案這個累加動作就很重要

當資料傳完就進到 end 事件中會用到 qsparse 來解析 formDataformData的內容是字串內容是

username=wordsmithampemail=wordsmith40somewhere

而 qsparse可以幫我們把這個 querystring轉成物件的格式也就是

username=wordsmithampemail=wordsmith40somewhere

一旦轉成物件並指定給 user之後其他的事情就和 GET方法時操作的一樣寫 response的表頭將內容回傳並將 userusername和 useremail代入到內容中

修改完成後接著執行 nodejs程式啟動 web server開啟瀏覽器進入表單測試看看POST的方式能否順利運作

完整程式碼如下

var http = require(rsquohttprsquo)

url = require(rsquourlrsquo)

fs = require(rdquofsrdquo)

qs = require(rsquoquerystringrsquo)

server

server = httpcreateServer(function (reqres)

var urlData

67 Express POST應用範例 61

Nodejs中文電子書

encode = rdquoutf8rdquo

filePath = rdquoviewexpress_post_example_formhtmlrdquo

formData

action

urlData = urlparse(requrltrue)

action = urlDatapathname

reswriteHead(200 rdquoContent-Typerdquordquotexthtml charset=utf-8rdquo)

if (action === rdquoSignuprdquo)

formData = rsquorsquo

reqon(rdquodatardquo function (data)

formData += data

)

reqon(rdquoendrdquo function ()

user = qsparse(formData)

resend(rdquolth1gtrdquo + userusername + rdquo 歡迎您的加入 lth1gtltpgt 我們已經將會員啟用信寄至rdquo + useremail + rdquoltpgtrdquo)

)

else

fsreadFile(filePath encode function(err file)

reswrite(file)

resend()

)

)

serverlisten(3000)

consolelog(rsquoServer 跑起來了現在時間是rsquo + new Date())

62 6 Express介紹

Nodejs中文電子書

68 Express AJAX應用範例

在 Nodejs要使用 Ajax傳送資料並且與之互動在接受資料的部份沒有太大的差別client端不是用 GET就是用 POST來傳資料重點在處理完後用 JSON格式回傳當然 Ajax不見得只傳 JSON格式有時是回傳一段 HTML碼不過後者對伺服器來說基本上就和前兩篇沒有差別了所以我們還是以回傳 JSON做為這一回的主題

這一回其實大多數的工作都會落在前端 Ajax上面前端要負責發送與接收資料並在接收資料後撤掉原先發送資料的表單並將取得的資料

改成 HTML格式之後放上頁面

首先先準備 HTML靜態頁面資料

ltDOCTYPE htmlgt

lthtmlgt

ltheadgt

lttitlegtNodejs 菜鳥筆記 (3)lttitlegt

ltlink rel=rdquostylesheetrdquo href=rdquocssstylecssrdquo type=rdquotextcssrdquo media=rdquoallrdquo gt

ltscript src=rdquohttpsajaxgoogleapiscomajaxlibsjquery171jqueryminjsrdquogtltscriptgt

ltheadgt

ltbodygt

lth1gtNodejs 菜鳥筆記-註冊 (用 Ajax)lth1gt

ltform id=rdquosignuprdquo action=rdquoSignuprdquo method=rdquoPOSTrdquogt

ltlabelgt 使用者名稱ltlabelgtltinput type=rdquotextrdquo id=rdquousernamerdquo name=rdquousernamerdquo gtltbrgt

ltlabelgt 電子郵件ltlabelgtltinput type=rdquotextrdquo id=rdquoemailrdquo name=rdquoemailrdquo gtltbrgt

ltinput type=rdquosubmitrdquo value=rdquo 註冊我的帳號rdquo gtltbrgt

ltformgt

ltdiv id=rdquoresultrdquogt

ltdivgt

ltscript type=rdquotextjavascriptrdquogt

$(function()$(rsquosignup input[type=rdquosubmitrdquo]rsquo)click(function(e)

var user =

userusername = $(rdquousernamerdquo)val()useremail = $(rdquoemailrdquo)val()

$post(rdquoSignuprdquouserfunction(data)greet(data)

)

68 Express AJAX應用範例 63

Nodejs中文電子書

epreventDefault()

)

var greet = function(msg)

var resultNode = $(rsquoresultrsquo)greeting = $(rsquolth2gtHirsquo+msgusername+rsquo 你的會員 id 是rsquo+ msgid +rsquo 我們會將會員啟動信寄至rsquo+msgemail+rsquolth2gtrsquo)

$(rsquosignuprsquo)hide()resultNodehtml(rdquordquo)

resultNodeappend(greeting)

)

ltscriptgt

ltbodygt

lthtmlgt

HTML頁面上準備了一個表單用來傳送註冊資料接著直接引用了Google CDN來載入 jQuery用來幫我們處理 Ajax的工作這次要傳送和接收的工作很大的變動都在 HTML頁面上的 JavaScript當中我們要做的事有 (相關 jQuery處理這邊不多做贅述指提起主要功能解說)

bull 用 jQUery取得 submit按鈕綁定它的 click動作

bull 取得表單 username和 email的值存放在 user這個物件中

bull 用 jQuery的 $post方法將 user的資料傳到 Server

bull 一旦成功取得資料後透過 greet這個 function組成回報給 user的訊息

bull 清空原本給使用者填資料的表單

bull 將 Server回傳的 usernameemail和 id這 3個資料組成回應的訊息

bull 將訊息放到原本表單的位置

經過以上的處理後一個 Ajax的表單的基本功能已經完成

接著進行 nodejs主要程式的編輯部分程式碼如下

var fs = require(rdquofsrdquo)

64 6 Express介紹

Nodejs中文電子書

qs = require(rsquoquerystringrsquo)

if (action === rdquoSignuprdquo)

formData = rsquorsquo

reqon(rdquodatardquo function (data)

formData += data

)

reqon(rdquoendrdquo function ()

var msg

user = qsparse(formData)

userid = rdquo123456rdquo

msg = JSONstringify(user)

reswriteHead(200 rdquoContent-Typerdquordquoapplicationjson charset=utf-8rdquordquoContent-Lengthrdquomsglength)

resend(msg)

)

這裡的程式和前面 POST範例基本上大同小異差別在

bull 幫 user的資料加上 id隨意存放一些文字進去讓 Server回傳的資料多於 Client端傳上來的不然會覺得 Server都沒做事

bull 增加了 msg 這個變數存放將 user 物件 JSON 文字化的結果JSONstringify這個轉換函式是 V8引擎所提供的如果你好奇的話

bull 大重點來了我們要告訴 Client端這次回傳的資料格式是 JSON所在 Content-type和 Content-Length要提供給 Client

Server很輕鬆就完成任務了最後進行程式測試啟動 nodejs主程式之後開啟瀏覽器就會看到表單填寫完畢按下送出就可以看到結果了

最後 nodejs本篇範例程式碼如下

var http = require(rsquohttprsquo)

url = require(rsquourlrsquo)

fs = require(rdquofsrdquo)

qs = require(rsquoquerystringrsquo)

server

68 Express AJAX應用範例 65

Nodejs中文電子書

server = httpcreateServer(function (reqres)

var urlData

encode = rdquoutf8rdquo

filePath = rdquoviewexpress_ajax_example_formhtmlrdquo

formData

action

urlData = urlparse(requrltrue)

action = urlDatapathname

if (action === rdquoSignuprdquo)

formData = rsquorsquo

reqon(rdquodatardquo function (data)

formData += data

)

reqon(rdquoendrdquo function ()

var msg

user = qsparse(formData)

userid = rdquo123456rdquo

msg = JSONstringify(user)

reswriteHead(200 rdquoContent-TyperdquordquoapplicationjsonrdquordquoContent-Lengthrdquomsglength)

resend(msg)

)

else

fsreadFile(filePath encode function(err file)

reswriteHead(200 rdquoContent-Typerdquordquotexthtml charset=utf-8rdquo)

reswrite(file)

resend()

)

)

serverlisten(3000)

66 6 Express介紹

Nodejs中文電子書

consolelog(rsquoServer 跑起來了現在時間是rsquo + new Date())

69 原始資料提供

bull [NodeJS初學者筆記 (1)-用 GET傳送資料] (httpithelpithomecomtwquestion10087402)

bull [NodeJS初學者筆記 (2)-用 POST傳送資料] (httpithelpithomecomtwquestion10087489)

bull [NodeJS 初學者筆記 (3)-用 Ajax 傳送資料] (httpithelpithomecomtwquestion10087627)

69 原始資料提供 67

Nodejs中文電子書

68 6 Express介紹

7

CoffeeScript

bull Spine

bull Hem

bull Spineapp

Letrsquos Have a Cup of CoffeeScript

bull es5-shim ECMAScript 5 compatibility shims for legacy JavaScript engines

ESnext

node-iform - A middleware for node-validator httpsgithubcomguileennode-iform

69

Nodejs中文電子書

70 7 CoffeeScript

8

製作一個 Hubot的 Plurk Adapter

81 應用事項提醒

此應用範例以CoffeeScript編寫主要程式架構採用Github釋放的Hubot專案以下有幾個主要注意事項請讀者注意

bull Hubot 一開始的架構除了內建的兩個 Adapter 之外其餘都要以Nodejs的Module方式才能運作

bull Hubot的 binhubot裡面寫著 npm install所以不管你怎麼改原始碼也不能改變第一點的狀況

其實上述都在討論同一件事情NodeJS的模組而且模組不能設定相對路徑之類的來安裝一定要包裝成 npm相符和格式才有辦法正確執行

82 建立 Adapter

首先需要準備兩個檔案

bull packagejson

bull plurkcoffee

71

Nodejs中文電子書

有這兩個就足以變成 NodeJS的模組了在開始 Coding之前先來設定一下 packagejson相依性

rdquonamerdquo rdquohubot-plurkrdquo

rdquoversionrdquo rdquo011rdquo

rdquomainrdquo rdquoplurkrdquo

rdquodependenciesrdquo

rdquohubotrdquo rdquogt=205rdquo

rdquooauthrdquo rdquordquo

rdquocronrdquordquordquo

這邊會使用到NodeJS的 cron模組(Module)而登入 Plurk需要OAuth標準授權才行所以也需要加載 OAuth模組

注意Hubot在讀取非內建模組時會自動在前面加上 hubot-的前置

83 建立 Robot跟 API

本篇應用基本上程式碼大部分參考 Hubot - Twitter Adapter來製作主要差異只有在使用 OAuth這個模組Twitter有 Streaming可用而 Plurk則得用 Comet方式來達到即時讀取

plurkcoffee程式碼

Robot = require(rdquohubotrdquo)robot()

Adapter = require(rdquohubtordquo)adapter()

EventEmitter = require(rdquoeventsrdquo)EventEmitter

oauth = require(rdquooauthrdquo)

cronJob = require(rdquocronrdquo)CronJob

class Plurk exntends Adapter

class PlurkStreaming exnteds EventEmitter

72 8 製作一個 Hubot的 Plurk Adapter

Nodejs中文電子書

先弄個基本架構主要 Class皆參考 Twitter Adapter命名方式接著再將 Plurk這個 Class增加幾個Method基本上只要有 run send reply就夠了而 run用來做初始化的部分

class Plurk entends Adapter

send (plurk id strings⋯) -gt

reply (plurk id strings⋯) -gt

run -gt

看起來有點東西接著來處理主要的 Plurk API結合部分

class PlurkStreaming extends EventEmitter

consuctor (options) -gt

plurk (callback) -gt

觀察河道

getChannel -gt

取得 Comet 網址

reply (plurk id message) -gt

回噗

acceptFriends -gt

接受好友

get (path callback) -gt

GET 請求

post (path body callback)-gt

POST 請求(其實是裝飾)

request (method path body callback)-gt

主要的 OAuth 請求

comet (server callback)-gt

噗浪的 Comet 傳回是 JavaScript Callback 要另外處理後才會變成 JSON

然後把注意力集中到 constructor上先把建構子弄好

constructor (options) -gt

super()

if optionskey and optionssecret and optionstoken and optionstoken secret

key = optionskey

secret = optionssecret

token = optionstoken

token secret = optionstoken secret

83 建立 Robot跟 API 73

Nodejs中文電子書

建立 OAuth 連接

consumer = new oauthOAuth(

rdquohttpwwwplurkcomOAuthrequest tokenrdquo

rdquohttpwwwplurkcomOAuthaccess tokenrdquo

key

secret

rdquo10rdquo

rdquohttpwwwplurkcomOAuthauthorizerdquo

rdquoHMAC-SHA1rdquo

)

domain = rdquowwwplurkcomrdquo

初始化取得 Comet 網址

do getChannel

else

throw new Error(rdquo 參數不足需要 Key Secret Token Token Secretrdquo)

接著來處理 request這個 method

request (method path body callback) -gt

記錄一下這次的 Request

consolelog(rdquohttpdomainpathrdquo)

Callback 這邊先不丟進去要用另一種方式處理

request = consumerget(rdquohttpdomainpathrdquo token token secret null)

requeston rdquoresponserdquo (res) -gt

reson rdquodatardquo (chunk) -gt

parseResponse(chunk+rsquorsquo callback)

reson rdquoendrdquo (data) -gt

consolelog rdquoEnd Request pathrdquo

reson rdquoerrorrdquo (data) -gt

consolelog rdquoError rdquo + data

requestend()

處理資料

parseResponse = (data callback) -gt

if datalength gt 0

用 TryCatch 避免處理 JSON 出錯導致整個中斷

try

callback null JSONparse(data)

catch err

74 8 製作一個 Hubot的 Plurk Adapter

Nodejs中文電子書

consolelog(rdquoError Parse JSONrdquo + data err)

繼續執行

callback null data ||

大致上就是這樣上面程式的架構已經將整個 Hubot Plurk Adapter完成因為在測試時竟然因為噗浪 Lag而沒讀到完整的 Comet資料然後造成程式異常為了避免這個問題發生因此需要加上為了完美呈現需要再

加上 Comet的處理所以要使用到 EventEmitter的功能

comet (server callback) -gt

在 Callback 裡面會找不到自身所以設定區域變數

self =

記錄一下這次的 Request

consolelog(rdquo[Comet] serverrdquo)

Callback 這邊先不丟進去要用另一種方式處理

request = consumerget(rdquohttpdomainpathrdquo token token secret null)

requeston rdquoresponserdquo (res) -gt

reson rdquodatardquo (chunk) -gt

parseResponse(chunk+rsquorsquo callback)

reson rdquoendrdquo (data) -gt

consolelog rdquoEnd Request pathrdquo

請求結束發出事件通知可以進行下一次請求

selfemit rdquonextPlurkrdquo

reson rdquoerrorrdquo (data) -gt

consolelog rdquoError rdquo + data

requestend()

處理資料

parseResponse = (data callback) -gt

if datalength gt 0

用 trycatch 避免失敗中斷

try

去掉 JavaScript 的 Callback

data = datamatch(CometChannelscriptCallback((+))s)

jsonData = rdquordquo

if data

83 建立 Robot跟 API 75

Nodejs中文電子書

jsonData = JSONparse(data[1])

else

如果沒有任何 Match 嘗試直接 parse

jsonData = JSONparse(data)

catch err

consolelog(rdquo[Comet] Errorrdquo data err)

用 TryCatch 避免處理 JSON 出錯導致整個中斷

try

只傳入 json 的 data 部分

callback null jsonDatadata

catch err

consolelog(rdquo[Comet]Error Parse JSONrdquo + data err)

繼續執行

callback null data ||

後面的 get跟 post就簡單多了

get (path callback) -gt

request(rdquoGETrdquo path null callback)

post (path body callback) -gt

request(rdquoPOSTrdquo path body callback)

接著處理取的 Comet網址的 getChannel

getChannel -gt

self =

get rdquoAPPRealtimegetUserChannelrdquo (error data) -gt

if error

檢查是否有 comet server

if datacomet server

selfchannel = datacomet server

如果沒有 Channel Ready 就嘗試連接會失敗

selfemit(rsquochannel readyrsquo)

那麼先來處理 Plurk Adaper好處理的部份

send (plruk id strings⋯)-gt

跟 Reply 一樣直接交給 reply 做

reply plurk id strings⋯

76 8 製作一個 Hubot的 Plurk Adapter

Nodejs中文電子書

reply (plurk id strings⋯) -gt

stringsforEach (message) =gt

botreply(plruk id message)

接著把 run處理好就可以上線運作摟

run -gt

self =

options =

key processenvHUBOT PLURK KEY

secret processenvHUBOT PLURK SECRET

token processenvHUBOT PLURK TOKEN

token secret processenvHUBOT PLURK TOKEN SECRET

創建剛剛的 API

bot = new PlurkStreaming(options)

依照 Twitter 的 new RobotTextMessage 會沒有反應所以參考 hubot-minecraft 的方式

r = robotconstructor

處理噗浪河道訊息

doPlurk = (data)-gt

檢查是否為回噗

if dataresponse

datacontent raw = dataresponsecontent raw

datauser id = dataresponseuser id

確定有噗浪 ID 跟訊息

if dataplurk id and datacontent raw

selfreceive new rTextMessage(dataplurk id datacontent raw)

取得 Comet Server 完成開始第一次 Comet 連接

boton rdquochannel readyrdquo () -gt

botplurk selfdoPlurk

上一次 Comet 完成繼續 Polling

boton rdquonextPlurkrdquo ()-gt

botplurk selfdoPlurk

定時接受好友邀請

do botacceptFriends

bot = bot

83 建立 Robot跟 API 77

Nodejs中文電子書

終於完成 AdapterHubot專案裡面的 scripts資料夾內是互動部分不需要像 Adapter如此大費周章處理只新增檔案並且設計好對白之後就會回噗了我開發用的機器人在此大家可以去跟他玩玩[httpplurkcomelct9620 bot](httpplurkcomelct9620 bot)

84 原始資料提供

bull [製 作 一 個 Hubot 的 噗 浪 Adapter](http revo-skillfrosttw blog20120318create-a-hubot-plurk-adapter)

78 8 製作一個 Hubot的 Plurk Adapter

9

Nodejs好用工具介紹

91 logio

Real-time log monitoring in your browser

httplogioorg

nodejs (gt=0412 lt=0611)

npm config set unsafe-perm true

npm install -g --prefix=usrlocal logio

logio server start

httplocalhost8998

79

Nodejs中文電子書

80 9 Nodejs好用工具介紹

10

精選文章

101 Express

bull 使用 NodeJS + Express從 GETPOST Request取值 [邀稿中]

bull link2 [邀稿中]

bull link3 [邀稿中]

81

Nodejs中文電子書

82 10 精選文章

11

參考資源

111 Nodejs書籍

bull Programming Nodejs April 2012 (est) Travis Swicegood Pragmatic Bookshelf

bull e Little Book on CoffeeScript January 2012 Alex MacCaw OrsquoReilly Media

bull Node for Front-End Developers January 2012 Garann Means OrsquoReilly Media

bull Building Hypermedia APIs with HTML5 and Node November 2011 MikeAmundsen OrsquoReilly Media

bull Node Web Development August 2011 David Herron PacktPub

bull CoffeeScript Accelerated JavaScript Development July 2011 Trevor BurnhamPragmatic Bookshelf

bull Getting Started with GEO CouchDB and Nodejs July 2011 Mick omp-son OrsquoReilly Media

bull What Is Node July 2011 Brett McLaughlin OrsquoReilly Media

bull Node Up and Running May 2011 Tom Hughes-Croucher Mike WilsonOrsquoReilly Media

83

Nodejs中文電子書

bull Hands-on Nodejs e Nodejs introduction and API reference May 2011 Pe-dro Teixeira LeanPub

bull e Node Beginner Book Manuel Kiessling

bull Mastering Nodejs visionmedia

112 Nodejs影音教學

bull e Node Sessions e Best of OSCON 2011 August 2011 OrsquoReilly Media(Video)

bull Tom Hughes-Croucher on Node March 2011 OrsquoReilly Media (Video)

113 Nodejs教學網站

bull Node Tuts

114 Nodejs課程

bull Introduction To Nodejs Van Nguyen CodeLesson

84 11 參考資源

Page 42: Nodejs Wiki

Nodejs中文電子書

44 nodejs http靜態檔案輸出

前面已經了解如何讀取本地端檔案接下來將配合 http伺服器路由讓每個路由都能夠輸出相對應的靜態 html檔案首先新增加幾個靜態 html檔案

ltDOCTYPE htmlgt

lthtml xmlns=rdquohttpwwww3org1999xhtmlrdquo xmllang=rdquozh-CNrdquo lang=rdquozh-CNrdquogt

ltheadgt

ltmeta http-equiv=rdquoContent-Typerdquo content=rdquotexthtml charset=UTF-7rdquo gt

lttitlegtnodejs index html filelttitlegt

ltheadgt

ltbodygt

lth1gtnodejs index html filelth1gt

ltbodygt

lthtmlgt

ltDOCTYPE htmlgt

lthtml xmlns=rdquohttpwwww3org1999xhtmlrdquo xmllang=rdquozh-CNrdquo lang=rdquozh-CNrdquogt

ltheadgt

ltmeta http-equiv=rdquoContent-Typerdquo content=rdquotexthtml charset=UTF-7rdquo gt

lttitlegtnodejs test html filelttitlegt

ltheadgt

ltbodygt

lth1gtnodejs test html filelth1gt

ltbodygt

lthtmlgt

ltDOCTYPE htmlgt

lthtml xmlns=rdquohttpwwww3org1999xhtmlrdquo xmllang=rdquozh-CNrdquo lang=rdquozh-CNrdquogt

ltheadgt

ltmeta http-equiv=rdquoContent-Typerdquo content=rdquotexthtml charset=UTF-7rdquo gt

lttitlegtnodejs static html filelttitlegt

ltheadgt

ltbodygt

lth1gtnodejs static html filelth1gt

ltbodygt

lthtmlgt

36 4 Nodejs基礎

Nodejs中文電子書

準備一個包含基本路由功能的 http伺服器

var server

ip = rdquo127001rdquo

port = 1337

http = require(rsquohttprsquo)

url = require(rsquourlrsquo)

server = httpcreateServer(function (req res)

var path = urlparse(requrl)

)

serverlisten(port ip)

consolelog(rdquoServer running at httprdquo + ip + rdquordquo + port)

加入 le system 模組使用 readFile 的功能將這一段程式放置於

createServer的回應函式中

fsreadFile(filePath encode function(err file)

)

readFile的回應函式裡面加入頁面輸出讓瀏覽器可以正確讀到檔案在這邊我們設定讀取的檔案為 html 靜態檔案所以 Content-type 設定為texthtml讀取到檔案的內容將會正確輸出成 html靜態檔案

fsreadFile(filePath encode function(err file)

reswriteHead(200 rsquoContent-Typersquo rsquotexthtmlrsquo)

reswrite(file)

resend()

)

到這邊為止基本的程式內容都已經完成剩下一些細節的調整首先

路徑上必須做調整目前的靜態檔案全部都放置於 static資料夾底下設定一個變數來記住資料夾位置

接著將瀏覽器發出要求路徑與資料夾組合讀取正確 html靜態檔案使用者有可能會輸入錯誤路徑所以在讀取檔案的時候要加入錯誤處理

同時回應 404伺服器無法正確回應的 http header格式

加入這些細節的修改一個基本的 http 靜態 html輸出伺服器就完成了完整程式碼如下

44 nodejs http靜態檔案輸出 37

Nodejs中文電子書

var server

ip = rdquo127001rdquo

port = 1337

http = require(rsquohttprsquo)

fs = require(rdquofsrdquo)

folderPath = rdquostaticrdquo

url = require(rsquourlrsquo)

path

filePath

encode = rdquoutf8rdquo

server = httpcreateServer(function (req res)

path = urlparse(requrl)

filePath = folderPath + pathpathname

fsreadFile(filePath encode function(err file)

if (err)

reswriteHead(404 rsquoContent-Typersquo rsquotextplainrsquo)

resend()

return

reswriteHead(200 rsquoContent-Typersquo rsquotextapplicationrsquo)

reswrite(file)

resend()

)

)

serverlisten(port ip)

consolelog(rdquoServer running at httprdquo + ip + rdquordquo + port)

45 nodejs http GET資料擷取

http伺服器中除了路由之外另一個最常使用的方法就是擷取 GET資料本單元將會介紹如何透過基本 http伺服器擷取瀏覽器傳來的要求擷取 GET資料

在 http協定中GET參數都是藉由 URL從瀏覽器發出要求送至伺服器

38 4 Nodejs基礎

Nodejs中文電子書

端基本的傳送網址格式可能如下

http127001testsend=1amptest=2

上面這段網址裡面的 GET參數就是 send而這個變數的數值就為 1如果想要在 http伺服器取得 GET資料需要在瀏覽器給予的要求 (request)做處理

首先需要載入 query string這個模組這個模組主要是用來將字串資料

過濾後轉換成javascript物件

qs = require(rsquoquerystringrsquo)

接著在第一階段利用 url模組過濾瀏覽器發出的 URL資料後將回應的物件裡面的 query這個變數是一個字串值資料過濾後如下

send=1amptest=2

透過 query string使用 parse這個方法將資料轉換成 javascript物件就表示 GET的資料已經被伺服器端正式擷取下來

path = urlparse(requrl)

parameter = qsparse(pathquery)

整個 nodejs http GET參數完整擷取程式碼如下

var server

ip = rdquo127001rdquo

port = 1337

http = require(rsquohttprsquo)

qs = require(rsquoquerystringrsquo)

url = require(rsquourlrsquo)

server = httpcreateServer(function (req res)

var path = urlparse(requrl)

parameter = qsparse(pathquery)

consoledir(parameter)

reswriteHead(200 rsquoContent-Typersquo rsquotextplainrsquo)

reswrite(rsquoBrowser test GET parameternrsquo)

resend()

)

45 nodejs http GET資料擷取 39

Nodejs中文電子書

serverlisten(port ip)

consolelog(rdquoServer running at httprdquo + ip + rdquordquo + port)

程式運作之後由瀏覽器輸入要求網址之後nodejs伺服器端回應資料為

Server running at http1270011337

send rsquo1rsquo test rsquo1rsquo

46 本章結語

前面所解說的部份一大部分主要是處理 http伺服器基本問題雖然在某些部分有牽扯到 http 伺服器基本運作原理主要還是希望可以藉由這些基本範例練習 nodejs練習回應函式與語法串接的特點習慣編寫javascript風格程式當然直接這樣開發 nodejs是非常辛苦的接下來在模組實戰開發的部份將會介紹特定的模組一步一步帶領各位從無到有進行

nodejs應用程式開發

40 4 Nodejs基礎

5

NPM套件管理工具

npm全名為 Node Package Manager是 Nodejs的套件(package)管理工具類似 Perl的 ppm或 PHP的 PEAR等安裝 npm後使用npm install

module name指令即可安裝新套件維護管理套件的工作會更加輕鬆

npm可以讓Nodejs的開發者直接利用擴充線上的套件庫(packagesregistry)加速軟體專案的開發npm提供很友善的搜尋功能可以快速找到安裝需要的套件當這些套件發行新版本時npm也可以協助開發者自動更新這些套件

npm不僅可用於安裝新的套件它也支援搜尋列出已安裝模組及更新的功能

51 安裝 NPM

Nodejs在 063版本開始內建 npm讀者安裝的版本若是此版本或更新的版本就可以略過以下安裝說明

若要檢查 npm是否正確安裝可以使用以下的指令

npm -v

41

Nodejs中文電子書

執行結果說明

若 npm正確安裝執行 npm -v將會看到類似 110-2的版本訊息

若讀者安裝的 Nodejs版本比較舊或是有興趣嘗試自己動手安裝 npm工具則可以參考以下的說明

安裝於Windows系統

Nodejs for Windows 於 062 版開始內建 npm使用 nodejsorg 官方提供的安裝程式不需要進一步的設定就可以立即使用 npm指令對於Windows的開發者來說大幅降低環境設定的問題與門檻

除了使用 Nodejs內建的 npm讀者也可以從 npm官方提供的以下網址

httpnpmjsorgdist

這是由 npm提供的 Fancy Windows Install版本請下載壓縮檔(例如npm-110-3zip)並將壓縮檔內容解壓縮至 Nodejs的安裝路徑(例如CProgram Filesnodejs)

解壓縮後在 Nodejs的安裝路徑下應該有以下的檔案及資料夾

bull npmcmd(檔案)

bull node modules(資料夾)

安裝於 Linux系統

Ubuntu Linux的使用者可以加入 NPM Unoffcial PPA這個 repository即可使用 apt-get完成 npm安裝

42 5 NPM套件管理工具

Nodejs中文電子書

Ubuntu Linux使用 apt-get安裝 npm

sudo apt-get install python-software-properties

sudo add-apt-repository ppagias-kay-leenpm

sudo apt-get update

sudo apt-get install npm

npm官方提供的安裝程式 installsh可以適用於大多數的 Linux系統使用這個安裝程式請先確認

1 系統已安裝 curl工具(請使用 curl --version查看版本訊息)

2 已安裝 Nodejs並且 PATH正確設置

3 Nodejs的版本必須大於 04x

以下為 npm提供的安裝指令

curl httpnpmjsorginstallsh | sh

安裝成功會看到如下訊息

installsh安裝成功的訊息

npm10105 homeUSERNAMElocalnodelibnode_modulesnpm

It worked

安裝於Mac OS X

建議採用與 Nodejs相同的方式進行 npm的安裝例如使用MacPorts安裝 Nodejs就同樣使用MacPorts安裝 npm這樣對日後的維護才會更方便容易

使用 MacPorts安裝 npm是本書比較建議的方式它可以讓 npm的安裝移除及更新工作自動化將會幫助開發者節省寶貴時間

51 安裝 NPM 43

Nodejs中文電子書

安裝MacPorts的提示

在MacPorts網站可以取得 OS X系統版本對應的安裝程式(例如 106或 107)httpwwwmacportsorg安裝過程會詢問系統管理者密碼使用預設的選項完成安裝即可安

裝MacPorts之後在終端機執行 port -v將會看到MacPorts的版本訊息

安裝 npm之前先更新MacPorts的套件清單以確保安裝的 npm是最新版本

sudo port -d selfupdate

接著安裝 npm

sudo port install npm

若讀者的 Nodejs並非使用MacPorts安裝則不建議使用MacPorts安裝npm因為 MacPorts會自動檢查並安裝相依套件而 npm相依 nodejs所以MacPorts也會一併將 nodejs套件安裝造成先前讀者使用其它方式安裝的 nodejs被覆蓋

讀者可以先使用 MacPorts安裝 curl(sudo port install curl)

再參考 Linux的 installsh安裝方式即可使用 npm官方提供的安裝程式

NPM安裝後測試

npm是指令列工具(command-line tool)使用時請先打開系統的文字終端機工具

測試 npm安裝與設定是否正確請輸入指令如下

npm -v

或是

npm --version

如果 npm已經正確安裝設定就會顯示版本訊息

44 5 NPM套件管理工具

Nodejs中文電子書

執行結果(範例)

110-2

52 使用 NPM安裝套件

npm目前擁有超過 6000種套件(packages)可以在 npm registry使用關鍵字搜尋套件

httpsearchnpmjsorg

舉例來說在關鍵字欄位輸入「coffee-script」下方的清單就會自動列出包含 coffee-script關鍵字的套件

接著我們回到終端機模式的操作npm的指令工具本身就可以完成套

件搜尋的任務

例如以下的指令同樣可以找出 coffee-script相關套件

npm search coffee-script

以下是搜尋結果的參考畫面

52 使用 NPM安裝套件 45

Nodejs中文電子書

找到需要的套件後(例如 express)即可使用以下指令安裝

npm install coffee-script

值得注意的一點是使用 npm install會將指定的套件安裝在工

作目錄(Working Directory)的 node modules資料夾下

以Windows為例如果執行 npm install的目錄位於

Cproject1

那麼 npm將會自動建立一個 node modules的子目錄(如果不存在)

Cproject1node modules

並且將下載的套件放置於這個子目錄例如

Cproject1node modulescoffee-script

這個設計讓專案可以個別管理相依的套件並且可以在專案佈署或發

行時將這些套件(位於 node modules)一併打包方便其它專案的使用者不必再重新下載套件

這個 npm install的預設安裝模式為 local(本地)只會變更當前專案的資料夾不會影響系統

另一種安裝模式稱為 global(全域)這種模式會將套件安裝到系統資

料夾也就是 npm安裝路徑的 node modules資料夾例如

CProgram Filesnodejsnode modules

46 5 NPM套件管理工具

Nodejs中文電子書

是否要使用全域安裝可以依照套件是否提供新指令來判斷舉例來說express 套件提供 express 這個指令而 coffee-script 則提供 coffee

指令

在 local 安 裝 模 式 中這 些 指 令 的 程 式 檔 案會 被 安 裝 到node modules的 bin這個隱藏資料夾下除非將bin的路徑加入 PATH環境變數否則要執行這些指令將會相當不便

為了方便指令的執行我們可以在 npm install 加上 -g 或 --

global參數啟用 global安裝模式例如

npm install -g coffee-script

npm install -g express

使用 global安裝模式需要注意執行權限的問題若權限不足可能會出現類似以下的錯誤訊息

npm ERR Error EACCES permission denied rsquorsquo

npm ERR

npm ERR Please try running this command again as rootAdministrator

要獲得足夠得執行權限請參考以下說明

bull Windows 7 或 2008 以上在「命令提示字元」的捷徑按右鍵選擇「以系統管理員身分執行」執行 npm指令時就會具有 Administrator身分

bull Mac OS X或 Linux系統可以使用 sudo指令例如

sudo npm install -g express

bull Linux系統可以使用 root權限登入或是以「sudo su -」切換成 root身分(使用 root權限操作系統相當危險因此並不建議使用這種方式)

若加上 -g參數使用 npm install -g coffee-script完成安裝

後就可以在終端機執行 coffee指令例如

coffee -v

52 使用 NPM安裝套件 47

Nodejs中文電子書

執行結果(範例)

CoffeeScript version 120

53 套件的更新及維護

除了前一節說明的 search 及 install 用法npm 還提供其他許多指令(commands)

使用 npm help可以查詢可用的指令

npm help

執行結果(部分)

where ltcommandgt is one of

adduser apihelp author bin bugs c cache completion

config deprecate docs edit explore faq find get

help help-search home i info init install la link

list ll ln login ls outdated owner pack prefix

prune publish r rb rebuild remove restart rm root

run-script s se search set show star start stop

submodule tag test un uninstall unlink unpublish

unstar up update version view whoami

使用 npm help command可以查詢指令的詳細用法例如

npm help list

接下來本節要介紹開發過程常用的 npm指令

使用 list可以列出已安裝套件

npm list

48 5 NPM套件管理工具

Nodejs中文電子書

執行結果(範例)

-- coffee-script120

- express256

- connect185

| -- formidable108

-- mime124

-- mkdirp007

-- qs041

檢視某個套件的詳細資訊例如

npm show express

升級所有套件(如果該套件已發佈更新版本)

npm update

升級指定的套件

npm update express

移除指定的套件

npm uninstall express

54 使用 packagejson

對於正式的 Nodejs專案可以建立一個命名為 packagejson的設

定檔(純文字格式)檔案內容參考範例如下

54 使用 packagejson 49

Nodejs中文電子書

packagejson(範例)

rdquonamerdquo rdquoapplication-namerdquo

rdquoversionrdquo rdquo001rdquo

rdquoprivaterdquo true

rdquodependenciesrdquo

rdquoexpressrdquo rdquo255rdquo

rdquocoffee-scriptrdquo rdquolatestrdquo

rdquomongooserdquo rdquogt= 253rdquo

其中 name與 version依照專案的需求設置

需要注意的是 dependencies的設定它用於指定專案相依的套件名

稱及版本

bull rdquoexpressrdquo rdquo255rdquo

代表此專案相依版本 255的 express套件

bull rdquocoffee-scriptrdquo rdquolatestrdquo

使用最新版的 coffee-script套件(每次更新都會檢查新版)

bull rdquomongooserdquo rdquogt= 253rdquo

使用版本大於 253的 mongoose套件

假設某個套件的新版可能造成專案無法正常運作就必須指定套件的

版本避免專案的程式碼來不及更新以相容新版套件通常在開發初期的

專案需要盡可能維持新套件的相容性(以取得套件的更新或修正)可以

用「gt=」設定最低相容的版本或是使用「latest」設定永遠保持最新

套件

50 5 NPM套件管理工具

6

Express介紹

在前面的 nodejs基礎當中介紹許多許多開設 http的使用方法及介紹以及許多基本的 nodejs基本應用

接下來要介紹一個套件稱為 express [Express](httpexpressjscom)這個套件主要幫忙解決許多 nodejs http server所需要的基本服務讓開發 httpservice變得更為容易不需要像之前需要透過層層模組(module)才有辦法開始編寫自己的程式

這個套件是由 TJ Holowaychuk 製作而成的套件裡面包含基本的路由處理 (route)http資料處理(GETPOSTPUT)另外還與樣板套件(jshtml template engine)搭配同時也可以處理許多複雜化的問題

61 Express安裝

安裝方式十分簡單只要透過之前介紹的 NPM就可以使用簡單的指令安裝指令如下

這邊建議需要將此套件安裝成為全域模組方便日後使用

51

Nodejs中文電子書

62 Express基本操作

express的使用也十分簡單先來建立一個基本的 hello world

var app = require(rsquoexpressrsquo)createServer()

port = 1337

applisten(port)

appget(rsquorsquo function(req res)

ressend(rsquohello worldrsquo)

)

consolelog(rsquostart express servernrsquo)

可以從上面的程式碼發現基本操作與 nodejs http的建立方式沒有太大差異主要差在當我們設定路由時可以直接透過 appget方式設定回應與接受方式

63 Express路由處理

Express對於 http服務上有許多包裝讓開發者使用及設定上更為方便例如有幾個路由設定那我們就統一藉由 appget來處理

Create http server

appget(rsquorsquo function(req res)

ressend(rsquohello worldrsquo)

)

appget(rsquotestrsquo function(req res)

ressend(rsquotest renderrsquo)

)

appget(rsquouserrsquo function(req res)

ressend(rsquouser pagersquo)

)

52 6 Express介紹

Nodejs中文電子書

如上面的程式碼所表示appget可以帶入兩個參數第一個是路徑名稱設定第二個為回應函式 (call back function)回應函式裡面就如同之前的 createServer方法裡面包含 requestresponse兩個物件可供使用使用者就可以透過瀏覽器輸入不同的 url切換到不同的頁面顯示不同的結果

路由設定上也有基本的配對方式讓使用者從瀏覽器輸入的網址可以

是一個變數只要符合型態就可以有對應的頁面產出例如

Create http server

appget(rsquouseridrsquo function(req res)

ressend(rsquouser rsquo + reqparamsid)

)

appget(rsquonumberrsquo function(req res)

ressend(rsquonumber rsquo + reqparamsnumber)

)

裡 面 使 用 到number 從 網 址 輸 入 之 後 就 可 以 直 接 使 用reqparamsnumber 取得所輸入的資料變成 url 參數使用當然前面也是可以加上路徑的設定userid在瀏覽器上路徑必須符合userxxx透

過 reqparamsid就可以取到 xxx這個字串值

另外express參數處理也提供了路由參數配對處理也可以透過正規表示法作為參數設定

var app = require(rsquoexpressrsquo)createServer()

port = 1337

applisten(port)

appget(^ip((d23)((d23))((d23))((d23))) function(req res)

ressend(reqparams)

)

上面程式碼可以發現後面路由設定的型態是正規表示法裡面設定

格式為ip之後必須要加上 ip型態才會符合資料格式同時取得 ip資料已經由正規表示法將資料做分群因此可以取得 ip的四個數字

此程式執行之後可以透過瀏覽器測試輸入網址為 localhost3000ip25525510010可以從頁面獲得資料

63 Express路由處理 53

Nodejs中文電子書

此章節全部範例程式碼如下

overview

author Caesar Chi

blog clonnblogspotcom

version 20120226

create server

var app = require(rsquoexpressrsquo)createServer()

port = 1337

applisten(port)

normal style

appget(rsquorsquo function(req res)

ressend(rsquohello worldrsquo)

)

appget(rsquotestrsquo function(req res)

ressend(rsquotest renderrsquo)

)

parameter style

appget(rsquouseridrsquo function(req res)

ressend(rsquouser rsquo + reqparamsid)

)

appget(rsquonumberrsquo function(req res)

ressend(rsquonumber rsquo + reqparamsnumber)

)

REGX style

appget(^ip((d23)((d23))((d23))) function(req res)

ressend(reqparams)

)

appget(rsquorsquo function(req res)

ressend(rsquoPage not foundrsquo 404)

54 6 Express介紹

Nodejs中文電子書

)

consolelog(rsquostart express servernrsquo)

64 Express middleware

Express 裡面有一個十分好用的應用概念稱為 middleware可以透過middleware做出複雜的效果同時上面也有介紹 next方法參數傳遞就是靠 middleware的概念來傳遞參數讓開發者可以明確的控制程式邏輯

create http server

appuse(expressbodyParser())

appuse(expressmethodOverride())

appuse(expresssession())

上面都是一種 middleware的使用方式透過 appuse方式裡面載入函式執行方法回應函式會包含三個基本參數responserequestnext其中next表示下一個 middleware執行函式同時會自動將預設三個參數繼續帶往下個函式執行底下有個實驗

上面的片段程式執行後開啟瀏覽器連結上 localhost1337會發現伺服器回應結果順序如下

first middle ware

second middle ware

execute middle ware

end middleware function

從上面的結果可以得知剛才設定的 middleware都生效了在 appuse設定的 middleware是所有 url皆會執行方法如果有指定特定方法就可以使用 appget的 middleware設定在 appget函式的第二個參數就可以帶入函式或者是匿名函式只要函式裡面最後會接受 request response next這三個參數同時也有正確指定 next函式的執行時機最後都會執行到最後一個方法當然開發者也可以評估程式邏輯要執行到哪一個階段讓邏輯

可以更為分明

64 Express middleware 55

Nodejs中文電子書

65 Express路由應用

在實際開發上可能會遇到需要使用參數等方式混和變數一起使用

express裡面提供了一個很棒的處理方法 appall這個方式可以先採用基本路由配對再將設定為每個不同的處理方式開發者可以透過這個方式簡

化自己的程式邏輯

overview

author

version 20120226

create server

var app = require(rsquoexpressrsquo)createServer()

port = 1337

users = [

name rsquoClonnrsquo

name rsquoChirsquo

]

applisten(port)

appall(rsquouseridoprsquo function(req res next)

requser = users[reqparamsid]

if (requser)

next()

else

next(new Error(rsquocannot find user rsquo + reqparamsid))

)

appget(rsquouseridrsquo function(req res)

ressend(rsquoviewing rsquo + requsername)

)

appget(rsquouserideditrsquo function(req res)

ressend(rsquoediting rsquo + requsername)

)

56 6 Express介紹

Nodejs中文電子書

appget(rsquouseriddeletersquo function(req res)

ressend(rsquodeleting rsquo + requsername)

)

appget(rsquorsquo function(req res)

ressend(rsquoPage not foundrsquo 404)

)

consolelog(rsquostart express servernrsquo)

內部宣告一組預設的使用者分別給予名稱設定藉由 appall這個方法可以先將路由雛形建立再接下來設定 appget的路徑格式只要符合格式就會分配進入對應的方法中像上面的程式當中如果使用者輸入路徑為user0除了執行 appall程式之後執行 next方法就會對應到路徑設定為userid的這個方法當中如果使用者輸入路徑為user0edit就會執行到useridedit的對應方法

66 Express GET應用範例

我們準備一個使用 GET方法傳送資料的表單

ltDOCTYPE htmlgt

lthtmlgt

ltheadgt

ltmeta http-equiv=rdquoContent-Typerdquo content=rdquotexthtml charset=utf8rdquogt

lttitlegtNodejs 菜鳥筆記 (1)lttitlegt

ltlink rel=rdquostylesheetrdquo href=rdquocssstylecssrdquo type=rdquotextcssrdquo media=rdquoallrdquo gt

ltheadgt

ltbodygt

lth1gtNodejs 菜鳥筆記-註冊lth1gt

ltform id=rdquosignuprdquo method=rdquoGETrdquo action=rdquohttplocalhost3000Signuprdquogt

ltlabelgt 使用者名稱ltlabelgtltinput type=rdquotextrdquo id=rdquousernamerdquo name=rdquousernamerdquo gtltbrgt

ltlabelgt 電子郵件ltlabelgtltinput type=rdquotextrdquo id=rdquoemailrdquo name=rdquoemailrdquo gtltbrgt

ltinput type=rdquosubmitrdquo value=rdquo 註冊我的帳號rdquo gtltbrgt

ltformgt

ltbodygt

lthtmlgt

66 Express GET應用範例 57

Nodejs中文電子書

這個表單沒有什麼特別的地方我們只需要看第 9 行form 使用的method是 GET然後 action是rdquohttplocalhost3000Signupldquo等一下我們要來撰寫Signup這個 URL Path的處理程式

處理 Signup行為

我們知道所謂的 GET方法會透過 URL來把表單的值給帶過去以上面的表單來說到時候 URL會以這樣的形式傳遞

httplocalhost3000Signupusername=xxxampemail=xxx

所以要能處理這樣的資料必須有以下功能

bull 解析 URL

bull 辨別動作是 Signup

bull 解析出 username和 email

一旦能取得 username和 email的值程式就能加以應用了

處理 Signup的程式碼雛形

load module

var url = require(rsquourlrsquo)

urlData = urlparse(requrltrue)

action = urlDatapathname

reswriteHead(200 rdquoContent-Typerdquordquotexthtml charset=utf-8rdquo)

if (action === rdquoSignuprdquo)

user = urlDataquery

resend(rdquolth1gtrdquo + userusername + rdquo 歡迎您的加入 lth1gtltpgt 我們已經將會員啟用信寄至rdquo + useremail + rdquoltpgtrdquo)

首先需要加載 url module它是用來協助我們解析 URL的模組接著使用 urlparse方法第一個傳入 url字串變數也就是 requrl另外第二個參數的用意是設為 ture則引進 querystring模組來協助處理預設是 false它影響到的是 urlDataquery設為 true會傳回物件不然就只是一般的字串urlparse會將字串內容整理成一個物件我們把它指定給 urlData

action變數作為記錄 pathname這是我們稍後要來判斷目前網頁的動作是什麼接著先將 html表頭資訊 (Header)準備好再來判斷路徑邏輯如

58 6 Express介紹

Nodejs中文電子書

果是 Signup這個動作就把 urlDataquery裡的資料指定給 user然後輸出userusername和 useremail把使用者從表單註冊的資料顯示於頁面中

最後進行程式測試啟動 nodejs主程式之後開啟瀏覽器就會看到表單填寫完畢按下送出就可以看到結果了

完整 nodejs程式碼如下

var http = require(rsquohttprsquo)

url = require(rsquourlrsquo)

fs = require(rdquofsrdquo)

server

server = httpcreateServer(function (reqres)

var urlData

encode = rdquoutf8rdquo

filePath = rdquoviewexpress_get_example_formhtmlrdquo

action

urlData = urlparse(requrltrue)

action = urlDatapathname

reswriteHead(200 rdquoContent-Typerdquordquotexthtml charset=utf-8rdquo)

if (action === rdquoSignuprdquo)

user = urlDataquery

resend(rdquolth1gtrdquo + userusername + rdquo 歡迎您的加入 lth1gtltpgt 我們已經將會員啟用信寄至rdquo + useremail + rdquoltpgtrdquo)

else

fsreadFile(filePath encode function(err file)

reswrite(file)

resend()

)

)

serverlisten(3000)

consolelog(rsquoServer 跑起來了現在時間是rsquo + new Date())

66 Express GET應用範例 59

Nodejs中文電子書

67 Express POST應用範例

一開始準備基本的 html表單傳送內容以 POST方式form的 action屬性設定為 POST其餘 html內容與前一個範例應用相同

ltDOCTYPE htmlgt

lthtmlgt

ltheadgt

ltmeta http-equiv=rdquoContent-Typerdquo content=rdquotexthtml charset=utf8rdquogt

lttitlegtNodejs 菜鳥筆記 (1)lttitlegt

ltlink rel=rdquostylesheetrdquo href=rdquocssstylecssrdquo type=rdquotextcssrdquo media=rdquoallrdquo gt

ltheadgt

ltbodygt

lth1gtNodejs 菜鳥筆記-註冊lth1gt

ltform id=rdquosignuprdquo method=rdquoPOSTrdquo action=rdquohttplocalhost3000Signuprdquogt

ltlabelgt 使用者名稱ltlabelgtltinput type=rdquotextrdquo id=rdquousernamerdquo name=rdquousernamerdquo gtltbrgt

ltlabelgt 電子郵件ltlabelgtltinput type=rdquotextrdquo id=rdquoemailrdquo name=rdquoemailrdquo gtltbrgt

ltinput type=rdquosubmitrdquo value=rdquo 註冊我的帳號rdquo gtltbrgt

ltformgt

ltbodygt

lthtmlgt

nodejs的程式處理邏輯與前面 GET範例類似部分程式碼如下

qs = require(rsquoquerystringrsquo)

if (action === rdquoSignuprdquo)

formData = rsquorsquo

reqon(rdquodatardquo function (data)

formData += data

)

reqon(rdquoendrdquo function ()

user = qsparse(formData)

resend(rdquolth1gtrdquo + userusername + rdquo 歡迎您的加入 lth1gtltpgt 我們已經將會員啟用信寄至rdquo + useremail + rdquoltpgtrdquo)

)

60 6 Express介紹

Nodejs中文電子書

主要加入了rsquoquerystringrsquo這個moduel方便我們等一下解析由表單 POST回來的資料另外加入一個 formData的變數用來搜集待等一下表單回傳的資料前面的 GET範例我們只從 req拿出 url的資料這次要在利用req身上的事件處理

JavaScript 在訂閱事件時使用 addEventListener而 nodejs 使用的則是on這邊加上了監聽 data的事件會在瀏覽器傳送資料到Web Server時被執行參數是它所接收到的資料型態是字串

接著再增加 end的事件當瀏覽器的請求事件結束時它就會動作

由於瀏覽器使用 POST在上傳資料時會將資料一塊塊地上傳因為我們在監聽 data事件時透過 formData變數將它累加起來lt不過由於我們

上傳的資料很少一次就結束不過如果日後需要傳的是資料比較大的檔

案這個累加動作就很重要

當資料傳完就進到 end 事件中會用到 qsparse 來解析 formDataformData的內容是字串內容是

username=wordsmithampemail=wordsmith40somewhere

而 qsparse可以幫我們把這個 querystring轉成物件的格式也就是

username=wordsmithampemail=wordsmith40somewhere

一旦轉成物件並指定給 user之後其他的事情就和 GET方法時操作的一樣寫 response的表頭將內容回傳並將 userusername和 useremail代入到內容中

修改完成後接著執行 nodejs程式啟動 web server開啟瀏覽器進入表單測試看看POST的方式能否順利運作

完整程式碼如下

var http = require(rsquohttprsquo)

url = require(rsquourlrsquo)

fs = require(rdquofsrdquo)

qs = require(rsquoquerystringrsquo)

server

server = httpcreateServer(function (reqres)

var urlData

67 Express POST應用範例 61

Nodejs中文電子書

encode = rdquoutf8rdquo

filePath = rdquoviewexpress_post_example_formhtmlrdquo

formData

action

urlData = urlparse(requrltrue)

action = urlDatapathname

reswriteHead(200 rdquoContent-Typerdquordquotexthtml charset=utf-8rdquo)

if (action === rdquoSignuprdquo)

formData = rsquorsquo

reqon(rdquodatardquo function (data)

formData += data

)

reqon(rdquoendrdquo function ()

user = qsparse(formData)

resend(rdquolth1gtrdquo + userusername + rdquo 歡迎您的加入 lth1gtltpgt 我們已經將會員啟用信寄至rdquo + useremail + rdquoltpgtrdquo)

)

else

fsreadFile(filePath encode function(err file)

reswrite(file)

resend()

)

)

serverlisten(3000)

consolelog(rsquoServer 跑起來了現在時間是rsquo + new Date())

62 6 Express介紹

Nodejs中文電子書

68 Express AJAX應用範例

在 Nodejs要使用 Ajax傳送資料並且與之互動在接受資料的部份沒有太大的差別client端不是用 GET就是用 POST來傳資料重點在處理完後用 JSON格式回傳當然 Ajax不見得只傳 JSON格式有時是回傳一段 HTML碼不過後者對伺服器來說基本上就和前兩篇沒有差別了所以我們還是以回傳 JSON做為這一回的主題

這一回其實大多數的工作都會落在前端 Ajax上面前端要負責發送與接收資料並在接收資料後撤掉原先發送資料的表單並將取得的資料

改成 HTML格式之後放上頁面

首先先準備 HTML靜態頁面資料

ltDOCTYPE htmlgt

lthtmlgt

ltheadgt

lttitlegtNodejs 菜鳥筆記 (3)lttitlegt

ltlink rel=rdquostylesheetrdquo href=rdquocssstylecssrdquo type=rdquotextcssrdquo media=rdquoallrdquo gt

ltscript src=rdquohttpsajaxgoogleapiscomajaxlibsjquery171jqueryminjsrdquogtltscriptgt

ltheadgt

ltbodygt

lth1gtNodejs 菜鳥筆記-註冊 (用 Ajax)lth1gt

ltform id=rdquosignuprdquo action=rdquoSignuprdquo method=rdquoPOSTrdquogt

ltlabelgt 使用者名稱ltlabelgtltinput type=rdquotextrdquo id=rdquousernamerdquo name=rdquousernamerdquo gtltbrgt

ltlabelgt 電子郵件ltlabelgtltinput type=rdquotextrdquo id=rdquoemailrdquo name=rdquoemailrdquo gtltbrgt

ltinput type=rdquosubmitrdquo value=rdquo 註冊我的帳號rdquo gtltbrgt

ltformgt

ltdiv id=rdquoresultrdquogt

ltdivgt

ltscript type=rdquotextjavascriptrdquogt

$(function()$(rsquosignup input[type=rdquosubmitrdquo]rsquo)click(function(e)

var user =

userusername = $(rdquousernamerdquo)val()useremail = $(rdquoemailrdquo)val()

$post(rdquoSignuprdquouserfunction(data)greet(data)

)

68 Express AJAX應用範例 63

Nodejs中文電子書

epreventDefault()

)

var greet = function(msg)

var resultNode = $(rsquoresultrsquo)greeting = $(rsquolth2gtHirsquo+msgusername+rsquo 你的會員 id 是rsquo+ msgid +rsquo 我們會將會員啟動信寄至rsquo+msgemail+rsquolth2gtrsquo)

$(rsquosignuprsquo)hide()resultNodehtml(rdquordquo)

resultNodeappend(greeting)

)

ltscriptgt

ltbodygt

lthtmlgt

HTML頁面上準備了一個表單用來傳送註冊資料接著直接引用了Google CDN來載入 jQuery用來幫我們處理 Ajax的工作這次要傳送和接收的工作很大的變動都在 HTML頁面上的 JavaScript當中我們要做的事有 (相關 jQuery處理這邊不多做贅述指提起主要功能解說)

bull 用 jQUery取得 submit按鈕綁定它的 click動作

bull 取得表單 username和 email的值存放在 user這個物件中

bull 用 jQuery的 $post方法將 user的資料傳到 Server

bull 一旦成功取得資料後透過 greet這個 function組成回報給 user的訊息

bull 清空原本給使用者填資料的表單

bull 將 Server回傳的 usernameemail和 id這 3個資料組成回應的訊息

bull 將訊息放到原本表單的位置

經過以上的處理後一個 Ajax的表單的基本功能已經完成

接著進行 nodejs主要程式的編輯部分程式碼如下

var fs = require(rdquofsrdquo)

64 6 Express介紹

Nodejs中文電子書

qs = require(rsquoquerystringrsquo)

if (action === rdquoSignuprdquo)

formData = rsquorsquo

reqon(rdquodatardquo function (data)

formData += data

)

reqon(rdquoendrdquo function ()

var msg

user = qsparse(formData)

userid = rdquo123456rdquo

msg = JSONstringify(user)

reswriteHead(200 rdquoContent-Typerdquordquoapplicationjson charset=utf-8rdquordquoContent-Lengthrdquomsglength)

resend(msg)

)

這裡的程式和前面 POST範例基本上大同小異差別在

bull 幫 user的資料加上 id隨意存放一些文字進去讓 Server回傳的資料多於 Client端傳上來的不然會覺得 Server都沒做事

bull 增加了 msg 這個變數存放將 user 物件 JSON 文字化的結果JSONstringify這個轉換函式是 V8引擎所提供的如果你好奇的話

bull 大重點來了我們要告訴 Client端這次回傳的資料格式是 JSON所在 Content-type和 Content-Length要提供給 Client

Server很輕鬆就完成任務了最後進行程式測試啟動 nodejs主程式之後開啟瀏覽器就會看到表單填寫完畢按下送出就可以看到結果了

最後 nodejs本篇範例程式碼如下

var http = require(rsquohttprsquo)

url = require(rsquourlrsquo)

fs = require(rdquofsrdquo)

qs = require(rsquoquerystringrsquo)

server

68 Express AJAX應用範例 65

Nodejs中文電子書

server = httpcreateServer(function (reqres)

var urlData

encode = rdquoutf8rdquo

filePath = rdquoviewexpress_ajax_example_formhtmlrdquo

formData

action

urlData = urlparse(requrltrue)

action = urlDatapathname

if (action === rdquoSignuprdquo)

formData = rsquorsquo

reqon(rdquodatardquo function (data)

formData += data

)

reqon(rdquoendrdquo function ()

var msg

user = qsparse(formData)

userid = rdquo123456rdquo

msg = JSONstringify(user)

reswriteHead(200 rdquoContent-TyperdquordquoapplicationjsonrdquordquoContent-Lengthrdquomsglength)

resend(msg)

)

else

fsreadFile(filePath encode function(err file)

reswriteHead(200 rdquoContent-Typerdquordquotexthtml charset=utf-8rdquo)

reswrite(file)

resend()

)

)

serverlisten(3000)

66 6 Express介紹

Nodejs中文電子書

consolelog(rsquoServer 跑起來了現在時間是rsquo + new Date())

69 原始資料提供

bull [NodeJS初學者筆記 (1)-用 GET傳送資料] (httpithelpithomecomtwquestion10087402)

bull [NodeJS初學者筆記 (2)-用 POST傳送資料] (httpithelpithomecomtwquestion10087489)

bull [NodeJS 初學者筆記 (3)-用 Ajax 傳送資料] (httpithelpithomecomtwquestion10087627)

69 原始資料提供 67

Nodejs中文電子書

68 6 Express介紹

7

CoffeeScript

bull Spine

bull Hem

bull Spineapp

Letrsquos Have a Cup of CoffeeScript

bull es5-shim ECMAScript 5 compatibility shims for legacy JavaScript engines

ESnext

node-iform - A middleware for node-validator httpsgithubcomguileennode-iform

69

Nodejs中文電子書

70 7 CoffeeScript

8

製作一個 Hubot的 Plurk Adapter

81 應用事項提醒

此應用範例以CoffeeScript編寫主要程式架構採用Github釋放的Hubot專案以下有幾個主要注意事項請讀者注意

bull Hubot 一開始的架構除了內建的兩個 Adapter 之外其餘都要以Nodejs的Module方式才能運作

bull Hubot的 binhubot裡面寫著 npm install所以不管你怎麼改原始碼也不能改變第一點的狀況

其實上述都在討論同一件事情NodeJS的模組而且模組不能設定相對路徑之類的來安裝一定要包裝成 npm相符和格式才有辦法正確執行

82 建立 Adapter

首先需要準備兩個檔案

bull packagejson

bull plurkcoffee

71

Nodejs中文電子書

有這兩個就足以變成 NodeJS的模組了在開始 Coding之前先來設定一下 packagejson相依性

rdquonamerdquo rdquohubot-plurkrdquo

rdquoversionrdquo rdquo011rdquo

rdquomainrdquo rdquoplurkrdquo

rdquodependenciesrdquo

rdquohubotrdquo rdquogt=205rdquo

rdquooauthrdquo rdquordquo

rdquocronrdquordquordquo

這邊會使用到NodeJS的 cron模組(Module)而登入 Plurk需要OAuth標準授權才行所以也需要加載 OAuth模組

注意Hubot在讀取非內建模組時會自動在前面加上 hubot-的前置

83 建立 Robot跟 API

本篇應用基本上程式碼大部分參考 Hubot - Twitter Adapter來製作主要差異只有在使用 OAuth這個模組Twitter有 Streaming可用而 Plurk則得用 Comet方式來達到即時讀取

plurkcoffee程式碼

Robot = require(rdquohubotrdquo)robot()

Adapter = require(rdquohubtordquo)adapter()

EventEmitter = require(rdquoeventsrdquo)EventEmitter

oauth = require(rdquooauthrdquo)

cronJob = require(rdquocronrdquo)CronJob

class Plurk exntends Adapter

class PlurkStreaming exnteds EventEmitter

72 8 製作一個 Hubot的 Plurk Adapter

Nodejs中文電子書

先弄個基本架構主要 Class皆參考 Twitter Adapter命名方式接著再將 Plurk這個 Class增加幾個Method基本上只要有 run send reply就夠了而 run用來做初始化的部分

class Plurk entends Adapter

send (plurk id strings⋯) -gt

reply (plurk id strings⋯) -gt

run -gt

看起來有點東西接著來處理主要的 Plurk API結合部分

class PlurkStreaming extends EventEmitter

consuctor (options) -gt

plurk (callback) -gt

觀察河道

getChannel -gt

取得 Comet 網址

reply (plurk id message) -gt

回噗

acceptFriends -gt

接受好友

get (path callback) -gt

GET 請求

post (path body callback)-gt

POST 請求(其實是裝飾)

request (method path body callback)-gt

主要的 OAuth 請求

comet (server callback)-gt

噗浪的 Comet 傳回是 JavaScript Callback 要另外處理後才會變成 JSON

然後把注意力集中到 constructor上先把建構子弄好

constructor (options) -gt

super()

if optionskey and optionssecret and optionstoken and optionstoken secret

key = optionskey

secret = optionssecret

token = optionstoken

token secret = optionstoken secret

83 建立 Robot跟 API 73

Nodejs中文電子書

建立 OAuth 連接

consumer = new oauthOAuth(

rdquohttpwwwplurkcomOAuthrequest tokenrdquo

rdquohttpwwwplurkcomOAuthaccess tokenrdquo

key

secret

rdquo10rdquo

rdquohttpwwwplurkcomOAuthauthorizerdquo

rdquoHMAC-SHA1rdquo

)

domain = rdquowwwplurkcomrdquo

初始化取得 Comet 網址

do getChannel

else

throw new Error(rdquo 參數不足需要 Key Secret Token Token Secretrdquo)

接著來處理 request這個 method

request (method path body callback) -gt

記錄一下這次的 Request

consolelog(rdquohttpdomainpathrdquo)

Callback 這邊先不丟進去要用另一種方式處理

request = consumerget(rdquohttpdomainpathrdquo token token secret null)

requeston rdquoresponserdquo (res) -gt

reson rdquodatardquo (chunk) -gt

parseResponse(chunk+rsquorsquo callback)

reson rdquoendrdquo (data) -gt

consolelog rdquoEnd Request pathrdquo

reson rdquoerrorrdquo (data) -gt

consolelog rdquoError rdquo + data

requestend()

處理資料

parseResponse = (data callback) -gt

if datalength gt 0

用 TryCatch 避免處理 JSON 出錯導致整個中斷

try

callback null JSONparse(data)

catch err

74 8 製作一個 Hubot的 Plurk Adapter

Nodejs中文電子書

consolelog(rdquoError Parse JSONrdquo + data err)

繼續執行

callback null data ||

大致上就是這樣上面程式的架構已經將整個 Hubot Plurk Adapter完成因為在測試時竟然因為噗浪 Lag而沒讀到完整的 Comet資料然後造成程式異常為了避免這個問題發生因此需要加上為了完美呈現需要再

加上 Comet的處理所以要使用到 EventEmitter的功能

comet (server callback) -gt

在 Callback 裡面會找不到自身所以設定區域變數

self =

記錄一下這次的 Request

consolelog(rdquo[Comet] serverrdquo)

Callback 這邊先不丟進去要用另一種方式處理

request = consumerget(rdquohttpdomainpathrdquo token token secret null)

requeston rdquoresponserdquo (res) -gt

reson rdquodatardquo (chunk) -gt

parseResponse(chunk+rsquorsquo callback)

reson rdquoendrdquo (data) -gt

consolelog rdquoEnd Request pathrdquo

請求結束發出事件通知可以進行下一次請求

selfemit rdquonextPlurkrdquo

reson rdquoerrorrdquo (data) -gt

consolelog rdquoError rdquo + data

requestend()

處理資料

parseResponse = (data callback) -gt

if datalength gt 0

用 trycatch 避免失敗中斷

try

去掉 JavaScript 的 Callback

data = datamatch(CometChannelscriptCallback((+))s)

jsonData = rdquordquo

if data

83 建立 Robot跟 API 75

Nodejs中文電子書

jsonData = JSONparse(data[1])

else

如果沒有任何 Match 嘗試直接 parse

jsonData = JSONparse(data)

catch err

consolelog(rdquo[Comet] Errorrdquo data err)

用 TryCatch 避免處理 JSON 出錯導致整個中斷

try

只傳入 json 的 data 部分

callback null jsonDatadata

catch err

consolelog(rdquo[Comet]Error Parse JSONrdquo + data err)

繼續執行

callback null data ||

後面的 get跟 post就簡單多了

get (path callback) -gt

request(rdquoGETrdquo path null callback)

post (path body callback) -gt

request(rdquoPOSTrdquo path body callback)

接著處理取的 Comet網址的 getChannel

getChannel -gt

self =

get rdquoAPPRealtimegetUserChannelrdquo (error data) -gt

if error

檢查是否有 comet server

if datacomet server

selfchannel = datacomet server

如果沒有 Channel Ready 就嘗試連接會失敗

selfemit(rsquochannel readyrsquo)

那麼先來處理 Plurk Adaper好處理的部份

send (plruk id strings⋯)-gt

跟 Reply 一樣直接交給 reply 做

reply plurk id strings⋯

76 8 製作一個 Hubot的 Plurk Adapter

Nodejs中文電子書

reply (plurk id strings⋯) -gt

stringsforEach (message) =gt

botreply(plruk id message)

接著把 run處理好就可以上線運作摟

run -gt

self =

options =

key processenvHUBOT PLURK KEY

secret processenvHUBOT PLURK SECRET

token processenvHUBOT PLURK TOKEN

token secret processenvHUBOT PLURK TOKEN SECRET

創建剛剛的 API

bot = new PlurkStreaming(options)

依照 Twitter 的 new RobotTextMessage 會沒有反應所以參考 hubot-minecraft 的方式

r = robotconstructor

處理噗浪河道訊息

doPlurk = (data)-gt

檢查是否為回噗

if dataresponse

datacontent raw = dataresponsecontent raw

datauser id = dataresponseuser id

確定有噗浪 ID 跟訊息

if dataplurk id and datacontent raw

selfreceive new rTextMessage(dataplurk id datacontent raw)

取得 Comet Server 完成開始第一次 Comet 連接

boton rdquochannel readyrdquo () -gt

botplurk selfdoPlurk

上一次 Comet 完成繼續 Polling

boton rdquonextPlurkrdquo ()-gt

botplurk selfdoPlurk

定時接受好友邀請

do botacceptFriends

bot = bot

83 建立 Robot跟 API 77

Nodejs中文電子書

終於完成 AdapterHubot專案裡面的 scripts資料夾內是互動部分不需要像 Adapter如此大費周章處理只新增檔案並且設計好對白之後就會回噗了我開發用的機器人在此大家可以去跟他玩玩[httpplurkcomelct9620 bot](httpplurkcomelct9620 bot)

84 原始資料提供

bull [製 作 一 個 Hubot 的 噗 浪 Adapter](http revo-skillfrosttw blog20120318create-a-hubot-plurk-adapter)

78 8 製作一個 Hubot的 Plurk Adapter

9

Nodejs好用工具介紹

91 logio

Real-time log monitoring in your browser

httplogioorg

nodejs (gt=0412 lt=0611)

npm config set unsafe-perm true

npm install -g --prefix=usrlocal logio

logio server start

httplocalhost8998

79

Nodejs中文電子書

80 9 Nodejs好用工具介紹

10

精選文章

101 Express

bull 使用 NodeJS + Express從 GETPOST Request取值 [邀稿中]

bull link2 [邀稿中]

bull link3 [邀稿中]

81

Nodejs中文電子書

82 10 精選文章

11

參考資源

111 Nodejs書籍

bull Programming Nodejs April 2012 (est) Travis Swicegood Pragmatic Bookshelf

bull e Little Book on CoffeeScript January 2012 Alex MacCaw OrsquoReilly Media

bull Node for Front-End Developers January 2012 Garann Means OrsquoReilly Media

bull Building Hypermedia APIs with HTML5 and Node November 2011 MikeAmundsen OrsquoReilly Media

bull Node Web Development August 2011 David Herron PacktPub

bull CoffeeScript Accelerated JavaScript Development July 2011 Trevor BurnhamPragmatic Bookshelf

bull Getting Started with GEO CouchDB and Nodejs July 2011 Mick omp-son OrsquoReilly Media

bull What Is Node July 2011 Brett McLaughlin OrsquoReilly Media

bull Node Up and Running May 2011 Tom Hughes-Croucher Mike WilsonOrsquoReilly Media

83

Nodejs中文電子書

bull Hands-on Nodejs e Nodejs introduction and API reference May 2011 Pe-dro Teixeira LeanPub

bull e Node Beginner Book Manuel Kiessling

bull Mastering Nodejs visionmedia

112 Nodejs影音教學

bull e Node Sessions e Best of OSCON 2011 August 2011 OrsquoReilly Media(Video)

bull Tom Hughes-Croucher on Node March 2011 OrsquoReilly Media (Video)

113 Nodejs教學網站

bull Node Tuts

114 Nodejs課程

bull Introduction To Nodejs Van Nguyen CodeLesson

84 11 參考資源

Page 43: Nodejs Wiki

Nodejs中文電子書

準備一個包含基本路由功能的 http伺服器

var server

ip = rdquo127001rdquo

port = 1337

http = require(rsquohttprsquo)

url = require(rsquourlrsquo)

server = httpcreateServer(function (req res)

var path = urlparse(requrl)

)

serverlisten(port ip)

consolelog(rdquoServer running at httprdquo + ip + rdquordquo + port)

加入 le system 模組使用 readFile 的功能將這一段程式放置於

createServer的回應函式中

fsreadFile(filePath encode function(err file)

)

readFile的回應函式裡面加入頁面輸出讓瀏覽器可以正確讀到檔案在這邊我們設定讀取的檔案為 html 靜態檔案所以 Content-type 設定為texthtml讀取到檔案的內容將會正確輸出成 html靜態檔案

fsreadFile(filePath encode function(err file)

reswriteHead(200 rsquoContent-Typersquo rsquotexthtmlrsquo)

reswrite(file)

resend()

)

到這邊為止基本的程式內容都已經完成剩下一些細節的調整首先

路徑上必須做調整目前的靜態檔案全部都放置於 static資料夾底下設定一個變數來記住資料夾位置

接著將瀏覽器發出要求路徑與資料夾組合讀取正確 html靜態檔案使用者有可能會輸入錯誤路徑所以在讀取檔案的時候要加入錯誤處理

同時回應 404伺服器無法正確回應的 http header格式

加入這些細節的修改一個基本的 http 靜態 html輸出伺服器就完成了完整程式碼如下

44 nodejs http靜態檔案輸出 37

Nodejs中文電子書

var server

ip = rdquo127001rdquo

port = 1337

http = require(rsquohttprsquo)

fs = require(rdquofsrdquo)

folderPath = rdquostaticrdquo

url = require(rsquourlrsquo)

path

filePath

encode = rdquoutf8rdquo

server = httpcreateServer(function (req res)

path = urlparse(requrl)

filePath = folderPath + pathpathname

fsreadFile(filePath encode function(err file)

if (err)

reswriteHead(404 rsquoContent-Typersquo rsquotextplainrsquo)

resend()

return

reswriteHead(200 rsquoContent-Typersquo rsquotextapplicationrsquo)

reswrite(file)

resend()

)

)

serverlisten(port ip)

consolelog(rdquoServer running at httprdquo + ip + rdquordquo + port)

45 nodejs http GET資料擷取

http伺服器中除了路由之外另一個最常使用的方法就是擷取 GET資料本單元將會介紹如何透過基本 http伺服器擷取瀏覽器傳來的要求擷取 GET資料

在 http協定中GET參數都是藉由 URL從瀏覽器發出要求送至伺服器

38 4 Nodejs基礎

Nodejs中文電子書

端基本的傳送網址格式可能如下

http127001testsend=1amptest=2

上面這段網址裡面的 GET參數就是 send而這個變數的數值就為 1如果想要在 http伺服器取得 GET資料需要在瀏覽器給予的要求 (request)做處理

首先需要載入 query string這個模組這個模組主要是用來將字串資料

過濾後轉換成javascript物件

qs = require(rsquoquerystringrsquo)

接著在第一階段利用 url模組過濾瀏覽器發出的 URL資料後將回應的物件裡面的 query這個變數是一個字串值資料過濾後如下

send=1amptest=2

透過 query string使用 parse這個方法將資料轉換成 javascript物件就表示 GET的資料已經被伺服器端正式擷取下來

path = urlparse(requrl)

parameter = qsparse(pathquery)

整個 nodejs http GET參數完整擷取程式碼如下

var server

ip = rdquo127001rdquo

port = 1337

http = require(rsquohttprsquo)

qs = require(rsquoquerystringrsquo)

url = require(rsquourlrsquo)

server = httpcreateServer(function (req res)

var path = urlparse(requrl)

parameter = qsparse(pathquery)

consoledir(parameter)

reswriteHead(200 rsquoContent-Typersquo rsquotextplainrsquo)

reswrite(rsquoBrowser test GET parameternrsquo)

resend()

)

45 nodejs http GET資料擷取 39

Nodejs中文電子書

serverlisten(port ip)

consolelog(rdquoServer running at httprdquo + ip + rdquordquo + port)

程式運作之後由瀏覽器輸入要求網址之後nodejs伺服器端回應資料為

Server running at http1270011337

send rsquo1rsquo test rsquo1rsquo

46 本章結語

前面所解說的部份一大部分主要是處理 http伺服器基本問題雖然在某些部分有牽扯到 http 伺服器基本運作原理主要還是希望可以藉由這些基本範例練習 nodejs練習回應函式與語法串接的特點習慣編寫javascript風格程式當然直接這樣開發 nodejs是非常辛苦的接下來在模組實戰開發的部份將會介紹特定的模組一步一步帶領各位從無到有進行

nodejs應用程式開發

40 4 Nodejs基礎

5

NPM套件管理工具

npm全名為 Node Package Manager是 Nodejs的套件(package)管理工具類似 Perl的 ppm或 PHP的 PEAR等安裝 npm後使用npm install

module name指令即可安裝新套件維護管理套件的工作會更加輕鬆

npm可以讓Nodejs的開發者直接利用擴充線上的套件庫(packagesregistry)加速軟體專案的開發npm提供很友善的搜尋功能可以快速找到安裝需要的套件當這些套件發行新版本時npm也可以協助開發者自動更新這些套件

npm不僅可用於安裝新的套件它也支援搜尋列出已安裝模組及更新的功能

51 安裝 NPM

Nodejs在 063版本開始內建 npm讀者安裝的版本若是此版本或更新的版本就可以略過以下安裝說明

若要檢查 npm是否正確安裝可以使用以下的指令

npm -v

41

Nodejs中文電子書

執行結果說明

若 npm正確安裝執行 npm -v將會看到類似 110-2的版本訊息

若讀者安裝的 Nodejs版本比較舊或是有興趣嘗試自己動手安裝 npm工具則可以參考以下的說明

安裝於Windows系統

Nodejs for Windows 於 062 版開始內建 npm使用 nodejsorg 官方提供的安裝程式不需要進一步的設定就可以立即使用 npm指令對於Windows的開發者來說大幅降低環境設定的問題與門檻

除了使用 Nodejs內建的 npm讀者也可以從 npm官方提供的以下網址

httpnpmjsorgdist

這是由 npm提供的 Fancy Windows Install版本請下載壓縮檔(例如npm-110-3zip)並將壓縮檔內容解壓縮至 Nodejs的安裝路徑(例如CProgram Filesnodejs)

解壓縮後在 Nodejs的安裝路徑下應該有以下的檔案及資料夾

bull npmcmd(檔案)

bull node modules(資料夾)

安裝於 Linux系統

Ubuntu Linux的使用者可以加入 NPM Unoffcial PPA這個 repository即可使用 apt-get完成 npm安裝

42 5 NPM套件管理工具

Nodejs中文電子書

Ubuntu Linux使用 apt-get安裝 npm

sudo apt-get install python-software-properties

sudo add-apt-repository ppagias-kay-leenpm

sudo apt-get update

sudo apt-get install npm

npm官方提供的安裝程式 installsh可以適用於大多數的 Linux系統使用這個安裝程式請先確認

1 系統已安裝 curl工具(請使用 curl --version查看版本訊息)

2 已安裝 Nodejs並且 PATH正確設置

3 Nodejs的版本必須大於 04x

以下為 npm提供的安裝指令

curl httpnpmjsorginstallsh | sh

安裝成功會看到如下訊息

installsh安裝成功的訊息

npm10105 homeUSERNAMElocalnodelibnode_modulesnpm

It worked

安裝於Mac OS X

建議採用與 Nodejs相同的方式進行 npm的安裝例如使用MacPorts安裝 Nodejs就同樣使用MacPorts安裝 npm這樣對日後的維護才會更方便容易

使用 MacPorts安裝 npm是本書比較建議的方式它可以讓 npm的安裝移除及更新工作自動化將會幫助開發者節省寶貴時間

51 安裝 NPM 43

Nodejs中文電子書

安裝MacPorts的提示

在MacPorts網站可以取得 OS X系統版本對應的安裝程式(例如 106或 107)httpwwwmacportsorg安裝過程會詢問系統管理者密碼使用預設的選項完成安裝即可安

裝MacPorts之後在終端機執行 port -v將會看到MacPorts的版本訊息

安裝 npm之前先更新MacPorts的套件清單以確保安裝的 npm是最新版本

sudo port -d selfupdate

接著安裝 npm

sudo port install npm

若讀者的 Nodejs並非使用MacPorts安裝則不建議使用MacPorts安裝npm因為 MacPorts會自動檢查並安裝相依套件而 npm相依 nodejs所以MacPorts也會一併將 nodejs套件安裝造成先前讀者使用其它方式安裝的 nodejs被覆蓋

讀者可以先使用 MacPorts安裝 curl(sudo port install curl)

再參考 Linux的 installsh安裝方式即可使用 npm官方提供的安裝程式

NPM安裝後測試

npm是指令列工具(command-line tool)使用時請先打開系統的文字終端機工具

測試 npm安裝與設定是否正確請輸入指令如下

npm -v

或是

npm --version

如果 npm已經正確安裝設定就會顯示版本訊息

44 5 NPM套件管理工具

Nodejs中文電子書

執行結果(範例)

110-2

52 使用 NPM安裝套件

npm目前擁有超過 6000種套件(packages)可以在 npm registry使用關鍵字搜尋套件

httpsearchnpmjsorg

舉例來說在關鍵字欄位輸入「coffee-script」下方的清單就會自動列出包含 coffee-script關鍵字的套件

接著我們回到終端機模式的操作npm的指令工具本身就可以完成套

件搜尋的任務

例如以下的指令同樣可以找出 coffee-script相關套件

npm search coffee-script

以下是搜尋結果的參考畫面

52 使用 NPM安裝套件 45

Nodejs中文電子書

找到需要的套件後(例如 express)即可使用以下指令安裝

npm install coffee-script

值得注意的一點是使用 npm install會將指定的套件安裝在工

作目錄(Working Directory)的 node modules資料夾下

以Windows為例如果執行 npm install的目錄位於

Cproject1

那麼 npm將會自動建立一個 node modules的子目錄(如果不存在)

Cproject1node modules

並且將下載的套件放置於這個子目錄例如

Cproject1node modulescoffee-script

這個設計讓專案可以個別管理相依的套件並且可以在專案佈署或發

行時將這些套件(位於 node modules)一併打包方便其它專案的使用者不必再重新下載套件

這個 npm install的預設安裝模式為 local(本地)只會變更當前專案的資料夾不會影響系統

另一種安裝模式稱為 global(全域)這種模式會將套件安裝到系統資

料夾也就是 npm安裝路徑的 node modules資料夾例如

CProgram Filesnodejsnode modules

46 5 NPM套件管理工具

Nodejs中文電子書

是否要使用全域安裝可以依照套件是否提供新指令來判斷舉例來說express 套件提供 express 這個指令而 coffee-script 則提供 coffee

指令

在 local 安 裝 模 式 中這 些 指 令 的 程 式 檔 案會 被 安 裝 到node modules的 bin這個隱藏資料夾下除非將bin的路徑加入 PATH環境變數否則要執行這些指令將會相當不便

為了方便指令的執行我們可以在 npm install 加上 -g 或 --

global參數啟用 global安裝模式例如

npm install -g coffee-script

npm install -g express

使用 global安裝模式需要注意執行權限的問題若權限不足可能會出現類似以下的錯誤訊息

npm ERR Error EACCES permission denied rsquorsquo

npm ERR

npm ERR Please try running this command again as rootAdministrator

要獲得足夠得執行權限請參考以下說明

bull Windows 7 或 2008 以上在「命令提示字元」的捷徑按右鍵選擇「以系統管理員身分執行」執行 npm指令時就會具有 Administrator身分

bull Mac OS X或 Linux系統可以使用 sudo指令例如

sudo npm install -g express

bull Linux系統可以使用 root權限登入或是以「sudo su -」切換成 root身分(使用 root權限操作系統相當危險因此並不建議使用這種方式)

若加上 -g參數使用 npm install -g coffee-script完成安裝

後就可以在終端機執行 coffee指令例如

coffee -v

52 使用 NPM安裝套件 47

Nodejs中文電子書

執行結果(範例)

CoffeeScript version 120

53 套件的更新及維護

除了前一節說明的 search 及 install 用法npm 還提供其他許多指令(commands)

使用 npm help可以查詢可用的指令

npm help

執行結果(部分)

where ltcommandgt is one of

adduser apihelp author bin bugs c cache completion

config deprecate docs edit explore faq find get

help help-search home i info init install la link

list ll ln login ls outdated owner pack prefix

prune publish r rb rebuild remove restart rm root

run-script s se search set show star start stop

submodule tag test un uninstall unlink unpublish

unstar up update version view whoami

使用 npm help command可以查詢指令的詳細用法例如

npm help list

接下來本節要介紹開發過程常用的 npm指令

使用 list可以列出已安裝套件

npm list

48 5 NPM套件管理工具

Nodejs中文電子書

執行結果(範例)

-- coffee-script120

- express256

- connect185

| -- formidable108

-- mime124

-- mkdirp007

-- qs041

檢視某個套件的詳細資訊例如

npm show express

升級所有套件(如果該套件已發佈更新版本)

npm update

升級指定的套件

npm update express

移除指定的套件

npm uninstall express

54 使用 packagejson

對於正式的 Nodejs專案可以建立一個命名為 packagejson的設

定檔(純文字格式)檔案內容參考範例如下

54 使用 packagejson 49

Nodejs中文電子書

packagejson(範例)

rdquonamerdquo rdquoapplication-namerdquo

rdquoversionrdquo rdquo001rdquo

rdquoprivaterdquo true

rdquodependenciesrdquo

rdquoexpressrdquo rdquo255rdquo

rdquocoffee-scriptrdquo rdquolatestrdquo

rdquomongooserdquo rdquogt= 253rdquo

其中 name與 version依照專案的需求設置

需要注意的是 dependencies的設定它用於指定專案相依的套件名

稱及版本

bull rdquoexpressrdquo rdquo255rdquo

代表此專案相依版本 255的 express套件

bull rdquocoffee-scriptrdquo rdquolatestrdquo

使用最新版的 coffee-script套件(每次更新都會檢查新版)

bull rdquomongooserdquo rdquogt= 253rdquo

使用版本大於 253的 mongoose套件

假設某個套件的新版可能造成專案無法正常運作就必須指定套件的

版本避免專案的程式碼來不及更新以相容新版套件通常在開發初期的

專案需要盡可能維持新套件的相容性(以取得套件的更新或修正)可以

用「gt=」設定最低相容的版本或是使用「latest」設定永遠保持最新

套件

50 5 NPM套件管理工具

6

Express介紹

在前面的 nodejs基礎當中介紹許多許多開設 http的使用方法及介紹以及許多基本的 nodejs基本應用

接下來要介紹一個套件稱為 express [Express](httpexpressjscom)這個套件主要幫忙解決許多 nodejs http server所需要的基本服務讓開發 httpservice變得更為容易不需要像之前需要透過層層模組(module)才有辦法開始編寫自己的程式

這個套件是由 TJ Holowaychuk 製作而成的套件裡面包含基本的路由處理 (route)http資料處理(GETPOSTPUT)另外還與樣板套件(jshtml template engine)搭配同時也可以處理許多複雜化的問題

61 Express安裝

安裝方式十分簡單只要透過之前介紹的 NPM就可以使用簡單的指令安裝指令如下

這邊建議需要將此套件安裝成為全域模組方便日後使用

51

Nodejs中文電子書

62 Express基本操作

express的使用也十分簡單先來建立一個基本的 hello world

var app = require(rsquoexpressrsquo)createServer()

port = 1337

applisten(port)

appget(rsquorsquo function(req res)

ressend(rsquohello worldrsquo)

)

consolelog(rsquostart express servernrsquo)

可以從上面的程式碼發現基本操作與 nodejs http的建立方式沒有太大差異主要差在當我們設定路由時可以直接透過 appget方式設定回應與接受方式

63 Express路由處理

Express對於 http服務上有許多包裝讓開發者使用及設定上更為方便例如有幾個路由設定那我們就統一藉由 appget來處理

Create http server

appget(rsquorsquo function(req res)

ressend(rsquohello worldrsquo)

)

appget(rsquotestrsquo function(req res)

ressend(rsquotest renderrsquo)

)

appget(rsquouserrsquo function(req res)

ressend(rsquouser pagersquo)

)

52 6 Express介紹

Nodejs中文電子書

如上面的程式碼所表示appget可以帶入兩個參數第一個是路徑名稱設定第二個為回應函式 (call back function)回應函式裡面就如同之前的 createServer方法裡面包含 requestresponse兩個物件可供使用使用者就可以透過瀏覽器輸入不同的 url切換到不同的頁面顯示不同的結果

路由設定上也有基本的配對方式讓使用者從瀏覽器輸入的網址可以

是一個變數只要符合型態就可以有對應的頁面產出例如

Create http server

appget(rsquouseridrsquo function(req res)

ressend(rsquouser rsquo + reqparamsid)

)

appget(rsquonumberrsquo function(req res)

ressend(rsquonumber rsquo + reqparamsnumber)

)

裡 面 使 用 到number 從 網 址 輸 入 之 後 就 可 以 直 接 使 用reqparamsnumber 取得所輸入的資料變成 url 參數使用當然前面也是可以加上路徑的設定userid在瀏覽器上路徑必須符合userxxx透

過 reqparamsid就可以取到 xxx這個字串值

另外express參數處理也提供了路由參數配對處理也可以透過正規表示法作為參數設定

var app = require(rsquoexpressrsquo)createServer()

port = 1337

applisten(port)

appget(^ip((d23)((d23))((d23))((d23))) function(req res)

ressend(reqparams)

)

上面程式碼可以發現後面路由設定的型態是正規表示法裡面設定

格式為ip之後必須要加上 ip型態才會符合資料格式同時取得 ip資料已經由正規表示法將資料做分群因此可以取得 ip的四個數字

此程式執行之後可以透過瀏覽器測試輸入網址為 localhost3000ip25525510010可以從頁面獲得資料

63 Express路由處理 53

Nodejs中文電子書

此章節全部範例程式碼如下

overview

author Caesar Chi

blog clonnblogspotcom

version 20120226

create server

var app = require(rsquoexpressrsquo)createServer()

port = 1337

applisten(port)

normal style

appget(rsquorsquo function(req res)

ressend(rsquohello worldrsquo)

)

appget(rsquotestrsquo function(req res)

ressend(rsquotest renderrsquo)

)

parameter style

appget(rsquouseridrsquo function(req res)

ressend(rsquouser rsquo + reqparamsid)

)

appget(rsquonumberrsquo function(req res)

ressend(rsquonumber rsquo + reqparamsnumber)

)

REGX style

appget(^ip((d23)((d23))((d23))) function(req res)

ressend(reqparams)

)

appget(rsquorsquo function(req res)

ressend(rsquoPage not foundrsquo 404)

54 6 Express介紹

Nodejs中文電子書

)

consolelog(rsquostart express servernrsquo)

64 Express middleware

Express 裡面有一個十分好用的應用概念稱為 middleware可以透過middleware做出複雜的效果同時上面也有介紹 next方法參數傳遞就是靠 middleware的概念來傳遞參數讓開發者可以明確的控制程式邏輯

create http server

appuse(expressbodyParser())

appuse(expressmethodOverride())

appuse(expresssession())

上面都是一種 middleware的使用方式透過 appuse方式裡面載入函式執行方法回應函式會包含三個基本參數responserequestnext其中next表示下一個 middleware執行函式同時會自動將預設三個參數繼續帶往下個函式執行底下有個實驗

上面的片段程式執行後開啟瀏覽器連結上 localhost1337會發現伺服器回應結果順序如下

first middle ware

second middle ware

execute middle ware

end middleware function

從上面的結果可以得知剛才設定的 middleware都生效了在 appuse設定的 middleware是所有 url皆會執行方法如果有指定特定方法就可以使用 appget的 middleware設定在 appget函式的第二個參數就可以帶入函式或者是匿名函式只要函式裡面最後會接受 request response next這三個參數同時也有正確指定 next函式的執行時機最後都會執行到最後一個方法當然開發者也可以評估程式邏輯要執行到哪一個階段讓邏輯

可以更為分明

64 Express middleware 55

Nodejs中文電子書

65 Express路由應用

在實際開發上可能會遇到需要使用參數等方式混和變數一起使用

express裡面提供了一個很棒的處理方法 appall這個方式可以先採用基本路由配對再將設定為每個不同的處理方式開發者可以透過這個方式簡

化自己的程式邏輯

overview

author

version 20120226

create server

var app = require(rsquoexpressrsquo)createServer()

port = 1337

users = [

name rsquoClonnrsquo

name rsquoChirsquo

]

applisten(port)

appall(rsquouseridoprsquo function(req res next)

requser = users[reqparamsid]

if (requser)

next()

else

next(new Error(rsquocannot find user rsquo + reqparamsid))

)

appget(rsquouseridrsquo function(req res)

ressend(rsquoviewing rsquo + requsername)

)

appget(rsquouserideditrsquo function(req res)

ressend(rsquoediting rsquo + requsername)

)

56 6 Express介紹

Nodejs中文電子書

appget(rsquouseriddeletersquo function(req res)

ressend(rsquodeleting rsquo + requsername)

)

appget(rsquorsquo function(req res)

ressend(rsquoPage not foundrsquo 404)

)

consolelog(rsquostart express servernrsquo)

內部宣告一組預設的使用者分別給予名稱設定藉由 appall這個方法可以先將路由雛形建立再接下來設定 appget的路徑格式只要符合格式就會分配進入對應的方法中像上面的程式當中如果使用者輸入路徑為user0除了執行 appall程式之後執行 next方法就會對應到路徑設定為userid的這個方法當中如果使用者輸入路徑為user0edit就會執行到useridedit的對應方法

66 Express GET應用範例

我們準備一個使用 GET方法傳送資料的表單

ltDOCTYPE htmlgt

lthtmlgt

ltheadgt

ltmeta http-equiv=rdquoContent-Typerdquo content=rdquotexthtml charset=utf8rdquogt

lttitlegtNodejs 菜鳥筆記 (1)lttitlegt

ltlink rel=rdquostylesheetrdquo href=rdquocssstylecssrdquo type=rdquotextcssrdquo media=rdquoallrdquo gt

ltheadgt

ltbodygt

lth1gtNodejs 菜鳥筆記-註冊lth1gt

ltform id=rdquosignuprdquo method=rdquoGETrdquo action=rdquohttplocalhost3000Signuprdquogt

ltlabelgt 使用者名稱ltlabelgtltinput type=rdquotextrdquo id=rdquousernamerdquo name=rdquousernamerdquo gtltbrgt

ltlabelgt 電子郵件ltlabelgtltinput type=rdquotextrdquo id=rdquoemailrdquo name=rdquoemailrdquo gtltbrgt

ltinput type=rdquosubmitrdquo value=rdquo 註冊我的帳號rdquo gtltbrgt

ltformgt

ltbodygt

lthtmlgt

66 Express GET應用範例 57

Nodejs中文電子書

這個表單沒有什麼特別的地方我們只需要看第 9 行form 使用的method是 GET然後 action是rdquohttplocalhost3000Signupldquo等一下我們要來撰寫Signup這個 URL Path的處理程式

處理 Signup行為

我們知道所謂的 GET方法會透過 URL來把表單的值給帶過去以上面的表單來說到時候 URL會以這樣的形式傳遞

httplocalhost3000Signupusername=xxxampemail=xxx

所以要能處理這樣的資料必須有以下功能

bull 解析 URL

bull 辨別動作是 Signup

bull 解析出 username和 email

一旦能取得 username和 email的值程式就能加以應用了

處理 Signup的程式碼雛形

load module

var url = require(rsquourlrsquo)

urlData = urlparse(requrltrue)

action = urlDatapathname

reswriteHead(200 rdquoContent-Typerdquordquotexthtml charset=utf-8rdquo)

if (action === rdquoSignuprdquo)

user = urlDataquery

resend(rdquolth1gtrdquo + userusername + rdquo 歡迎您的加入 lth1gtltpgt 我們已經將會員啟用信寄至rdquo + useremail + rdquoltpgtrdquo)

首先需要加載 url module它是用來協助我們解析 URL的模組接著使用 urlparse方法第一個傳入 url字串變數也就是 requrl另外第二個參數的用意是設為 ture則引進 querystring模組來協助處理預設是 false它影響到的是 urlDataquery設為 true會傳回物件不然就只是一般的字串urlparse會將字串內容整理成一個物件我們把它指定給 urlData

action變數作為記錄 pathname這是我們稍後要來判斷目前網頁的動作是什麼接著先將 html表頭資訊 (Header)準備好再來判斷路徑邏輯如

58 6 Express介紹

Nodejs中文電子書

果是 Signup這個動作就把 urlDataquery裡的資料指定給 user然後輸出userusername和 useremail把使用者從表單註冊的資料顯示於頁面中

最後進行程式測試啟動 nodejs主程式之後開啟瀏覽器就會看到表單填寫完畢按下送出就可以看到結果了

完整 nodejs程式碼如下

var http = require(rsquohttprsquo)

url = require(rsquourlrsquo)

fs = require(rdquofsrdquo)

server

server = httpcreateServer(function (reqres)

var urlData

encode = rdquoutf8rdquo

filePath = rdquoviewexpress_get_example_formhtmlrdquo

action

urlData = urlparse(requrltrue)

action = urlDatapathname

reswriteHead(200 rdquoContent-Typerdquordquotexthtml charset=utf-8rdquo)

if (action === rdquoSignuprdquo)

user = urlDataquery

resend(rdquolth1gtrdquo + userusername + rdquo 歡迎您的加入 lth1gtltpgt 我們已經將會員啟用信寄至rdquo + useremail + rdquoltpgtrdquo)

else

fsreadFile(filePath encode function(err file)

reswrite(file)

resend()

)

)

serverlisten(3000)

consolelog(rsquoServer 跑起來了現在時間是rsquo + new Date())

66 Express GET應用範例 59

Nodejs中文電子書

67 Express POST應用範例

一開始準備基本的 html表單傳送內容以 POST方式form的 action屬性設定為 POST其餘 html內容與前一個範例應用相同

ltDOCTYPE htmlgt

lthtmlgt

ltheadgt

ltmeta http-equiv=rdquoContent-Typerdquo content=rdquotexthtml charset=utf8rdquogt

lttitlegtNodejs 菜鳥筆記 (1)lttitlegt

ltlink rel=rdquostylesheetrdquo href=rdquocssstylecssrdquo type=rdquotextcssrdquo media=rdquoallrdquo gt

ltheadgt

ltbodygt

lth1gtNodejs 菜鳥筆記-註冊lth1gt

ltform id=rdquosignuprdquo method=rdquoPOSTrdquo action=rdquohttplocalhost3000Signuprdquogt

ltlabelgt 使用者名稱ltlabelgtltinput type=rdquotextrdquo id=rdquousernamerdquo name=rdquousernamerdquo gtltbrgt

ltlabelgt 電子郵件ltlabelgtltinput type=rdquotextrdquo id=rdquoemailrdquo name=rdquoemailrdquo gtltbrgt

ltinput type=rdquosubmitrdquo value=rdquo 註冊我的帳號rdquo gtltbrgt

ltformgt

ltbodygt

lthtmlgt

nodejs的程式處理邏輯與前面 GET範例類似部分程式碼如下

qs = require(rsquoquerystringrsquo)

if (action === rdquoSignuprdquo)

formData = rsquorsquo

reqon(rdquodatardquo function (data)

formData += data

)

reqon(rdquoendrdquo function ()

user = qsparse(formData)

resend(rdquolth1gtrdquo + userusername + rdquo 歡迎您的加入 lth1gtltpgt 我們已經將會員啟用信寄至rdquo + useremail + rdquoltpgtrdquo)

)

60 6 Express介紹

Nodejs中文電子書

主要加入了rsquoquerystringrsquo這個moduel方便我們等一下解析由表單 POST回來的資料另外加入一個 formData的變數用來搜集待等一下表單回傳的資料前面的 GET範例我們只從 req拿出 url的資料這次要在利用req身上的事件處理

JavaScript 在訂閱事件時使用 addEventListener而 nodejs 使用的則是on這邊加上了監聽 data的事件會在瀏覽器傳送資料到Web Server時被執行參數是它所接收到的資料型態是字串

接著再增加 end的事件當瀏覽器的請求事件結束時它就會動作

由於瀏覽器使用 POST在上傳資料時會將資料一塊塊地上傳因為我們在監聽 data事件時透過 formData變數將它累加起來lt不過由於我們

上傳的資料很少一次就結束不過如果日後需要傳的是資料比較大的檔

案這個累加動作就很重要

當資料傳完就進到 end 事件中會用到 qsparse 來解析 formDataformData的內容是字串內容是

username=wordsmithampemail=wordsmith40somewhere

而 qsparse可以幫我們把這個 querystring轉成物件的格式也就是

username=wordsmithampemail=wordsmith40somewhere

一旦轉成物件並指定給 user之後其他的事情就和 GET方法時操作的一樣寫 response的表頭將內容回傳並將 userusername和 useremail代入到內容中

修改完成後接著執行 nodejs程式啟動 web server開啟瀏覽器進入表單測試看看POST的方式能否順利運作

完整程式碼如下

var http = require(rsquohttprsquo)

url = require(rsquourlrsquo)

fs = require(rdquofsrdquo)

qs = require(rsquoquerystringrsquo)

server

server = httpcreateServer(function (reqres)

var urlData

67 Express POST應用範例 61

Nodejs中文電子書

encode = rdquoutf8rdquo

filePath = rdquoviewexpress_post_example_formhtmlrdquo

formData

action

urlData = urlparse(requrltrue)

action = urlDatapathname

reswriteHead(200 rdquoContent-Typerdquordquotexthtml charset=utf-8rdquo)

if (action === rdquoSignuprdquo)

formData = rsquorsquo

reqon(rdquodatardquo function (data)

formData += data

)

reqon(rdquoendrdquo function ()

user = qsparse(formData)

resend(rdquolth1gtrdquo + userusername + rdquo 歡迎您的加入 lth1gtltpgt 我們已經將會員啟用信寄至rdquo + useremail + rdquoltpgtrdquo)

)

else

fsreadFile(filePath encode function(err file)

reswrite(file)

resend()

)

)

serverlisten(3000)

consolelog(rsquoServer 跑起來了現在時間是rsquo + new Date())

62 6 Express介紹

Nodejs中文電子書

68 Express AJAX應用範例

在 Nodejs要使用 Ajax傳送資料並且與之互動在接受資料的部份沒有太大的差別client端不是用 GET就是用 POST來傳資料重點在處理完後用 JSON格式回傳當然 Ajax不見得只傳 JSON格式有時是回傳一段 HTML碼不過後者對伺服器來說基本上就和前兩篇沒有差別了所以我們還是以回傳 JSON做為這一回的主題

這一回其實大多數的工作都會落在前端 Ajax上面前端要負責發送與接收資料並在接收資料後撤掉原先發送資料的表單並將取得的資料

改成 HTML格式之後放上頁面

首先先準備 HTML靜態頁面資料

ltDOCTYPE htmlgt

lthtmlgt

ltheadgt

lttitlegtNodejs 菜鳥筆記 (3)lttitlegt

ltlink rel=rdquostylesheetrdquo href=rdquocssstylecssrdquo type=rdquotextcssrdquo media=rdquoallrdquo gt

ltscript src=rdquohttpsajaxgoogleapiscomajaxlibsjquery171jqueryminjsrdquogtltscriptgt

ltheadgt

ltbodygt

lth1gtNodejs 菜鳥筆記-註冊 (用 Ajax)lth1gt

ltform id=rdquosignuprdquo action=rdquoSignuprdquo method=rdquoPOSTrdquogt

ltlabelgt 使用者名稱ltlabelgtltinput type=rdquotextrdquo id=rdquousernamerdquo name=rdquousernamerdquo gtltbrgt

ltlabelgt 電子郵件ltlabelgtltinput type=rdquotextrdquo id=rdquoemailrdquo name=rdquoemailrdquo gtltbrgt

ltinput type=rdquosubmitrdquo value=rdquo 註冊我的帳號rdquo gtltbrgt

ltformgt

ltdiv id=rdquoresultrdquogt

ltdivgt

ltscript type=rdquotextjavascriptrdquogt

$(function()$(rsquosignup input[type=rdquosubmitrdquo]rsquo)click(function(e)

var user =

userusername = $(rdquousernamerdquo)val()useremail = $(rdquoemailrdquo)val()

$post(rdquoSignuprdquouserfunction(data)greet(data)

)

68 Express AJAX應用範例 63

Nodejs中文電子書

epreventDefault()

)

var greet = function(msg)

var resultNode = $(rsquoresultrsquo)greeting = $(rsquolth2gtHirsquo+msgusername+rsquo 你的會員 id 是rsquo+ msgid +rsquo 我們會將會員啟動信寄至rsquo+msgemail+rsquolth2gtrsquo)

$(rsquosignuprsquo)hide()resultNodehtml(rdquordquo)

resultNodeappend(greeting)

)

ltscriptgt

ltbodygt

lthtmlgt

HTML頁面上準備了一個表單用來傳送註冊資料接著直接引用了Google CDN來載入 jQuery用來幫我們處理 Ajax的工作這次要傳送和接收的工作很大的變動都在 HTML頁面上的 JavaScript當中我們要做的事有 (相關 jQuery處理這邊不多做贅述指提起主要功能解說)

bull 用 jQUery取得 submit按鈕綁定它的 click動作

bull 取得表單 username和 email的值存放在 user這個物件中

bull 用 jQuery的 $post方法將 user的資料傳到 Server

bull 一旦成功取得資料後透過 greet這個 function組成回報給 user的訊息

bull 清空原本給使用者填資料的表單

bull 將 Server回傳的 usernameemail和 id這 3個資料組成回應的訊息

bull 將訊息放到原本表單的位置

經過以上的處理後一個 Ajax的表單的基本功能已經完成

接著進行 nodejs主要程式的編輯部分程式碼如下

var fs = require(rdquofsrdquo)

64 6 Express介紹

Nodejs中文電子書

qs = require(rsquoquerystringrsquo)

if (action === rdquoSignuprdquo)

formData = rsquorsquo

reqon(rdquodatardquo function (data)

formData += data

)

reqon(rdquoendrdquo function ()

var msg

user = qsparse(formData)

userid = rdquo123456rdquo

msg = JSONstringify(user)

reswriteHead(200 rdquoContent-Typerdquordquoapplicationjson charset=utf-8rdquordquoContent-Lengthrdquomsglength)

resend(msg)

)

這裡的程式和前面 POST範例基本上大同小異差別在

bull 幫 user的資料加上 id隨意存放一些文字進去讓 Server回傳的資料多於 Client端傳上來的不然會覺得 Server都沒做事

bull 增加了 msg 這個變數存放將 user 物件 JSON 文字化的結果JSONstringify這個轉換函式是 V8引擎所提供的如果你好奇的話

bull 大重點來了我們要告訴 Client端這次回傳的資料格式是 JSON所在 Content-type和 Content-Length要提供給 Client

Server很輕鬆就完成任務了最後進行程式測試啟動 nodejs主程式之後開啟瀏覽器就會看到表單填寫完畢按下送出就可以看到結果了

最後 nodejs本篇範例程式碼如下

var http = require(rsquohttprsquo)

url = require(rsquourlrsquo)

fs = require(rdquofsrdquo)

qs = require(rsquoquerystringrsquo)

server

68 Express AJAX應用範例 65

Nodejs中文電子書

server = httpcreateServer(function (reqres)

var urlData

encode = rdquoutf8rdquo

filePath = rdquoviewexpress_ajax_example_formhtmlrdquo

formData

action

urlData = urlparse(requrltrue)

action = urlDatapathname

if (action === rdquoSignuprdquo)

formData = rsquorsquo

reqon(rdquodatardquo function (data)

formData += data

)

reqon(rdquoendrdquo function ()

var msg

user = qsparse(formData)

userid = rdquo123456rdquo

msg = JSONstringify(user)

reswriteHead(200 rdquoContent-TyperdquordquoapplicationjsonrdquordquoContent-Lengthrdquomsglength)

resend(msg)

)

else

fsreadFile(filePath encode function(err file)

reswriteHead(200 rdquoContent-Typerdquordquotexthtml charset=utf-8rdquo)

reswrite(file)

resend()

)

)

serverlisten(3000)

66 6 Express介紹

Nodejs中文電子書

consolelog(rsquoServer 跑起來了現在時間是rsquo + new Date())

69 原始資料提供

bull [NodeJS初學者筆記 (1)-用 GET傳送資料] (httpithelpithomecomtwquestion10087402)

bull [NodeJS初學者筆記 (2)-用 POST傳送資料] (httpithelpithomecomtwquestion10087489)

bull [NodeJS 初學者筆記 (3)-用 Ajax 傳送資料] (httpithelpithomecomtwquestion10087627)

69 原始資料提供 67

Nodejs中文電子書

68 6 Express介紹

7

CoffeeScript

bull Spine

bull Hem

bull Spineapp

Letrsquos Have a Cup of CoffeeScript

bull es5-shim ECMAScript 5 compatibility shims for legacy JavaScript engines

ESnext

node-iform - A middleware for node-validator httpsgithubcomguileennode-iform

69

Nodejs中文電子書

70 7 CoffeeScript

8

製作一個 Hubot的 Plurk Adapter

81 應用事項提醒

此應用範例以CoffeeScript編寫主要程式架構採用Github釋放的Hubot專案以下有幾個主要注意事項請讀者注意

bull Hubot 一開始的架構除了內建的兩個 Adapter 之外其餘都要以Nodejs的Module方式才能運作

bull Hubot的 binhubot裡面寫著 npm install所以不管你怎麼改原始碼也不能改變第一點的狀況

其實上述都在討論同一件事情NodeJS的模組而且模組不能設定相對路徑之類的來安裝一定要包裝成 npm相符和格式才有辦法正確執行

82 建立 Adapter

首先需要準備兩個檔案

bull packagejson

bull plurkcoffee

71

Nodejs中文電子書

有這兩個就足以變成 NodeJS的模組了在開始 Coding之前先來設定一下 packagejson相依性

rdquonamerdquo rdquohubot-plurkrdquo

rdquoversionrdquo rdquo011rdquo

rdquomainrdquo rdquoplurkrdquo

rdquodependenciesrdquo

rdquohubotrdquo rdquogt=205rdquo

rdquooauthrdquo rdquordquo

rdquocronrdquordquordquo

這邊會使用到NodeJS的 cron模組(Module)而登入 Plurk需要OAuth標準授權才行所以也需要加載 OAuth模組

注意Hubot在讀取非內建模組時會自動在前面加上 hubot-的前置

83 建立 Robot跟 API

本篇應用基本上程式碼大部分參考 Hubot - Twitter Adapter來製作主要差異只有在使用 OAuth這個模組Twitter有 Streaming可用而 Plurk則得用 Comet方式來達到即時讀取

plurkcoffee程式碼

Robot = require(rdquohubotrdquo)robot()

Adapter = require(rdquohubtordquo)adapter()

EventEmitter = require(rdquoeventsrdquo)EventEmitter

oauth = require(rdquooauthrdquo)

cronJob = require(rdquocronrdquo)CronJob

class Plurk exntends Adapter

class PlurkStreaming exnteds EventEmitter

72 8 製作一個 Hubot的 Plurk Adapter

Nodejs中文電子書

先弄個基本架構主要 Class皆參考 Twitter Adapter命名方式接著再將 Plurk這個 Class增加幾個Method基本上只要有 run send reply就夠了而 run用來做初始化的部分

class Plurk entends Adapter

send (plurk id strings⋯) -gt

reply (plurk id strings⋯) -gt

run -gt

看起來有點東西接著來處理主要的 Plurk API結合部分

class PlurkStreaming extends EventEmitter

consuctor (options) -gt

plurk (callback) -gt

觀察河道

getChannel -gt

取得 Comet 網址

reply (plurk id message) -gt

回噗

acceptFriends -gt

接受好友

get (path callback) -gt

GET 請求

post (path body callback)-gt

POST 請求(其實是裝飾)

request (method path body callback)-gt

主要的 OAuth 請求

comet (server callback)-gt

噗浪的 Comet 傳回是 JavaScript Callback 要另外處理後才會變成 JSON

然後把注意力集中到 constructor上先把建構子弄好

constructor (options) -gt

super()

if optionskey and optionssecret and optionstoken and optionstoken secret

key = optionskey

secret = optionssecret

token = optionstoken

token secret = optionstoken secret

83 建立 Robot跟 API 73

Nodejs中文電子書

建立 OAuth 連接

consumer = new oauthOAuth(

rdquohttpwwwplurkcomOAuthrequest tokenrdquo

rdquohttpwwwplurkcomOAuthaccess tokenrdquo

key

secret

rdquo10rdquo

rdquohttpwwwplurkcomOAuthauthorizerdquo

rdquoHMAC-SHA1rdquo

)

domain = rdquowwwplurkcomrdquo

初始化取得 Comet 網址

do getChannel

else

throw new Error(rdquo 參數不足需要 Key Secret Token Token Secretrdquo)

接著來處理 request這個 method

request (method path body callback) -gt

記錄一下這次的 Request

consolelog(rdquohttpdomainpathrdquo)

Callback 這邊先不丟進去要用另一種方式處理

request = consumerget(rdquohttpdomainpathrdquo token token secret null)

requeston rdquoresponserdquo (res) -gt

reson rdquodatardquo (chunk) -gt

parseResponse(chunk+rsquorsquo callback)

reson rdquoendrdquo (data) -gt

consolelog rdquoEnd Request pathrdquo

reson rdquoerrorrdquo (data) -gt

consolelog rdquoError rdquo + data

requestend()

處理資料

parseResponse = (data callback) -gt

if datalength gt 0

用 TryCatch 避免處理 JSON 出錯導致整個中斷

try

callback null JSONparse(data)

catch err

74 8 製作一個 Hubot的 Plurk Adapter

Nodejs中文電子書

consolelog(rdquoError Parse JSONrdquo + data err)

繼續執行

callback null data ||

大致上就是這樣上面程式的架構已經將整個 Hubot Plurk Adapter完成因為在測試時竟然因為噗浪 Lag而沒讀到完整的 Comet資料然後造成程式異常為了避免這個問題發生因此需要加上為了完美呈現需要再

加上 Comet的處理所以要使用到 EventEmitter的功能

comet (server callback) -gt

在 Callback 裡面會找不到自身所以設定區域變數

self =

記錄一下這次的 Request

consolelog(rdquo[Comet] serverrdquo)

Callback 這邊先不丟進去要用另一種方式處理

request = consumerget(rdquohttpdomainpathrdquo token token secret null)

requeston rdquoresponserdquo (res) -gt

reson rdquodatardquo (chunk) -gt

parseResponse(chunk+rsquorsquo callback)

reson rdquoendrdquo (data) -gt

consolelog rdquoEnd Request pathrdquo

請求結束發出事件通知可以進行下一次請求

selfemit rdquonextPlurkrdquo

reson rdquoerrorrdquo (data) -gt

consolelog rdquoError rdquo + data

requestend()

處理資料

parseResponse = (data callback) -gt

if datalength gt 0

用 trycatch 避免失敗中斷

try

去掉 JavaScript 的 Callback

data = datamatch(CometChannelscriptCallback((+))s)

jsonData = rdquordquo

if data

83 建立 Robot跟 API 75

Nodejs中文電子書

jsonData = JSONparse(data[1])

else

如果沒有任何 Match 嘗試直接 parse

jsonData = JSONparse(data)

catch err

consolelog(rdquo[Comet] Errorrdquo data err)

用 TryCatch 避免處理 JSON 出錯導致整個中斷

try

只傳入 json 的 data 部分

callback null jsonDatadata

catch err

consolelog(rdquo[Comet]Error Parse JSONrdquo + data err)

繼續執行

callback null data ||

後面的 get跟 post就簡單多了

get (path callback) -gt

request(rdquoGETrdquo path null callback)

post (path body callback) -gt

request(rdquoPOSTrdquo path body callback)

接著處理取的 Comet網址的 getChannel

getChannel -gt

self =

get rdquoAPPRealtimegetUserChannelrdquo (error data) -gt

if error

檢查是否有 comet server

if datacomet server

selfchannel = datacomet server

如果沒有 Channel Ready 就嘗試連接會失敗

selfemit(rsquochannel readyrsquo)

那麼先來處理 Plurk Adaper好處理的部份

send (plruk id strings⋯)-gt

跟 Reply 一樣直接交給 reply 做

reply plurk id strings⋯

76 8 製作一個 Hubot的 Plurk Adapter

Nodejs中文電子書

reply (plurk id strings⋯) -gt

stringsforEach (message) =gt

botreply(plruk id message)

接著把 run處理好就可以上線運作摟

run -gt

self =

options =

key processenvHUBOT PLURK KEY

secret processenvHUBOT PLURK SECRET

token processenvHUBOT PLURK TOKEN

token secret processenvHUBOT PLURK TOKEN SECRET

創建剛剛的 API

bot = new PlurkStreaming(options)

依照 Twitter 的 new RobotTextMessage 會沒有反應所以參考 hubot-minecraft 的方式

r = robotconstructor

處理噗浪河道訊息

doPlurk = (data)-gt

檢查是否為回噗

if dataresponse

datacontent raw = dataresponsecontent raw

datauser id = dataresponseuser id

確定有噗浪 ID 跟訊息

if dataplurk id and datacontent raw

selfreceive new rTextMessage(dataplurk id datacontent raw)

取得 Comet Server 完成開始第一次 Comet 連接

boton rdquochannel readyrdquo () -gt

botplurk selfdoPlurk

上一次 Comet 完成繼續 Polling

boton rdquonextPlurkrdquo ()-gt

botplurk selfdoPlurk

定時接受好友邀請

do botacceptFriends

bot = bot

83 建立 Robot跟 API 77

Nodejs中文電子書

終於完成 AdapterHubot專案裡面的 scripts資料夾內是互動部分不需要像 Adapter如此大費周章處理只新增檔案並且設計好對白之後就會回噗了我開發用的機器人在此大家可以去跟他玩玩[httpplurkcomelct9620 bot](httpplurkcomelct9620 bot)

84 原始資料提供

bull [製 作 一 個 Hubot 的 噗 浪 Adapter](http revo-skillfrosttw blog20120318create-a-hubot-plurk-adapter)

78 8 製作一個 Hubot的 Plurk Adapter

9

Nodejs好用工具介紹

91 logio

Real-time log monitoring in your browser

httplogioorg

nodejs (gt=0412 lt=0611)

npm config set unsafe-perm true

npm install -g --prefix=usrlocal logio

logio server start

httplocalhost8998

79

Nodejs中文電子書

80 9 Nodejs好用工具介紹

10

精選文章

101 Express

bull 使用 NodeJS + Express從 GETPOST Request取值 [邀稿中]

bull link2 [邀稿中]

bull link3 [邀稿中]

81

Nodejs中文電子書

82 10 精選文章

11

參考資源

111 Nodejs書籍

bull Programming Nodejs April 2012 (est) Travis Swicegood Pragmatic Bookshelf

bull e Little Book on CoffeeScript January 2012 Alex MacCaw OrsquoReilly Media

bull Node for Front-End Developers January 2012 Garann Means OrsquoReilly Media

bull Building Hypermedia APIs with HTML5 and Node November 2011 MikeAmundsen OrsquoReilly Media

bull Node Web Development August 2011 David Herron PacktPub

bull CoffeeScript Accelerated JavaScript Development July 2011 Trevor BurnhamPragmatic Bookshelf

bull Getting Started with GEO CouchDB and Nodejs July 2011 Mick omp-son OrsquoReilly Media

bull What Is Node July 2011 Brett McLaughlin OrsquoReilly Media

bull Node Up and Running May 2011 Tom Hughes-Croucher Mike WilsonOrsquoReilly Media

83

Nodejs中文電子書

bull Hands-on Nodejs e Nodejs introduction and API reference May 2011 Pe-dro Teixeira LeanPub

bull e Node Beginner Book Manuel Kiessling

bull Mastering Nodejs visionmedia

112 Nodejs影音教學

bull e Node Sessions e Best of OSCON 2011 August 2011 OrsquoReilly Media(Video)

bull Tom Hughes-Croucher on Node March 2011 OrsquoReilly Media (Video)

113 Nodejs教學網站

bull Node Tuts

114 Nodejs課程

bull Introduction To Nodejs Van Nguyen CodeLesson

84 11 參考資源

Page 44: Nodejs Wiki

Nodejs中文電子書

var server

ip = rdquo127001rdquo

port = 1337

http = require(rsquohttprsquo)

fs = require(rdquofsrdquo)

folderPath = rdquostaticrdquo

url = require(rsquourlrsquo)

path

filePath

encode = rdquoutf8rdquo

server = httpcreateServer(function (req res)

path = urlparse(requrl)

filePath = folderPath + pathpathname

fsreadFile(filePath encode function(err file)

if (err)

reswriteHead(404 rsquoContent-Typersquo rsquotextplainrsquo)

resend()

return

reswriteHead(200 rsquoContent-Typersquo rsquotextapplicationrsquo)

reswrite(file)

resend()

)

)

serverlisten(port ip)

consolelog(rdquoServer running at httprdquo + ip + rdquordquo + port)

45 nodejs http GET資料擷取

http伺服器中除了路由之外另一個最常使用的方法就是擷取 GET資料本單元將會介紹如何透過基本 http伺服器擷取瀏覽器傳來的要求擷取 GET資料

在 http協定中GET參數都是藉由 URL從瀏覽器發出要求送至伺服器

38 4 Nodejs基礎

Nodejs中文電子書

端基本的傳送網址格式可能如下

http127001testsend=1amptest=2

上面這段網址裡面的 GET參數就是 send而這個變數的數值就為 1如果想要在 http伺服器取得 GET資料需要在瀏覽器給予的要求 (request)做處理

首先需要載入 query string這個模組這個模組主要是用來將字串資料

過濾後轉換成javascript物件

qs = require(rsquoquerystringrsquo)

接著在第一階段利用 url模組過濾瀏覽器發出的 URL資料後將回應的物件裡面的 query這個變數是一個字串值資料過濾後如下

send=1amptest=2

透過 query string使用 parse這個方法將資料轉換成 javascript物件就表示 GET的資料已經被伺服器端正式擷取下來

path = urlparse(requrl)

parameter = qsparse(pathquery)

整個 nodejs http GET參數完整擷取程式碼如下

var server

ip = rdquo127001rdquo

port = 1337

http = require(rsquohttprsquo)

qs = require(rsquoquerystringrsquo)

url = require(rsquourlrsquo)

server = httpcreateServer(function (req res)

var path = urlparse(requrl)

parameter = qsparse(pathquery)

consoledir(parameter)

reswriteHead(200 rsquoContent-Typersquo rsquotextplainrsquo)

reswrite(rsquoBrowser test GET parameternrsquo)

resend()

)

45 nodejs http GET資料擷取 39

Nodejs中文電子書

serverlisten(port ip)

consolelog(rdquoServer running at httprdquo + ip + rdquordquo + port)

程式運作之後由瀏覽器輸入要求網址之後nodejs伺服器端回應資料為

Server running at http1270011337

send rsquo1rsquo test rsquo1rsquo

46 本章結語

前面所解說的部份一大部分主要是處理 http伺服器基本問題雖然在某些部分有牽扯到 http 伺服器基本運作原理主要還是希望可以藉由這些基本範例練習 nodejs練習回應函式與語法串接的特點習慣編寫javascript風格程式當然直接這樣開發 nodejs是非常辛苦的接下來在模組實戰開發的部份將會介紹特定的模組一步一步帶領各位從無到有進行

nodejs應用程式開發

40 4 Nodejs基礎

5

NPM套件管理工具

npm全名為 Node Package Manager是 Nodejs的套件(package)管理工具類似 Perl的 ppm或 PHP的 PEAR等安裝 npm後使用npm install

module name指令即可安裝新套件維護管理套件的工作會更加輕鬆

npm可以讓Nodejs的開發者直接利用擴充線上的套件庫(packagesregistry)加速軟體專案的開發npm提供很友善的搜尋功能可以快速找到安裝需要的套件當這些套件發行新版本時npm也可以協助開發者自動更新這些套件

npm不僅可用於安裝新的套件它也支援搜尋列出已安裝模組及更新的功能

51 安裝 NPM

Nodejs在 063版本開始內建 npm讀者安裝的版本若是此版本或更新的版本就可以略過以下安裝說明

若要檢查 npm是否正確安裝可以使用以下的指令

npm -v

41

Nodejs中文電子書

執行結果說明

若 npm正確安裝執行 npm -v將會看到類似 110-2的版本訊息

若讀者安裝的 Nodejs版本比較舊或是有興趣嘗試自己動手安裝 npm工具則可以參考以下的說明

安裝於Windows系統

Nodejs for Windows 於 062 版開始內建 npm使用 nodejsorg 官方提供的安裝程式不需要進一步的設定就可以立即使用 npm指令對於Windows的開發者來說大幅降低環境設定的問題與門檻

除了使用 Nodejs內建的 npm讀者也可以從 npm官方提供的以下網址

httpnpmjsorgdist

這是由 npm提供的 Fancy Windows Install版本請下載壓縮檔(例如npm-110-3zip)並將壓縮檔內容解壓縮至 Nodejs的安裝路徑(例如CProgram Filesnodejs)

解壓縮後在 Nodejs的安裝路徑下應該有以下的檔案及資料夾

bull npmcmd(檔案)

bull node modules(資料夾)

安裝於 Linux系統

Ubuntu Linux的使用者可以加入 NPM Unoffcial PPA這個 repository即可使用 apt-get完成 npm安裝

42 5 NPM套件管理工具

Nodejs中文電子書

Ubuntu Linux使用 apt-get安裝 npm

sudo apt-get install python-software-properties

sudo add-apt-repository ppagias-kay-leenpm

sudo apt-get update

sudo apt-get install npm

npm官方提供的安裝程式 installsh可以適用於大多數的 Linux系統使用這個安裝程式請先確認

1 系統已安裝 curl工具(請使用 curl --version查看版本訊息)

2 已安裝 Nodejs並且 PATH正確設置

3 Nodejs的版本必須大於 04x

以下為 npm提供的安裝指令

curl httpnpmjsorginstallsh | sh

安裝成功會看到如下訊息

installsh安裝成功的訊息

npm10105 homeUSERNAMElocalnodelibnode_modulesnpm

It worked

安裝於Mac OS X

建議採用與 Nodejs相同的方式進行 npm的安裝例如使用MacPorts安裝 Nodejs就同樣使用MacPorts安裝 npm這樣對日後的維護才會更方便容易

使用 MacPorts安裝 npm是本書比較建議的方式它可以讓 npm的安裝移除及更新工作自動化將會幫助開發者節省寶貴時間

51 安裝 NPM 43

Nodejs中文電子書

安裝MacPorts的提示

在MacPorts網站可以取得 OS X系統版本對應的安裝程式(例如 106或 107)httpwwwmacportsorg安裝過程會詢問系統管理者密碼使用預設的選項完成安裝即可安

裝MacPorts之後在終端機執行 port -v將會看到MacPorts的版本訊息

安裝 npm之前先更新MacPorts的套件清單以確保安裝的 npm是最新版本

sudo port -d selfupdate

接著安裝 npm

sudo port install npm

若讀者的 Nodejs並非使用MacPorts安裝則不建議使用MacPorts安裝npm因為 MacPorts會自動檢查並安裝相依套件而 npm相依 nodejs所以MacPorts也會一併將 nodejs套件安裝造成先前讀者使用其它方式安裝的 nodejs被覆蓋

讀者可以先使用 MacPorts安裝 curl(sudo port install curl)

再參考 Linux的 installsh安裝方式即可使用 npm官方提供的安裝程式

NPM安裝後測試

npm是指令列工具(command-line tool)使用時請先打開系統的文字終端機工具

測試 npm安裝與設定是否正確請輸入指令如下

npm -v

或是

npm --version

如果 npm已經正確安裝設定就會顯示版本訊息

44 5 NPM套件管理工具

Nodejs中文電子書

執行結果(範例)

110-2

52 使用 NPM安裝套件

npm目前擁有超過 6000種套件(packages)可以在 npm registry使用關鍵字搜尋套件

httpsearchnpmjsorg

舉例來說在關鍵字欄位輸入「coffee-script」下方的清單就會自動列出包含 coffee-script關鍵字的套件

接著我們回到終端機模式的操作npm的指令工具本身就可以完成套

件搜尋的任務

例如以下的指令同樣可以找出 coffee-script相關套件

npm search coffee-script

以下是搜尋結果的參考畫面

52 使用 NPM安裝套件 45

Nodejs中文電子書

找到需要的套件後(例如 express)即可使用以下指令安裝

npm install coffee-script

值得注意的一點是使用 npm install會將指定的套件安裝在工

作目錄(Working Directory)的 node modules資料夾下

以Windows為例如果執行 npm install的目錄位於

Cproject1

那麼 npm將會自動建立一個 node modules的子目錄(如果不存在)

Cproject1node modules

並且將下載的套件放置於這個子目錄例如

Cproject1node modulescoffee-script

這個設計讓專案可以個別管理相依的套件並且可以在專案佈署或發

行時將這些套件(位於 node modules)一併打包方便其它專案的使用者不必再重新下載套件

這個 npm install的預設安裝模式為 local(本地)只會變更當前專案的資料夾不會影響系統

另一種安裝模式稱為 global(全域)這種模式會將套件安裝到系統資

料夾也就是 npm安裝路徑的 node modules資料夾例如

CProgram Filesnodejsnode modules

46 5 NPM套件管理工具

Nodejs中文電子書

是否要使用全域安裝可以依照套件是否提供新指令來判斷舉例來說express 套件提供 express 這個指令而 coffee-script 則提供 coffee

指令

在 local 安 裝 模 式 中這 些 指 令 的 程 式 檔 案會 被 安 裝 到node modules的 bin這個隱藏資料夾下除非將bin的路徑加入 PATH環境變數否則要執行這些指令將會相當不便

為了方便指令的執行我們可以在 npm install 加上 -g 或 --

global參數啟用 global安裝模式例如

npm install -g coffee-script

npm install -g express

使用 global安裝模式需要注意執行權限的問題若權限不足可能會出現類似以下的錯誤訊息

npm ERR Error EACCES permission denied rsquorsquo

npm ERR

npm ERR Please try running this command again as rootAdministrator

要獲得足夠得執行權限請參考以下說明

bull Windows 7 或 2008 以上在「命令提示字元」的捷徑按右鍵選擇「以系統管理員身分執行」執行 npm指令時就會具有 Administrator身分

bull Mac OS X或 Linux系統可以使用 sudo指令例如

sudo npm install -g express

bull Linux系統可以使用 root權限登入或是以「sudo su -」切換成 root身分(使用 root權限操作系統相當危險因此並不建議使用這種方式)

若加上 -g參數使用 npm install -g coffee-script完成安裝

後就可以在終端機執行 coffee指令例如

coffee -v

52 使用 NPM安裝套件 47

Nodejs中文電子書

執行結果(範例)

CoffeeScript version 120

53 套件的更新及維護

除了前一節說明的 search 及 install 用法npm 還提供其他許多指令(commands)

使用 npm help可以查詢可用的指令

npm help

執行結果(部分)

where ltcommandgt is one of

adduser apihelp author bin bugs c cache completion

config deprecate docs edit explore faq find get

help help-search home i info init install la link

list ll ln login ls outdated owner pack prefix

prune publish r rb rebuild remove restart rm root

run-script s se search set show star start stop

submodule tag test un uninstall unlink unpublish

unstar up update version view whoami

使用 npm help command可以查詢指令的詳細用法例如

npm help list

接下來本節要介紹開發過程常用的 npm指令

使用 list可以列出已安裝套件

npm list

48 5 NPM套件管理工具

Nodejs中文電子書

執行結果(範例)

-- coffee-script120

- express256

- connect185

| -- formidable108

-- mime124

-- mkdirp007

-- qs041

檢視某個套件的詳細資訊例如

npm show express

升級所有套件(如果該套件已發佈更新版本)

npm update

升級指定的套件

npm update express

移除指定的套件

npm uninstall express

54 使用 packagejson

對於正式的 Nodejs專案可以建立一個命名為 packagejson的設

定檔(純文字格式)檔案內容參考範例如下

54 使用 packagejson 49

Nodejs中文電子書

packagejson(範例)

rdquonamerdquo rdquoapplication-namerdquo

rdquoversionrdquo rdquo001rdquo

rdquoprivaterdquo true

rdquodependenciesrdquo

rdquoexpressrdquo rdquo255rdquo

rdquocoffee-scriptrdquo rdquolatestrdquo

rdquomongooserdquo rdquogt= 253rdquo

其中 name與 version依照專案的需求設置

需要注意的是 dependencies的設定它用於指定專案相依的套件名

稱及版本

bull rdquoexpressrdquo rdquo255rdquo

代表此專案相依版本 255的 express套件

bull rdquocoffee-scriptrdquo rdquolatestrdquo

使用最新版的 coffee-script套件(每次更新都會檢查新版)

bull rdquomongooserdquo rdquogt= 253rdquo

使用版本大於 253的 mongoose套件

假設某個套件的新版可能造成專案無法正常運作就必須指定套件的

版本避免專案的程式碼來不及更新以相容新版套件通常在開發初期的

專案需要盡可能維持新套件的相容性(以取得套件的更新或修正)可以

用「gt=」設定最低相容的版本或是使用「latest」設定永遠保持最新

套件

50 5 NPM套件管理工具

6

Express介紹

在前面的 nodejs基礎當中介紹許多許多開設 http的使用方法及介紹以及許多基本的 nodejs基本應用

接下來要介紹一個套件稱為 express [Express](httpexpressjscom)這個套件主要幫忙解決許多 nodejs http server所需要的基本服務讓開發 httpservice變得更為容易不需要像之前需要透過層層模組(module)才有辦法開始編寫自己的程式

這個套件是由 TJ Holowaychuk 製作而成的套件裡面包含基本的路由處理 (route)http資料處理(GETPOSTPUT)另外還與樣板套件(jshtml template engine)搭配同時也可以處理許多複雜化的問題

61 Express安裝

安裝方式十分簡單只要透過之前介紹的 NPM就可以使用簡單的指令安裝指令如下

這邊建議需要將此套件安裝成為全域模組方便日後使用

51

Nodejs中文電子書

62 Express基本操作

express的使用也十分簡單先來建立一個基本的 hello world

var app = require(rsquoexpressrsquo)createServer()

port = 1337

applisten(port)

appget(rsquorsquo function(req res)

ressend(rsquohello worldrsquo)

)

consolelog(rsquostart express servernrsquo)

可以從上面的程式碼發現基本操作與 nodejs http的建立方式沒有太大差異主要差在當我們設定路由時可以直接透過 appget方式設定回應與接受方式

63 Express路由處理

Express對於 http服務上有許多包裝讓開發者使用及設定上更為方便例如有幾個路由設定那我們就統一藉由 appget來處理

Create http server

appget(rsquorsquo function(req res)

ressend(rsquohello worldrsquo)

)

appget(rsquotestrsquo function(req res)

ressend(rsquotest renderrsquo)

)

appget(rsquouserrsquo function(req res)

ressend(rsquouser pagersquo)

)

52 6 Express介紹

Nodejs中文電子書

如上面的程式碼所表示appget可以帶入兩個參數第一個是路徑名稱設定第二個為回應函式 (call back function)回應函式裡面就如同之前的 createServer方法裡面包含 requestresponse兩個物件可供使用使用者就可以透過瀏覽器輸入不同的 url切換到不同的頁面顯示不同的結果

路由設定上也有基本的配對方式讓使用者從瀏覽器輸入的網址可以

是一個變數只要符合型態就可以有對應的頁面產出例如

Create http server

appget(rsquouseridrsquo function(req res)

ressend(rsquouser rsquo + reqparamsid)

)

appget(rsquonumberrsquo function(req res)

ressend(rsquonumber rsquo + reqparamsnumber)

)

裡 面 使 用 到number 從 網 址 輸 入 之 後 就 可 以 直 接 使 用reqparamsnumber 取得所輸入的資料變成 url 參數使用當然前面也是可以加上路徑的設定userid在瀏覽器上路徑必須符合userxxx透

過 reqparamsid就可以取到 xxx這個字串值

另外express參數處理也提供了路由參數配對處理也可以透過正規表示法作為參數設定

var app = require(rsquoexpressrsquo)createServer()

port = 1337

applisten(port)

appget(^ip((d23)((d23))((d23))((d23))) function(req res)

ressend(reqparams)

)

上面程式碼可以發現後面路由設定的型態是正規表示法裡面設定

格式為ip之後必須要加上 ip型態才會符合資料格式同時取得 ip資料已經由正規表示法將資料做分群因此可以取得 ip的四個數字

此程式執行之後可以透過瀏覽器測試輸入網址為 localhost3000ip25525510010可以從頁面獲得資料

63 Express路由處理 53

Nodejs中文電子書

此章節全部範例程式碼如下

overview

author Caesar Chi

blog clonnblogspotcom

version 20120226

create server

var app = require(rsquoexpressrsquo)createServer()

port = 1337

applisten(port)

normal style

appget(rsquorsquo function(req res)

ressend(rsquohello worldrsquo)

)

appget(rsquotestrsquo function(req res)

ressend(rsquotest renderrsquo)

)

parameter style

appget(rsquouseridrsquo function(req res)

ressend(rsquouser rsquo + reqparamsid)

)

appget(rsquonumberrsquo function(req res)

ressend(rsquonumber rsquo + reqparamsnumber)

)

REGX style

appget(^ip((d23)((d23))((d23))) function(req res)

ressend(reqparams)

)

appget(rsquorsquo function(req res)

ressend(rsquoPage not foundrsquo 404)

54 6 Express介紹

Nodejs中文電子書

)

consolelog(rsquostart express servernrsquo)

64 Express middleware

Express 裡面有一個十分好用的應用概念稱為 middleware可以透過middleware做出複雜的效果同時上面也有介紹 next方法參數傳遞就是靠 middleware的概念來傳遞參數讓開發者可以明確的控制程式邏輯

create http server

appuse(expressbodyParser())

appuse(expressmethodOverride())

appuse(expresssession())

上面都是一種 middleware的使用方式透過 appuse方式裡面載入函式執行方法回應函式會包含三個基本參數responserequestnext其中next表示下一個 middleware執行函式同時會自動將預設三個參數繼續帶往下個函式執行底下有個實驗

上面的片段程式執行後開啟瀏覽器連結上 localhost1337會發現伺服器回應結果順序如下

first middle ware

second middle ware

execute middle ware

end middleware function

從上面的結果可以得知剛才設定的 middleware都生效了在 appuse設定的 middleware是所有 url皆會執行方法如果有指定特定方法就可以使用 appget的 middleware設定在 appget函式的第二個參數就可以帶入函式或者是匿名函式只要函式裡面最後會接受 request response next這三個參數同時也有正確指定 next函式的執行時機最後都會執行到最後一個方法當然開發者也可以評估程式邏輯要執行到哪一個階段讓邏輯

可以更為分明

64 Express middleware 55

Nodejs中文電子書

65 Express路由應用

在實際開發上可能會遇到需要使用參數等方式混和變數一起使用

express裡面提供了一個很棒的處理方法 appall這個方式可以先採用基本路由配對再將設定為每個不同的處理方式開發者可以透過這個方式簡

化自己的程式邏輯

overview

author

version 20120226

create server

var app = require(rsquoexpressrsquo)createServer()

port = 1337

users = [

name rsquoClonnrsquo

name rsquoChirsquo

]

applisten(port)

appall(rsquouseridoprsquo function(req res next)

requser = users[reqparamsid]

if (requser)

next()

else

next(new Error(rsquocannot find user rsquo + reqparamsid))

)

appget(rsquouseridrsquo function(req res)

ressend(rsquoviewing rsquo + requsername)

)

appget(rsquouserideditrsquo function(req res)

ressend(rsquoediting rsquo + requsername)

)

56 6 Express介紹

Nodejs中文電子書

appget(rsquouseriddeletersquo function(req res)

ressend(rsquodeleting rsquo + requsername)

)

appget(rsquorsquo function(req res)

ressend(rsquoPage not foundrsquo 404)

)

consolelog(rsquostart express servernrsquo)

內部宣告一組預設的使用者分別給予名稱設定藉由 appall這個方法可以先將路由雛形建立再接下來設定 appget的路徑格式只要符合格式就會分配進入對應的方法中像上面的程式當中如果使用者輸入路徑為user0除了執行 appall程式之後執行 next方法就會對應到路徑設定為userid的這個方法當中如果使用者輸入路徑為user0edit就會執行到useridedit的對應方法

66 Express GET應用範例

我們準備一個使用 GET方法傳送資料的表單

ltDOCTYPE htmlgt

lthtmlgt

ltheadgt

ltmeta http-equiv=rdquoContent-Typerdquo content=rdquotexthtml charset=utf8rdquogt

lttitlegtNodejs 菜鳥筆記 (1)lttitlegt

ltlink rel=rdquostylesheetrdquo href=rdquocssstylecssrdquo type=rdquotextcssrdquo media=rdquoallrdquo gt

ltheadgt

ltbodygt

lth1gtNodejs 菜鳥筆記-註冊lth1gt

ltform id=rdquosignuprdquo method=rdquoGETrdquo action=rdquohttplocalhost3000Signuprdquogt

ltlabelgt 使用者名稱ltlabelgtltinput type=rdquotextrdquo id=rdquousernamerdquo name=rdquousernamerdquo gtltbrgt

ltlabelgt 電子郵件ltlabelgtltinput type=rdquotextrdquo id=rdquoemailrdquo name=rdquoemailrdquo gtltbrgt

ltinput type=rdquosubmitrdquo value=rdquo 註冊我的帳號rdquo gtltbrgt

ltformgt

ltbodygt

lthtmlgt

66 Express GET應用範例 57

Nodejs中文電子書

這個表單沒有什麼特別的地方我們只需要看第 9 行form 使用的method是 GET然後 action是rdquohttplocalhost3000Signupldquo等一下我們要來撰寫Signup這個 URL Path的處理程式

處理 Signup行為

我們知道所謂的 GET方法會透過 URL來把表單的值給帶過去以上面的表單來說到時候 URL會以這樣的形式傳遞

httplocalhost3000Signupusername=xxxampemail=xxx

所以要能處理這樣的資料必須有以下功能

bull 解析 URL

bull 辨別動作是 Signup

bull 解析出 username和 email

一旦能取得 username和 email的值程式就能加以應用了

處理 Signup的程式碼雛形

load module

var url = require(rsquourlrsquo)

urlData = urlparse(requrltrue)

action = urlDatapathname

reswriteHead(200 rdquoContent-Typerdquordquotexthtml charset=utf-8rdquo)

if (action === rdquoSignuprdquo)

user = urlDataquery

resend(rdquolth1gtrdquo + userusername + rdquo 歡迎您的加入 lth1gtltpgt 我們已經將會員啟用信寄至rdquo + useremail + rdquoltpgtrdquo)

首先需要加載 url module它是用來協助我們解析 URL的模組接著使用 urlparse方法第一個傳入 url字串變數也就是 requrl另外第二個參數的用意是設為 ture則引進 querystring模組來協助處理預設是 false它影響到的是 urlDataquery設為 true會傳回物件不然就只是一般的字串urlparse會將字串內容整理成一個物件我們把它指定給 urlData

action變數作為記錄 pathname這是我們稍後要來判斷目前網頁的動作是什麼接著先將 html表頭資訊 (Header)準備好再來判斷路徑邏輯如

58 6 Express介紹

Nodejs中文電子書

果是 Signup這個動作就把 urlDataquery裡的資料指定給 user然後輸出userusername和 useremail把使用者從表單註冊的資料顯示於頁面中

最後進行程式測試啟動 nodejs主程式之後開啟瀏覽器就會看到表單填寫完畢按下送出就可以看到結果了

完整 nodejs程式碼如下

var http = require(rsquohttprsquo)

url = require(rsquourlrsquo)

fs = require(rdquofsrdquo)

server

server = httpcreateServer(function (reqres)

var urlData

encode = rdquoutf8rdquo

filePath = rdquoviewexpress_get_example_formhtmlrdquo

action

urlData = urlparse(requrltrue)

action = urlDatapathname

reswriteHead(200 rdquoContent-Typerdquordquotexthtml charset=utf-8rdquo)

if (action === rdquoSignuprdquo)

user = urlDataquery

resend(rdquolth1gtrdquo + userusername + rdquo 歡迎您的加入 lth1gtltpgt 我們已經將會員啟用信寄至rdquo + useremail + rdquoltpgtrdquo)

else

fsreadFile(filePath encode function(err file)

reswrite(file)

resend()

)

)

serverlisten(3000)

consolelog(rsquoServer 跑起來了現在時間是rsquo + new Date())

66 Express GET應用範例 59

Nodejs中文電子書

67 Express POST應用範例

一開始準備基本的 html表單傳送內容以 POST方式form的 action屬性設定為 POST其餘 html內容與前一個範例應用相同

ltDOCTYPE htmlgt

lthtmlgt

ltheadgt

ltmeta http-equiv=rdquoContent-Typerdquo content=rdquotexthtml charset=utf8rdquogt

lttitlegtNodejs 菜鳥筆記 (1)lttitlegt

ltlink rel=rdquostylesheetrdquo href=rdquocssstylecssrdquo type=rdquotextcssrdquo media=rdquoallrdquo gt

ltheadgt

ltbodygt

lth1gtNodejs 菜鳥筆記-註冊lth1gt

ltform id=rdquosignuprdquo method=rdquoPOSTrdquo action=rdquohttplocalhost3000Signuprdquogt

ltlabelgt 使用者名稱ltlabelgtltinput type=rdquotextrdquo id=rdquousernamerdquo name=rdquousernamerdquo gtltbrgt

ltlabelgt 電子郵件ltlabelgtltinput type=rdquotextrdquo id=rdquoemailrdquo name=rdquoemailrdquo gtltbrgt

ltinput type=rdquosubmitrdquo value=rdquo 註冊我的帳號rdquo gtltbrgt

ltformgt

ltbodygt

lthtmlgt

nodejs的程式處理邏輯與前面 GET範例類似部分程式碼如下

qs = require(rsquoquerystringrsquo)

if (action === rdquoSignuprdquo)

formData = rsquorsquo

reqon(rdquodatardquo function (data)

formData += data

)

reqon(rdquoendrdquo function ()

user = qsparse(formData)

resend(rdquolth1gtrdquo + userusername + rdquo 歡迎您的加入 lth1gtltpgt 我們已經將會員啟用信寄至rdquo + useremail + rdquoltpgtrdquo)

)

60 6 Express介紹

Nodejs中文電子書

主要加入了rsquoquerystringrsquo這個moduel方便我們等一下解析由表單 POST回來的資料另外加入一個 formData的變數用來搜集待等一下表單回傳的資料前面的 GET範例我們只從 req拿出 url的資料這次要在利用req身上的事件處理

JavaScript 在訂閱事件時使用 addEventListener而 nodejs 使用的則是on這邊加上了監聽 data的事件會在瀏覽器傳送資料到Web Server時被執行參數是它所接收到的資料型態是字串

接著再增加 end的事件當瀏覽器的請求事件結束時它就會動作

由於瀏覽器使用 POST在上傳資料時會將資料一塊塊地上傳因為我們在監聽 data事件時透過 formData變數將它累加起來lt不過由於我們

上傳的資料很少一次就結束不過如果日後需要傳的是資料比較大的檔

案這個累加動作就很重要

當資料傳完就進到 end 事件中會用到 qsparse 來解析 formDataformData的內容是字串內容是

username=wordsmithampemail=wordsmith40somewhere

而 qsparse可以幫我們把這個 querystring轉成物件的格式也就是

username=wordsmithampemail=wordsmith40somewhere

一旦轉成物件並指定給 user之後其他的事情就和 GET方法時操作的一樣寫 response的表頭將內容回傳並將 userusername和 useremail代入到內容中

修改完成後接著執行 nodejs程式啟動 web server開啟瀏覽器進入表單測試看看POST的方式能否順利運作

完整程式碼如下

var http = require(rsquohttprsquo)

url = require(rsquourlrsquo)

fs = require(rdquofsrdquo)

qs = require(rsquoquerystringrsquo)

server

server = httpcreateServer(function (reqres)

var urlData

67 Express POST應用範例 61

Nodejs中文電子書

encode = rdquoutf8rdquo

filePath = rdquoviewexpress_post_example_formhtmlrdquo

formData

action

urlData = urlparse(requrltrue)

action = urlDatapathname

reswriteHead(200 rdquoContent-Typerdquordquotexthtml charset=utf-8rdquo)

if (action === rdquoSignuprdquo)

formData = rsquorsquo

reqon(rdquodatardquo function (data)

formData += data

)

reqon(rdquoendrdquo function ()

user = qsparse(formData)

resend(rdquolth1gtrdquo + userusername + rdquo 歡迎您的加入 lth1gtltpgt 我們已經將會員啟用信寄至rdquo + useremail + rdquoltpgtrdquo)

)

else

fsreadFile(filePath encode function(err file)

reswrite(file)

resend()

)

)

serverlisten(3000)

consolelog(rsquoServer 跑起來了現在時間是rsquo + new Date())

62 6 Express介紹

Nodejs中文電子書

68 Express AJAX應用範例

在 Nodejs要使用 Ajax傳送資料並且與之互動在接受資料的部份沒有太大的差別client端不是用 GET就是用 POST來傳資料重點在處理完後用 JSON格式回傳當然 Ajax不見得只傳 JSON格式有時是回傳一段 HTML碼不過後者對伺服器來說基本上就和前兩篇沒有差別了所以我們還是以回傳 JSON做為這一回的主題

這一回其實大多數的工作都會落在前端 Ajax上面前端要負責發送與接收資料並在接收資料後撤掉原先發送資料的表單並將取得的資料

改成 HTML格式之後放上頁面

首先先準備 HTML靜態頁面資料

ltDOCTYPE htmlgt

lthtmlgt

ltheadgt

lttitlegtNodejs 菜鳥筆記 (3)lttitlegt

ltlink rel=rdquostylesheetrdquo href=rdquocssstylecssrdquo type=rdquotextcssrdquo media=rdquoallrdquo gt

ltscript src=rdquohttpsajaxgoogleapiscomajaxlibsjquery171jqueryminjsrdquogtltscriptgt

ltheadgt

ltbodygt

lth1gtNodejs 菜鳥筆記-註冊 (用 Ajax)lth1gt

ltform id=rdquosignuprdquo action=rdquoSignuprdquo method=rdquoPOSTrdquogt

ltlabelgt 使用者名稱ltlabelgtltinput type=rdquotextrdquo id=rdquousernamerdquo name=rdquousernamerdquo gtltbrgt

ltlabelgt 電子郵件ltlabelgtltinput type=rdquotextrdquo id=rdquoemailrdquo name=rdquoemailrdquo gtltbrgt

ltinput type=rdquosubmitrdquo value=rdquo 註冊我的帳號rdquo gtltbrgt

ltformgt

ltdiv id=rdquoresultrdquogt

ltdivgt

ltscript type=rdquotextjavascriptrdquogt

$(function()$(rsquosignup input[type=rdquosubmitrdquo]rsquo)click(function(e)

var user =

userusername = $(rdquousernamerdquo)val()useremail = $(rdquoemailrdquo)val()

$post(rdquoSignuprdquouserfunction(data)greet(data)

)

68 Express AJAX應用範例 63

Nodejs中文電子書

epreventDefault()

)

var greet = function(msg)

var resultNode = $(rsquoresultrsquo)greeting = $(rsquolth2gtHirsquo+msgusername+rsquo 你的會員 id 是rsquo+ msgid +rsquo 我們會將會員啟動信寄至rsquo+msgemail+rsquolth2gtrsquo)

$(rsquosignuprsquo)hide()resultNodehtml(rdquordquo)

resultNodeappend(greeting)

)

ltscriptgt

ltbodygt

lthtmlgt

HTML頁面上準備了一個表單用來傳送註冊資料接著直接引用了Google CDN來載入 jQuery用來幫我們處理 Ajax的工作這次要傳送和接收的工作很大的變動都在 HTML頁面上的 JavaScript當中我們要做的事有 (相關 jQuery處理這邊不多做贅述指提起主要功能解說)

bull 用 jQUery取得 submit按鈕綁定它的 click動作

bull 取得表單 username和 email的值存放在 user這個物件中

bull 用 jQuery的 $post方法將 user的資料傳到 Server

bull 一旦成功取得資料後透過 greet這個 function組成回報給 user的訊息

bull 清空原本給使用者填資料的表單

bull 將 Server回傳的 usernameemail和 id這 3個資料組成回應的訊息

bull 將訊息放到原本表單的位置

經過以上的處理後一個 Ajax的表單的基本功能已經完成

接著進行 nodejs主要程式的編輯部分程式碼如下

var fs = require(rdquofsrdquo)

64 6 Express介紹

Nodejs中文電子書

qs = require(rsquoquerystringrsquo)

if (action === rdquoSignuprdquo)

formData = rsquorsquo

reqon(rdquodatardquo function (data)

formData += data

)

reqon(rdquoendrdquo function ()

var msg

user = qsparse(formData)

userid = rdquo123456rdquo

msg = JSONstringify(user)

reswriteHead(200 rdquoContent-Typerdquordquoapplicationjson charset=utf-8rdquordquoContent-Lengthrdquomsglength)

resend(msg)

)

這裡的程式和前面 POST範例基本上大同小異差別在

bull 幫 user的資料加上 id隨意存放一些文字進去讓 Server回傳的資料多於 Client端傳上來的不然會覺得 Server都沒做事

bull 增加了 msg 這個變數存放將 user 物件 JSON 文字化的結果JSONstringify這個轉換函式是 V8引擎所提供的如果你好奇的話

bull 大重點來了我們要告訴 Client端這次回傳的資料格式是 JSON所在 Content-type和 Content-Length要提供給 Client

Server很輕鬆就完成任務了最後進行程式測試啟動 nodejs主程式之後開啟瀏覽器就會看到表單填寫完畢按下送出就可以看到結果了

最後 nodejs本篇範例程式碼如下

var http = require(rsquohttprsquo)

url = require(rsquourlrsquo)

fs = require(rdquofsrdquo)

qs = require(rsquoquerystringrsquo)

server

68 Express AJAX應用範例 65

Nodejs中文電子書

server = httpcreateServer(function (reqres)

var urlData

encode = rdquoutf8rdquo

filePath = rdquoviewexpress_ajax_example_formhtmlrdquo

formData

action

urlData = urlparse(requrltrue)

action = urlDatapathname

if (action === rdquoSignuprdquo)

formData = rsquorsquo

reqon(rdquodatardquo function (data)

formData += data

)

reqon(rdquoendrdquo function ()

var msg

user = qsparse(formData)

userid = rdquo123456rdquo

msg = JSONstringify(user)

reswriteHead(200 rdquoContent-TyperdquordquoapplicationjsonrdquordquoContent-Lengthrdquomsglength)

resend(msg)

)

else

fsreadFile(filePath encode function(err file)

reswriteHead(200 rdquoContent-Typerdquordquotexthtml charset=utf-8rdquo)

reswrite(file)

resend()

)

)

serverlisten(3000)

66 6 Express介紹

Nodejs中文電子書

consolelog(rsquoServer 跑起來了現在時間是rsquo + new Date())

69 原始資料提供

bull [NodeJS初學者筆記 (1)-用 GET傳送資料] (httpithelpithomecomtwquestion10087402)

bull [NodeJS初學者筆記 (2)-用 POST傳送資料] (httpithelpithomecomtwquestion10087489)

bull [NodeJS 初學者筆記 (3)-用 Ajax 傳送資料] (httpithelpithomecomtwquestion10087627)

69 原始資料提供 67

Nodejs中文電子書

68 6 Express介紹

7

CoffeeScript

bull Spine

bull Hem

bull Spineapp

Letrsquos Have a Cup of CoffeeScript

bull es5-shim ECMAScript 5 compatibility shims for legacy JavaScript engines

ESnext

node-iform - A middleware for node-validator httpsgithubcomguileennode-iform

69

Nodejs中文電子書

70 7 CoffeeScript

8

製作一個 Hubot的 Plurk Adapter

81 應用事項提醒

此應用範例以CoffeeScript編寫主要程式架構採用Github釋放的Hubot專案以下有幾個主要注意事項請讀者注意

bull Hubot 一開始的架構除了內建的兩個 Adapter 之外其餘都要以Nodejs的Module方式才能運作

bull Hubot的 binhubot裡面寫著 npm install所以不管你怎麼改原始碼也不能改變第一點的狀況

其實上述都在討論同一件事情NodeJS的模組而且模組不能設定相對路徑之類的來安裝一定要包裝成 npm相符和格式才有辦法正確執行

82 建立 Adapter

首先需要準備兩個檔案

bull packagejson

bull plurkcoffee

71

Nodejs中文電子書

有這兩個就足以變成 NodeJS的模組了在開始 Coding之前先來設定一下 packagejson相依性

rdquonamerdquo rdquohubot-plurkrdquo

rdquoversionrdquo rdquo011rdquo

rdquomainrdquo rdquoplurkrdquo

rdquodependenciesrdquo

rdquohubotrdquo rdquogt=205rdquo

rdquooauthrdquo rdquordquo

rdquocronrdquordquordquo

這邊會使用到NodeJS的 cron模組(Module)而登入 Plurk需要OAuth標準授權才行所以也需要加載 OAuth模組

注意Hubot在讀取非內建模組時會自動在前面加上 hubot-的前置

83 建立 Robot跟 API

本篇應用基本上程式碼大部分參考 Hubot - Twitter Adapter來製作主要差異只有在使用 OAuth這個模組Twitter有 Streaming可用而 Plurk則得用 Comet方式來達到即時讀取

plurkcoffee程式碼

Robot = require(rdquohubotrdquo)robot()

Adapter = require(rdquohubtordquo)adapter()

EventEmitter = require(rdquoeventsrdquo)EventEmitter

oauth = require(rdquooauthrdquo)

cronJob = require(rdquocronrdquo)CronJob

class Plurk exntends Adapter

class PlurkStreaming exnteds EventEmitter

72 8 製作一個 Hubot的 Plurk Adapter

Nodejs中文電子書

先弄個基本架構主要 Class皆參考 Twitter Adapter命名方式接著再將 Plurk這個 Class增加幾個Method基本上只要有 run send reply就夠了而 run用來做初始化的部分

class Plurk entends Adapter

send (plurk id strings⋯) -gt

reply (plurk id strings⋯) -gt

run -gt

看起來有點東西接著來處理主要的 Plurk API結合部分

class PlurkStreaming extends EventEmitter

consuctor (options) -gt

plurk (callback) -gt

觀察河道

getChannel -gt

取得 Comet 網址

reply (plurk id message) -gt

回噗

acceptFriends -gt

接受好友

get (path callback) -gt

GET 請求

post (path body callback)-gt

POST 請求(其實是裝飾)

request (method path body callback)-gt

主要的 OAuth 請求

comet (server callback)-gt

噗浪的 Comet 傳回是 JavaScript Callback 要另外處理後才會變成 JSON

然後把注意力集中到 constructor上先把建構子弄好

constructor (options) -gt

super()

if optionskey and optionssecret and optionstoken and optionstoken secret

key = optionskey

secret = optionssecret

token = optionstoken

token secret = optionstoken secret

83 建立 Robot跟 API 73

Nodejs中文電子書

建立 OAuth 連接

consumer = new oauthOAuth(

rdquohttpwwwplurkcomOAuthrequest tokenrdquo

rdquohttpwwwplurkcomOAuthaccess tokenrdquo

key

secret

rdquo10rdquo

rdquohttpwwwplurkcomOAuthauthorizerdquo

rdquoHMAC-SHA1rdquo

)

domain = rdquowwwplurkcomrdquo

初始化取得 Comet 網址

do getChannel

else

throw new Error(rdquo 參數不足需要 Key Secret Token Token Secretrdquo)

接著來處理 request這個 method

request (method path body callback) -gt

記錄一下這次的 Request

consolelog(rdquohttpdomainpathrdquo)

Callback 這邊先不丟進去要用另一種方式處理

request = consumerget(rdquohttpdomainpathrdquo token token secret null)

requeston rdquoresponserdquo (res) -gt

reson rdquodatardquo (chunk) -gt

parseResponse(chunk+rsquorsquo callback)

reson rdquoendrdquo (data) -gt

consolelog rdquoEnd Request pathrdquo

reson rdquoerrorrdquo (data) -gt

consolelog rdquoError rdquo + data

requestend()

處理資料

parseResponse = (data callback) -gt

if datalength gt 0

用 TryCatch 避免處理 JSON 出錯導致整個中斷

try

callback null JSONparse(data)

catch err

74 8 製作一個 Hubot的 Plurk Adapter

Nodejs中文電子書

consolelog(rdquoError Parse JSONrdquo + data err)

繼續執行

callback null data ||

大致上就是這樣上面程式的架構已經將整個 Hubot Plurk Adapter完成因為在測試時竟然因為噗浪 Lag而沒讀到完整的 Comet資料然後造成程式異常為了避免這個問題發生因此需要加上為了完美呈現需要再

加上 Comet的處理所以要使用到 EventEmitter的功能

comet (server callback) -gt

在 Callback 裡面會找不到自身所以設定區域變數

self =

記錄一下這次的 Request

consolelog(rdquo[Comet] serverrdquo)

Callback 這邊先不丟進去要用另一種方式處理

request = consumerget(rdquohttpdomainpathrdquo token token secret null)

requeston rdquoresponserdquo (res) -gt

reson rdquodatardquo (chunk) -gt

parseResponse(chunk+rsquorsquo callback)

reson rdquoendrdquo (data) -gt

consolelog rdquoEnd Request pathrdquo

請求結束發出事件通知可以進行下一次請求

selfemit rdquonextPlurkrdquo

reson rdquoerrorrdquo (data) -gt

consolelog rdquoError rdquo + data

requestend()

處理資料

parseResponse = (data callback) -gt

if datalength gt 0

用 trycatch 避免失敗中斷

try

去掉 JavaScript 的 Callback

data = datamatch(CometChannelscriptCallback((+))s)

jsonData = rdquordquo

if data

83 建立 Robot跟 API 75

Nodejs中文電子書

jsonData = JSONparse(data[1])

else

如果沒有任何 Match 嘗試直接 parse

jsonData = JSONparse(data)

catch err

consolelog(rdquo[Comet] Errorrdquo data err)

用 TryCatch 避免處理 JSON 出錯導致整個中斷

try

只傳入 json 的 data 部分

callback null jsonDatadata

catch err

consolelog(rdquo[Comet]Error Parse JSONrdquo + data err)

繼續執行

callback null data ||

後面的 get跟 post就簡單多了

get (path callback) -gt

request(rdquoGETrdquo path null callback)

post (path body callback) -gt

request(rdquoPOSTrdquo path body callback)

接著處理取的 Comet網址的 getChannel

getChannel -gt

self =

get rdquoAPPRealtimegetUserChannelrdquo (error data) -gt

if error

檢查是否有 comet server

if datacomet server

selfchannel = datacomet server

如果沒有 Channel Ready 就嘗試連接會失敗

selfemit(rsquochannel readyrsquo)

那麼先來處理 Plurk Adaper好處理的部份

send (plruk id strings⋯)-gt

跟 Reply 一樣直接交給 reply 做

reply plurk id strings⋯

76 8 製作一個 Hubot的 Plurk Adapter

Nodejs中文電子書

reply (plurk id strings⋯) -gt

stringsforEach (message) =gt

botreply(plruk id message)

接著把 run處理好就可以上線運作摟

run -gt

self =

options =

key processenvHUBOT PLURK KEY

secret processenvHUBOT PLURK SECRET

token processenvHUBOT PLURK TOKEN

token secret processenvHUBOT PLURK TOKEN SECRET

創建剛剛的 API

bot = new PlurkStreaming(options)

依照 Twitter 的 new RobotTextMessage 會沒有反應所以參考 hubot-minecraft 的方式

r = robotconstructor

處理噗浪河道訊息

doPlurk = (data)-gt

檢查是否為回噗

if dataresponse

datacontent raw = dataresponsecontent raw

datauser id = dataresponseuser id

確定有噗浪 ID 跟訊息

if dataplurk id and datacontent raw

selfreceive new rTextMessage(dataplurk id datacontent raw)

取得 Comet Server 完成開始第一次 Comet 連接

boton rdquochannel readyrdquo () -gt

botplurk selfdoPlurk

上一次 Comet 完成繼續 Polling

boton rdquonextPlurkrdquo ()-gt

botplurk selfdoPlurk

定時接受好友邀請

do botacceptFriends

bot = bot

83 建立 Robot跟 API 77

Nodejs中文電子書

終於完成 AdapterHubot專案裡面的 scripts資料夾內是互動部分不需要像 Adapter如此大費周章處理只新增檔案並且設計好對白之後就會回噗了我開發用的機器人在此大家可以去跟他玩玩[httpplurkcomelct9620 bot](httpplurkcomelct9620 bot)

84 原始資料提供

bull [製 作 一 個 Hubot 的 噗 浪 Adapter](http revo-skillfrosttw blog20120318create-a-hubot-plurk-adapter)

78 8 製作一個 Hubot的 Plurk Adapter

9

Nodejs好用工具介紹

91 logio

Real-time log monitoring in your browser

httplogioorg

nodejs (gt=0412 lt=0611)

npm config set unsafe-perm true

npm install -g --prefix=usrlocal logio

logio server start

httplocalhost8998

79

Nodejs中文電子書

80 9 Nodejs好用工具介紹

10

精選文章

101 Express

bull 使用 NodeJS + Express從 GETPOST Request取值 [邀稿中]

bull link2 [邀稿中]

bull link3 [邀稿中]

81

Nodejs中文電子書

82 10 精選文章

11

參考資源

111 Nodejs書籍

bull Programming Nodejs April 2012 (est) Travis Swicegood Pragmatic Bookshelf

bull e Little Book on CoffeeScript January 2012 Alex MacCaw OrsquoReilly Media

bull Node for Front-End Developers January 2012 Garann Means OrsquoReilly Media

bull Building Hypermedia APIs with HTML5 and Node November 2011 MikeAmundsen OrsquoReilly Media

bull Node Web Development August 2011 David Herron PacktPub

bull CoffeeScript Accelerated JavaScript Development July 2011 Trevor BurnhamPragmatic Bookshelf

bull Getting Started with GEO CouchDB and Nodejs July 2011 Mick omp-son OrsquoReilly Media

bull What Is Node July 2011 Brett McLaughlin OrsquoReilly Media

bull Node Up and Running May 2011 Tom Hughes-Croucher Mike WilsonOrsquoReilly Media

83

Nodejs中文電子書

bull Hands-on Nodejs e Nodejs introduction and API reference May 2011 Pe-dro Teixeira LeanPub

bull e Node Beginner Book Manuel Kiessling

bull Mastering Nodejs visionmedia

112 Nodejs影音教學

bull e Node Sessions e Best of OSCON 2011 August 2011 OrsquoReilly Media(Video)

bull Tom Hughes-Croucher on Node March 2011 OrsquoReilly Media (Video)

113 Nodejs教學網站

bull Node Tuts

114 Nodejs課程

bull Introduction To Nodejs Van Nguyen CodeLesson

84 11 參考資源

Page 45: Nodejs Wiki

Nodejs中文電子書

端基本的傳送網址格式可能如下

http127001testsend=1amptest=2

上面這段網址裡面的 GET參數就是 send而這個變數的數值就為 1如果想要在 http伺服器取得 GET資料需要在瀏覽器給予的要求 (request)做處理

首先需要載入 query string這個模組這個模組主要是用來將字串資料

過濾後轉換成javascript物件

qs = require(rsquoquerystringrsquo)

接著在第一階段利用 url模組過濾瀏覽器發出的 URL資料後將回應的物件裡面的 query這個變數是一個字串值資料過濾後如下

send=1amptest=2

透過 query string使用 parse這個方法將資料轉換成 javascript物件就表示 GET的資料已經被伺服器端正式擷取下來

path = urlparse(requrl)

parameter = qsparse(pathquery)

整個 nodejs http GET參數完整擷取程式碼如下

var server

ip = rdquo127001rdquo

port = 1337

http = require(rsquohttprsquo)

qs = require(rsquoquerystringrsquo)

url = require(rsquourlrsquo)

server = httpcreateServer(function (req res)

var path = urlparse(requrl)

parameter = qsparse(pathquery)

consoledir(parameter)

reswriteHead(200 rsquoContent-Typersquo rsquotextplainrsquo)

reswrite(rsquoBrowser test GET parameternrsquo)

resend()

)

45 nodejs http GET資料擷取 39

Nodejs中文電子書

serverlisten(port ip)

consolelog(rdquoServer running at httprdquo + ip + rdquordquo + port)

程式運作之後由瀏覽器輸入要求網址之後nodejs伺服器端回應資料為

Server running at http1270011337

send rsquo1rsquo test rsquo1rsquo

46 本章結語

前面所解說的部份一大部分主要是處理 http伺服器基本問題雖然在某些部分有牽扯到 http 伺服器基本運作原理主要還是希望可以藉由這些基本範例練習 nodejs練習回應函式與語法串接的特點習慣編寫javascript風格程式當然直接這樣開發 nodejs是非常辛苦的接下來在模組實戰開發的部份將會介紹特定的模組一步一步帶領各位從無到有進行

nodejs應用程式開發

40 4 Nodejs基礎

5

NPM套件管理工具

npm全名為 Node Package Manager是 Nodejs的套件(package)管理工具類似 Perl的 ppm或 PHP的 PEAR等安裝 npm後使用npm install

module name指令即可安裝新套件維護管理套件的工作會更加輕鬆

npm可以讓Nodejs的開發者直接利用擴充線上的套件庫(packagesregistry)加速軟體專案的開發npm提供很友善的搜尋功能可以快速找到安裝需要的套件當這些套件發行新版本時npm也可以協助開發者自動更新這些套件

npm不僅可用於安裝新的套件它也支援搜尋列出已安裝模組及更新的功能

51 安裝 NPM

Nodejs在 063版本開始內建 npm讀者安裝的版本若是此版本或更新的版本就可以略過以下安裝說明

若要檢查 npm是否正確安裝可以使用以下的指令

npm -v

41

Nodejs中文電子書

執行結果說明

若 npm正確安裝執行 npm -v將會看到類似 110-2的版本訊息

若讀者安裝的 Nodejs版本比較舊或是有興趣嘗試自己動手安裝 npm工具則可以參考以下的說明

安裝於Windows系統

Nodejs for Windows 於 062 版開始內建 npm使用 nodejsorg 官方提供的安裝程式不需要進一步的設定就可以立即使用 npm指令對於Windows的開發者來說大幅降低環境設定的問題與門檻

除了使用 Nodejs內建的 npm讀者也可以從 npm官方提供的以下網址

httpnpmjsorgdist

這是由 npm提供的 Fancy Windows Install版本請下載壓縮檔(例如npm-110-3zip)並將壓縮檔內容解壓縮至 Nodejs的安裝路徑(例如CProgram Filesnodejs)

解壓縮後在 Nodejs的安裝路徑下應該有以下的檔案及資料夾

bull npmcmd(檔案)

bull node modules(資料夾)

安裝於 Linux系統

Ubuntu Linux的使用者可以加入 NPM Unoffcial PPA這個 repository即可使用 apt-get完成 npm安裝

42 5 NPM套件管理工具

Nodejs中文電子書

Ubuntu Linux使用 apt-get安裝 npm

sudo apt-get install python-software-properties

sudo add-apt-repository ppagias-kay-leenpm

sudo apt-get update

sudo apt-get install npm

npm官方提供的安裝程式 installsh可以適用於大多數的 Linux系統使用這個安裝程式請先確認

1 系統已安裝 curl工具(請使用 curl --version查看版本訊息)

2 已安裝 Nodejs並且 PATH正確設置

3 Nodejs的版本必須大於 04x

以下為 npm提供的安裝指令

curl httpnpmjsorginstallsh | sh

安裝成功會看到如下訊息

installsh安裝成功的訊息

npm10105 homeUSERNAMElocalnodelibnode_modulesnpm

It worked

安裝於Mac OS X

建議採用與 Nodejs相同的方式進行 npm的安裝例如使用MacPorts安裝 Nodejs就同樣使用MacPorts安裝 npm這樣對日後的維護才會更方便容易

使用 MacPorts安裝 npm是本書比較建議的方式它可以讓 npm的安裝移除及更新工作自動化將會幫助開發者節省寶貴時間

51 安裝 NPM 43

Nodejs中文電子書

安裝MacPorts的提示

在MacPorts網站可以取得 OS X系統版本對應的安裝程式(例如 106或 107)httpwwwmacportsorg安裝過程會詢問系統管理者密碼使用預設的選項完成安裝即可安

裝MacPorts之後在終端機執行 port -v將會看到MacPorts的版本訊息

安裝 npm之前先更新MacPorts的套件清單以確保安裝的 npm是最新版本

sudo port -d selfupdate

接著安裝 npm

sudo port install npm

若讀者的 Nodejs並非使用MacPorts安裝則不建議使用MacPorts安裝npm因為 MacPorts會自動檢查並安裝相依套件而 npm相依 nodejs所以MacPorts也會一併將 nodejs套件安裝造成先前讀者使用其它方式安裝的 nodejs被覆蓋

讀者可以先使用 MacPorts安裝 curl(sudo port install curl)

再參考 Linux的 installsh安裝方式即可使用 npm官方提供的安裝程式

NPM安裝後測試

npm是指令列工具(command-line tool)使用時請先打開系統的文字終端機工具

測試 npm安裝與設定是否正確請輸入指令如下

npm -v

或是

npm --version

如果 npm已經正確安裝設定就會顯示版本訊息

44 5 NPM套件管理工具

Nodejs中文電子書

執行結果(範例)

110-2

52 使用 NPM安裝套件

npm目前擁有超過 6000種套件(packages)可以在 npm registry使用關鍵字搜尋套件

httpsearchnpmjsorg

舉例來說在關鍵字欄位輸入「coffee-script」下方的清單就會自動列出包含 coffee-script關鍵字的套件

接著我們回到終端機模式的操作npm的指令工具本身就可以完成套

件搜尋的任務

例如以下的指令同樣可以找出 coffee-script相關套件

npm search coffee-script

以下是搜尋結果的參考畫面

52 使用 NPM安裝套件 45

Nodejs中文電子書

找到需要的套件後(例如 express)即可使用以下指令安裝

npm install coffee-script

值得注意的一點是使用 npm install會將指定的套件安裝在工

作目錄(Working Directory)的 node modules資料夾下

以Windows為例如果執行 npm install的目錄位於

Cproject1

那麼 npm將會自動建立一個 node modules的子目錄(如果不存在)

Cproject1node modules

並且將下載的套件放置於這個子目錄例如

Cproject1node modulescoffee-script

這個設計讓專案可以個別管理相依的套件並且可以在專案佈署或發

行時將這些套件(位於 node modules)一併打包方便其它專案的使用者不必再重新下載套件

這個 npm install的預設安裝模式為 local(本地)只會變更當前專案的資料夾不會影響系統

另一種安裝模式稱為 global(全域)這種模式會將套件安裝到系統資

料夾也就是 npm安裝路徑的 node modules資料夾例如

CProgram Filesnodejsnode modules

46 5 NPM套件管理工具

Nodejs中文電子書

是否要使用全域安裝可以依照套件是否提供新指令來判斷舉例來說express 套件提供 express 這個指令而 coffee-script 則提供 coffee

指令

在 local 安 裝 模 式 中這 些 指 令 的 程 式 檔 案會 被 安 裝 到node modules的 bin這個隱藏資料夾下除非將bin的路徑加入 PATH環境變數否則要執行這些指令將會相當不便

為了方便指令的執行我們可以在 npm install 加上 -g 或 --

global參數啟用 global安裝模式例如

npm install -g coffee-script

npm install -g express

使用 global安裝模式需要注意執行權限的問題若權限不足可能會出現類似以下的錯誤訊息

npm ERR Error EACCES permission denied rsquorsquo

npm ERR

npm ERR Please try running this command again as rootAdministrator

要獲得足夠得執行權限請參考以下說明

bull Windows 7 或 2008 以上在「命令提示字元」的捷徑按右鍵選擇「以系統管理員身分執行」執行 npm指令時就會具有 Administrator身分

bull Mac OS X或 Linux系統可以使用 sudo指令例如

sudo npm install -g express

bull Linux系統可以使用 root權限登入或是以「sudo su -」切換成 root身分(使用 root權限操作系統相當危險因此並不建議使用這種方式)

若加上 -g參數使用 npm install -g coffee-script完成安裝

後就可以在終端機執行 coffee指令例如

coffee -v

52 使用 NPM安裝套件 47

Nodejs中文電子書

執行結果(範例)

CoffeeScript version 120

53 套件的更新及維護

除了前一節說明的 search 及 install 用法npm 還提供其他許多指令(commands)

使用 npm help可以查詢可用的指令

npm help

執行結果(部分)

where ltcommandgt is one of

adduser apihelp author bin bugs c cache completion

config deprecate docs edit explore faq find get

help help-search home i info init install la link

list ll ln login ls outdated owner pack prefix

prune publish r rb rebuild remove restart rm root

run-script s se search set show star start stop

submodule tag test un uninstall unlink unpublish

unstar up update version view whoami

使用 npm help command可以查詢指令的詳細用法例如

npm help list

接下來本節要介紹開發過程常用的 npm指令

使用 list可以列出已安裝套件

npm list

48 5 NPM套件管理工具

Nodejs中文電子書

執行結果(範例)

-- coffee-script120

- express256

- connect185

| -- formidable108

-- mime124

-- mkdirp007

-- qs041

檢視某個套件的詳細資訊例如

npm show express

升級所有套件(如果該套件已發佈更新版本)

npm update

升級指定的套件

npm update express

移除指定的套件

npm uninstall express

54 使用 packagejson

對於正式的 Nodejs專案可以建立一個命名為 packagejson的設

定檔(純文字格式)檔案內容參考範例如下

54 使用 packagejson 49

Nodejs中文電子書

packagejson(範例)

rdquonamerdquo rdquoapplication-namerdquo

rdquoversionrdquo rdquo001rdquo

rdquoprivaterdquo true

rdquodependenciesrdquo

rdquoexpressrdquo rdquo255rdquo

rdquocoffee-scriptrdquo rdquolatestrdquo

rdquomongooserdquo rdquogt= 253rdquo

其中 name與 version依照專案的需求設置

需要注意的是 dependencies的設定它用於指定專案相依的套件名

稱及版本

bull rdquoexpressrdquo rdquo255rdquo

代表此專案相依版本 255的 express套件

bull rdquocoffee-scriptrdquo rdquolatestrdquo

使用最新版的 coffee-script套件(每次更新都會檢查新版)

bull rdquomongooserdquo rdquogt= 253rdquo

使用版本大於 253的 mongoose套件

假設某個套件的新版可能造成專案無法正常運作就必須指定套件的

版本避免專案的程式碼來不及更新以相容新版套件通常在開發初期的

專案需要盡可能維持新套件的相容性(以取得套件的更新或修正)可以

用「gt=」設定最低相容的版本或是使用「latest」設定永遠保持最新

套件

50 5 NPM套件管理工具

6

Express介紹

在前面的 nodejs基礎當中介紹許多許多開設 http的使用方法及介紹以及許多基本的 nodejs基本應用

接下來要介紹一個套件稱為 express [Express](httpexpressjscom)這個套件主要幫忙解決許多 nodejs http server所需要的基本服務讓開發 httpservice變得更為容易不需要像之前需要透過層層模組(module)才有辦法開始編寫自己的程式

這個套件是由 TJ Holowaychuk 製作而成的套件裡面包含基本的路由處理 (route)http資料處理(GETPOSTPUT)另外還與樣板套件(jshtml template engine)搭配同時也可以處理許多複雜化的問題

61 Express安裝

安裝方式十分簡單只要透過之前介紹的 NPM就可以使用簡單的指令安裝指令如下

這邊建議需要將此套件安裝成為全域模組方便日後使用

51

Nodejs中文電子書

62 Express基本操作

express的使用也十分簡單先來建立一個基本的 hello world

var app = require(rsquoexpressrsquo)createServer()

port = 1337

applisten(port)

appget(rsquorsquo function(req res)

ressend(rsquohello worldrsquo)

)

consolelog(rsquostart express servernrsquo)

可以從上面的程式碼發現基本操作與 nodejs http的建立方式沒有太大差異主要差在當我們設定路由時可以直接透過 appget方式設定回應與接受方式

63 Express路由處理

Express對於 http服務上有許多包裝讓開發者使用及設定上更為方便例如有幾個路由設定那我們就統一藉由 appget來處理

Create http server

appget(rsquorsquo function(req res)

ressend(rsquohello worldrsquo)

)

appget(rsquotestrsquo function(req res)

ressend(rsquotest renderrsquo)

)

appget(rsquouserrsquo function(req res)

ressend(rsquouser pagersquo)

)

52 6 Express介紹

Nodejs中文電子書

如上面的程式碼所表示appget可以帶入兩個參數第一個是路徑名稱設定第二個為回應函式 (call back function)回應函式裡面就如同之前的 createServer方法裡面包含 requestresponse兩個物件可供使用使用者就可以透過瀏覽器輸入不同的 url切換到不同的頁面顯示不同的結果

路由設定上也有基本的配對方式讓使用者從瀏覽器輸入的網址可以

是一個變數只要符合型態就可以有對應的頁面產出例如

Create http server

appget(rsquouseridrsquo function(req res)

ressend(rsquouser rsquo + reqparamsid)

)

appget(rsquonumberrsquo function(req res)

ressend(rsquonumber rsquo + reqparamsnumber)

)

裡 面 使 用 到number 從 網 址 輸 入 之 後 就 可 以 直 接 使 用reqparamsnumber 取得所輸入的資料變成 url 參數使用當然前面也是可以加上路徑的設定userid在瀏覽器上路徑必須符合userxxx透

過 reqparamsid就可以取到 xxx這個字串值

另外express參數處理也提供了路由參數配對處理也可以透過正規表示法作為參數設定

var app = require(rsquoexpressrsquo)createServer()

port = 1337

applisten(port)

appget(^ip((d23)((d23))((d23))((d23))) function(req res)

ressend(reqparams)

)

上面程式碼可以發現後面路由設定的型態是正規表示法裡面設定

格式為ip之後必須要加上 ip型態才會符合資料格式同時取得 ip資料已經由正規表示法將資料做分群因此可以取得 ip的四個數字

此程式執行之後可以透過瀏覽器測試輸入網址為 localhost3000ip25525510010可以從頁面獲得資料

63 Express路由處理 53

Nodejs中文電子書

此章節全部範例程式碼如下

overview

author Caesar Chi

blog clonnblogspotcom

version 20120226

create server

var app = require(rsquoexpressrsquo)createServer()

port = 1337

applisten(port)

normal style

appget(rsquorsquo function(req res)

ressend(rsquohello worldrsquo)

)

appget(rsquotestrsquo function(req res)

ressend(rsquotest renderrsquo)

)

parameter style

appget(rsquouseridrsquo function(req res)

ressend(rsquouser rsquo + reqparamsid)

)

appget(rsquonumberrsquo function(req res)

ressend(rsquonumber rsquo + reqparamsnumber)

)

REGX style

appget(^ip((d23)((d23))((d23))) function(req res)

ressend(reqparams)

)

appget(rsquorsquo function(req res)

ressend(rsquoPage not foundrsquo 404)

54 6 Express介紹

Nodejs中文電子書

)

consolelog(rsquostart express servernrsquo)

64 Express middleware

Express 裡面有一個十分好用的應用概念稱為 middleware可以透過middleware做出複雜的效果同時上面也有介紹 next方法參數傳遞就是靠 middleware的概念來傳遞參數讓開發者可以明確的控制程式邏輯

create http server

appuse(expressbodyParser())

appuse(expressmethodOverride())

appuse(expresssession())

上面都是一種 middleware的使用方式透過 appuse方式裡面載入函式執行方法回應函式會包含三個基本參數responserequestnext其中next表示下一個 middleware執行函式同時會自動將預設三個參數繼續帶往下個函式執行底下有個實驗

上面的片段程式執行後開啟瀏覽器連結上 localhost1337會發現伺服器回應結果順序如下

first middle ware

second middle ware

execute middle ware

end middleware function

從上面的結果可以得知剛才設定的 middleware都生效了在 appuse設定的 middleware是所有 url皆會執行方法如果有指定特定方法就可以使用 appget的 middleware設定在 appget函式的第二個參數就可以帶入函式或者是匿名函式只要函式裡面最後會接受 request response next這三個參數同時也有正確指定 next函式的執行時機最後都會執行到最後一個方法當然開發者也可以評估程式邏輯要執行到哪一個階段讓邏輯

可以更為分明

64 Express middleware 55

Nodejs中文電子書

65 Express路由應用

在實際開發上可能會遇到需要使用參數等方式混和變數一起使用

express裡面提供了一個很棒的處理方法 appall這個方式可以先採用基本路由配對再將設定為每個不同的處理方式開發者可以透過這個方式簡

化自己的程式邏輯

overview

author

version 20120226

create server

var app = require(rsquoexpressrsquo)createServer()

port = 1337

users = [

name rsquoClonnrsquo

name rsquoChirsquo

]

applisten(port)

appall(rsquouseridoprsquo function(req res next)

requser = users[reqparamsid]

if (requser)

next()

else

next(new Error(rsquocannot find user rsquo + reqparamsid))

)

appget(rsquouseridrsquo function(req res)

ressend(rsquoviewing rsquo + requsername)

)

appget(rsquouserideditrsquo function(req res)

ressend(rsquoediting rsquo + requsername)

)

56 6 Express介紹

Nodejs中文電子書

appget(rsquouseriddeletersquo function(req res)

ressend(rsquodeleting rsquo + requsername)

)

appget(rsquorsquo function(req res)

ressend(rsquoPage not foundrsquo 404)

)

consolelog(rsquostart express servernrsquo)

內部宣告一組預設的使用者分別給予名稱設定藉由 appall這個方法可以先將路由雛形建立再接下來設定 appget的路徑格式只要符合格式就會分配進入對應的方法中像上面的程式當中如果使用者輸入路徑為user0除了執行 appall程式之後執行 next方法就會對應到路徑設定為userid的這個方法當中如果使用者輸入路徑為user0edit就會執行到useridedit的對應方法

66 Express GET應用範例

我們準備一個使用 GET方法傳送資料的表單

ltDOCTYPE htmlgt

lthtmlgt

ltheadgt

ltmeta http-equiv=rdquoContent-Typerdquo content=rdquotexthtml charset=utf8rdquogt

lttitlegtNodejs 菜鳥筆記 (1)lttitlegt

ltlink rel=rdquostylesheetrdquo href=rdquocssstylecssrdquo type=rdquotextcssrdquo media=rdquoallrdquo gt

ltheadgt

ltbodygt

lth1gtNodejs 菜鳥筆記-註冊lth1gt

ltform id=rdquosignuprdquo method=rdquoGETrdquo action=rdquohttplocalhost3000Signuprdquogt

ltlabelgt 使用者名稱ltlabelgtltinput type=rdquotextrdquo id=rdquousernamerdquo name=rdquousernamerdquo gtltbrgt

ltlabelgt 電子郵件ltlabelgtltinput type=rdquotextrdquo id=rdquoemailrdquo name=rdquoemailrdquo gtltbrgt

ltinput type=rdquosubmitrdquo value=rdquo 註冊我的帳號rdquo gtltbrgt

ltformgt

ltbodygt

lthtmlgt

66 Express GET應用範例 57

Nodejs中文電子書

這個表單沒有什麼特別的地方我們只需要看第 9 行form 使用的method是 GET然後 action是rdquohttplocalhost3000Signupldquo等一下我們要來撰寫Signup這個 URL Path的處理程式

處理 Signup行為

我們知道所謂的 GET方法會透過 URL來把表單的值給帶過去以上面的表單來說到時候 URL會以這樣的形式傳遞

httplocalhost3000Signupusername=xxxampemail=xxx

所以要能處理這樣的資料必須有以下功能

bull 解析 URL

bull 辨別動作是 Signup

bull 解析出 username和 email

一旦能取得 username和 email的值程式就能加以應用了

處理 Signup的程式碼雛形

load module

var url = require(rsquourlrsquo)

urlData = urlparse(requrltrue)

action = urlDatapathname

reswriteHead(200 rdquoContent-Typerdquordquotexthtml charset=utf-8rdquo)

if (action === rdquoSignuprdquo)

user = urlDataquery

resend(rdquolth1gtrdquo + userusername + rdquo 歡迎您的加入 lth1gtltpgt 我們已經將會員啟用信寄至rdquo + useremail + rdquoltpgtrdquo)

首先需要加載 url module它是用來協助我們解析 URL的模組接著使用 urlparse方法第一個傳入 url字串變數也就是 requrl另外第二個參數的用意是設為 ture則引進 querystring模組來協助處理預設是 false它影響到的是 urlDataquery設為 true會傳回物件不然就只是一般的字串urlparse會將字串內容整理成一個物件我們把它指定給 urlData

action變數作為記錄 pathname這是我們稍後要來判斷目前網頁的動作是什麼接著先將 html表頭資訊 (Header)準備好再來判斷路徑邏輯如

58 6 Express介紹

Nodejs中文電子書

果是 Signup這個動作就把 urlDataquery裡的資料指定給 user然後輸出userusername和 useremail把使用者從表單註冊的資料顯示於頁面中

最後進行程式測試啟動 nodejs主程式之後開啟瀏覽器就會看到表單填寫完畢按下送出就可以看到結果了

完整 nodejs程式碼如下

var http = require(rsquohttprsquo)

url = require(rsquourlrsquo)

fs = require(rdquofsrdquo)

server

server = httpcreateServer(function (reqres)

var urlData

encode = rdquoutf8rdquo

filePath = rdquoviewexpress_get_example_formhtmlrdquo

action

urlData = urlparse(requrltrue)

action = urlDatapathname

reswriteHead(200 rdquoContent-Typerdquordquotexthtml charset=utf-8rdquo)

if (action === rdquoSignuprdquo)

user = urlDataquery

resend(rdquolth1gtrdquo + userusername + rdquo 歡迎您的加入 lth1gtltpgt 我們已經將會員啟用信寄至rdquo + useremail + rdquoltpgtrdquo)

else

fsreadFile(filePath encode function(err file)

reswrite(file)

resend()

)

)

serverlisten(3000)

consolelog(rsquoServer 跑起來了現在時間是rsquo + new Date())

66 Express GET應用範例 59

Nodejs中文電子書

67 Express POST應用範例

一開始準備基本的 html表單傳送內容以 POST方式form的 action屬性設定為 POST其餘 html內容與前一個範例應用相同

ltDOCTYPE htmlgt

lthtmlgt

ltheadgt

ltmeta http-equiv=rdquoContent-Typerdquo content=rdquotexthtml charset=utf8rdquogt

lttitlegtNodejs 菜鳥筆記 (1)lttitlegt

ltlink rel=rdquostylesheetrdquo href=rdquocssstylecssrdquo type=rdquotextcssrdquo media=rdquoallrdquo gt

ltheadgt

ltbodygt

lth1gtNodejs 菜鳥筆記-註冊lth1gt

ltform id=rdquosignuprdquo method=rdquoPOSTrdquo action=rdquohttplocalhost3000Signuprdquogt

ltlabelgt 使用者名稱ltlabelgtltinput type=rdquotextrdquo id=rdquousernamerdquo name=rdquousernamerdquo gtltbrgt

ltlabelgt 電子郵件ltlabelgtltinput type=rdquotextrdquo id=rdquoemailrdquo name=rdquoemailrdquo gtltbrgt

ltinput type=rdquosubmitrdquo value=rdquo 註冊我的帳號rdquo gtltbrgt

ltformgt

ltbodygt

lthtmlgt

nodejs的程式處理邏輯與前面 GET範例類似部分程式碼如下

qs = require(rsquoquerystringrsquo)

if (action === rdquoSignuprdquo)

formData = rsquorsquo

reqon(rdquodatardquo function (data)

formData += data

)

reqon(rdquoendrdquo function ()

user = qsparse(formData)

resend(rdquolth1gtrdquo + userusername + rdquo 歡迎您的加入 lth1gtltpgt 我們已經將會員啟用信寄至rdquo + useremail + rdquoltpgtrdquo)

)

60 6 Express介紹

Nodejs中文電子書

主要加入了rsquoquerystringrsquo這個moduel方便我們等一下解析由表單 POST回來的資料另外加入一個 formData的變數用來搜集待等一下表單回傳的資料前面的 GET範例我們只從 req拿出 url的資料這次要在利用req身上的事件處理

JavaScript 在訂閱事件時使用 addEventListener而 nodejs 使用的則是on這邊加上了監聽 data的事件會在瀏覽器傳送資料到Web Server時被執行參數是它所接收到的資料型態是字串

接著再增加 end的事件當瀏覽器的請求事件結束時它就會動作

由於瀏覽器使用 POST在上傳資料時會將資料一塊塊地上傳因為我們在監聽 data事件時透過 formData變數將它累加起來lt不過由於我們

上傳的資料很少一次就結束不過如果日後需要傳的是資料比較大的檔

案這個累加動作就很重要

當資料傳完就進到 end 事件中會用到 qsparse 來解析 formDataformData的內容是字串內容是

username=wordsmithampemail=wordsmith40somewhere

而 qsparse可以幫我們把這個 querystring轉成物件的格式也就是

username=wordsmithampemail=wordsmith40somewhere

一旦轉成物件並指定給 user之後其他的事情就和 GET方法時操作的一樣寫 response的表頭將內容回傳並將 userusername和 useremail代入到內容中

修改完成後接著執行 nodejs程式啟動 web server開啟瀏覽器進入表單測試看看POST的方式能否順利運作

完整程式碼如下

var http = require(rsquohttprsquo)

url = require(rsquourlrsquo)

fs = require(rdquofsrdquo)

qs = require(rsquoquerystringrsquo)

server

server = httpcreateServer(function (reqres)

var urlData

67 Express POST應用範例 61

Nodejs中文電子書

encode = rdquoutf8rdquo

filePath = rdquoviewexpress_post_example_formhtmlrdquo

formData

action

urlData = urlparse(requrltrue)

action = urlDatapathname

reswriteHead(200 rdquoContent-Typerdquordquotexthtml charset=utf-8rdquo)

if (action === rdquoSignuprdquo)

formData = rsquorsquo

reqon(rdquodatardquo function (data)

formData += data

)

reqon(rdquoendrdquo function ()

user = qsparse(formData)

resend(rdquolth1gtrdquo + userusername + rdquo 歡迎您的加入 lth1gtltpgt 我們已經將會員啟用信寄至rdquo + useremail + rdquoltpgtrdquo)

)

else

fsreadFile(filePath encode function(err file)

reswrite(file)

resend()

)

)

serverlisten(3000)

consolelog(rsquoServer 跑起來了現在時間是rsquo + new Date())

62 6 Express介紹

Nodejs中文電子書

68 Express AJAX應用範例

在 Nodejs要使用 Ajax傳送資料並且與之互動在接受資料的部份沒有太大的差別client端不是用 GET就是用 POST來傳資料重點在處理完後用 JSON格式回傳當然 Ajax不見得只傳 JSON格式有時是回傳一段 HTML碼不過後者對伺服器來說基本上就和前兩篇沒有差別了所以我們還是以回傳 JSON做為這一回的主題

這一回其實大多數的工作都會落在前端 Ajax上面前端要負責發送與接收資料並在接收資料後撤掉原先發送資料的表單並將取得的資料

改成 HTML格式之後放上頁面

首先先準備 HTML靜態頁面資料

ltDOCTYPE htmlgt

lthtmlgt

ltheadgt

lttitlegtNodejs 菜鳥筆記 (3)lttitlegt

ltlink rel=rdquostylesheetrdquo href=rdquocssstylecssrdquo type=rdquotextcssrdquo media=rdquoallrdquo gt

ltscript src=rdquohttpsajaxgoogleapiscomajaxlibsjquery171jqueryminjsrdquogtltscriptgt

ltheadgt

ltbodygt

lth1gtNodejs 菜鳥筆記-註冊 (用 Ajax)lth1gt

ltform id=rdquosignuprdquo action=rdquoSignuprdquo method=rdquoPOSTrdquogt

ltlabelgt 使用者名稱ltlabelgtltinput type=rdquotextrdquo id=rdquousernamerdquo name=rdquousernamerdquo gtltbrgt

ltlabelgt 電子郵件ltlabelgtltinput type=rdquotextrdquo id=rdquoemailrdquo name=rdquoemailrdquo gtltbrgt

ltinput type=rdquosubmitrdquo value=rdquo 註冊我的帳號rdquo gtltbrgt

ltformgt

ltdiv id=rdquoresultrdquogt

ltdivgt

ltscript type=rdquotextjavascriptrdquogt

$(function()$(rsquosignup input[type=rdquosubmitrdquo]rsquo)click(function(e)

var user =

userusername = $(rdquousernamerdquo)val()useremail = $(rdquoemailrdquo)val()

$post(rdquoSignuprdquouserfunction(data)greet(data)

)

68 Express AJAX應用範例 63

Nodejs中文電子書

epreventDefault()

)

var greet = function(msg)

var resultNode = $(rsquoresultrsquo)greeting = $(rsquolth2gtHirsquo+msgusername+rsquo 你的會員 id 是rsquo+ msgid +rsquo 我們會將會員啟動信寄至rsquo+msgemail+rsquolth2gtrsquo)

$(rsquosignuprsquo)hide()resultNodehtml(rdquordquo)

resultNodeappend(greeting)

)

ltscriptgt

ltbodygt

lthtmlgt

HTML頁面上準備了一個表單用來傳送註冊資料接著直接引用了Google CDN來載入 jQuery用來幫我們處理 Ajax的工作這次要傳送和接收的工作很大的變動都在 HTML頁面上的 JavaScript當中我們要做的事有 (相關 jQuery處理這邊不多做贅述指提起主要功能解說)

bull 用 jQUery取得 submit按鈕綁定它的 click動作

bull 取得表單 username和 email的值存放在 user這個物件中

bull 用 jQuery的 $post方法將 user的資料傳到 Server

bull 一旦成功取得資料後透過 greet這個 function組成回報給 user的訊息

bull 清空原本給使用者填資料的表單

bull 將 Server回傳的 usernameemail和 id這 3個資料組成回應的訊息

bull 將訊息放到原本表單的位置

經過以上的處理後一個 Ajax的表單的基本功能已經完成

接著進行 nodejs主要程式的編輯部分程式碼如下

var fs = require(rdquofsrdquo)

64 6 Express介紹

Nodejs中文電子書

qs = require(rsquoquerystringrsquo)

if (action === rdquoSignuprdquo)

formData = rsquorsquo

reqon(rdquodatardquo function (data)

formData += data

)

reqon(rdquoendrdquo function ()

var msg

user = qsparse(formData)

userid = rdquo123456rdquo

msg = JSONstringify(user)

reswriteHead(200 rdquoContent-Typerdquordquoapplicationjson charset=utf-8rdquordquoContent-Lengthrdquomsglength)

resend(msg)

)

這裡的程式和前面 POST範例基本上大同小異差別在

bull 幫 user的資料加上 id隨意存放一些文字進去讓 Server回傳的資料多於 Client端傳上來的不然會覺得 Server都沒做事

bull 增加了 msg 這個變數存放將 user 物件 JSON 文字化的結果JSONstringify這個轉換函式是 V8引擎所提供的如果你好奇的話

bull 大重點來了我們要告訴 Client端這次回傳的資料格式是 JSON所在 Content-type和 Content-Length要提供給 Client

Server很輕鬆就完成任務了最後進行程式測試啟動 nodejs主程式之後開啟瀏覽器就會看到表單填寫完畢按下送出就可以看到結果了

最後 nodejs本篇範例程式碼如下

var http = require(rsquohttprsquo)

url = require(rsquourlrsquo)

fs = require(rdquofsrdquo)

qs = require(rsquoquerystringrsquo)

server

68 Express AJAX應用範例 65

Nodejs中文電子書

server = httpcreateServer(function (reqres)

var urlData

encode = rdquoutf8rdquo

filePath = rdquoviewexpress_ajax_example_formhtmlrdquo

formData

action

urlData = urlparse(requrltrue)

action = urlDatapathname

if (action === rdquoSignuprdquo)

formData = rsquorsquo

reqon(rdquodatardquo function (data)

formData += data

)

reqon(rdquoendrdquo function ()

var msg

user = qsparse(formData)

userid = rdquo123456rdquo

msg = JSONstringify(user)

reswriteHead(200 rdquoContent-TyperdquordquoapplicationjsonrdquordquoContent-Lengthrdquomsglength)

resend(msg)

)

else

fsreadFile(filePath encode function(err file)

reswriteHead(200 rdquoContent-Typerdquordquotexthtml charset=utf-8rdquo)

reswrite(file)

resend()

)

)

serverlisten(3000)

66 6 Express介紹

Nodejs中文電子書

consolelog(rsquoServer 跑起來了現在時間是rsquo + new Date())

69 原始資料提供

bull [NodeJS初學者筆記 (1)-用 GET傳送資料] (httpithelpithomecomtwquestion10087402)

bull [NodeJS初學者筆記 (2)-用 POST傳送資料] (httpithelpithomecomtwquestion10087489)

bull [NodeJS 初學者筆記 (3)-用 Ajax 傳送資料] (httpithelpithomecomtwquestion10087627)

69 原始資料提供 67

Nodejs中文電子書

68 6 Express介紹

7

CoffeeScript

bull Spine

bull Hem

bull Spineapp

Letrsquos Have a Cup of CoffeeScript

bull es5-shim ECMAScript 5 compatibility shims for legacy JavaScript engines

ESnext

node-iform - A middleware for node-validator httpsgithubcomguileennode-iform

69

Nodejs中文電子書

70 7 CoffeeScript

8

製作一個 Hubot的 Plurk Adapter

81 應用事項提醒

此應用範例以CoffeeScript編寫主要程式架構採用Github釋放的Hubot專案以下有幾個主要注意事項請讀者注意

bull Hubot 一開始的架構除了內建的兩個 Adapter 之外其餘都要以Nodejs的Module方式才能運作

bull Hubot的 binhubot裡面寫著 npm install所以不管你怎麼改原始碼也不能改變第一點的狀況

其實上述都在討論同一件事情NodeJS的模組而且模組不能設定相對路徑之類的來安裝一定要包裝成 npm相符和格式才有辦法正確執行

82 建立 Adapter

首先需要準備兩個檔案

bull packagejson

bull plurkcoffee

71

Nodejs中文電子書

有這兩個就足以變成 NodeJS的模組了在開始 Coding之前先來設定一下 packagejson相依性

rdquonamerdquo rdquohubot-plurkrdquo

rdquoversionrdquo rdquo011rdquo

rdquomainrdquo rdquoplurkrdquo

rdquodependenciesrdquo

rdquohubotrdquo rdquogt=205rdquo

rdquooauthrdquo rdquordquo

rdquocronrdquordquordquo

這邊會使用到NodeJS的 cron模組(Module)而登入 Plurk需要OAuth標準授權才行所以也需要加載 OAuth模組

注意Hubot在讀取非內建模組時會自動在前面加上 hubot-的前置

83 建立 Robot跟 API

本篇應用基本上程式碼大部分參考 Hubot - Twitter Adapter來製作主要差異只有在使用 OAuth這個模組Twitter有 Streaming可用而 Plurk則得用 Comet方式來達到即時讀取

plurkcoffee程式碼

Robot = require(rdquohubotrdquo)robot()

Adapter = require(rdquohubtordquo)adapter()

EventEmitter = require(rdquoeventsrdquo)EventEmitter

oauth = require(rdquooauthrdquo)

cronJob = require(rdquocronrdquo)CronJob

class Plurk exntends Adapter

class PlurkStreaming exnteds EventEmitter

72 8 製作一個 Hubot的 Plurk Adapter

Nodejs中文電子書

先弄個基本架構主要 Class皆參考 Twitter Adapter命名方式接著再將 Plurk這個 Class增加幾個Method基本上只要有 run send reply就夠了而 run用來做初始化的部分

class Plurk entends Adapter

send (plurk id strings⋯) -gt

reply (plurk id strings⋯) -gt

run -gt

看起來有點東西接著來處理主要的 Plurk API結合部分

class PlurkStreaming extends EventEmitter

consuctor (options) -gt

plurk (callback) -gt

觀察河道

getChannel -gt

取得 Comet 網址

reply (plurk id message) -gt

回噗

acceptFriends -gt

接受好友

get (path callback) -gt

GET 請求

post (path body callback)-gt

POST 請求(其實是裝飾)

request (method path body callback)-gt

主要的 OAuth 請求

comet (server callback)-gt

噗浪的 Comet 傳回是 JavaScript Callback 要另外處理後才會變成 JSON

然後把注意力集中到 constructor上先把建構子弄好

constructor (options) -gt

super()

if optionskey and optionssecret and optionstoken and optionstoken secret

key = optionskey

secret = optionssecret

token = optionstoken

token secret = optionstoken secret

83 建立 Robot跟 API 73

Nodejs中文電子書

建立 OAuth 連接

consumer = new oauthOAuth(

rdquohttpwwwplurkcomOAuthrequest tokenrdquo

rdquohttpwwwplurkcomOAuthaccess tokenrdquo

key

secret

rdquo10rdquo

rdquohttpwwwplurkcomOAuthauthorizerdquo

rdquoHMAC-SHA1rdquo

)

domain = rdquowwwplurkcomrdquo

初始化取得 Comet 網址

do getChannel

else

throw new Error(rdquo 參數不足需要 Key Secret Token Token Secretrdquo)

接著來處理 request這個 method

request (method path body callback) -gt

記錄一下這次的 Request

consolelog(rdquohttpdomainpathrdquo)

Callback 這邊先不丟進去要用另一種方式處理

request = consumerget(rdquohttpdomainpathrdquo token token secret null)

requeston rdquoresponserdquo (res) -gt

reson rdquodatardquo (chunk) -gt

parseResponse(chunk+rsquorsquo callback)

reson rdquoendrdquo (data) -gt

consolelog rdquoEnd Request pathrdquo

reson rdquoerrorrdquo (data) -gt

consolelog rdquoError rdquo + data

requestend()

處理資料

parseResponse = (data callback) -gt

if datalength gt 0

用 TryCatch 避免處理 JSON 出錯導致整個中斷

try

callback null JSONparse(data)

catch err

74 8 製作一個 Hubot的 Plurk Adapter

Nodejs中文電子書

consolelog(rdquoError Parse JSONrdquo + data err)

繼續執行

callback null data ||

大致上就是這樣上面程式的架構已經將整個 Hubot Plurk Adapter完成因為在測試時竟然因為噗浪 Lag而沒讀到完整的 Comet資料然後造成程式異常為了避免這個問題發生因此需要加上為了完美呈現需要再

加上 Comet的處理所以要使用到 EventEmitter的功能

comet (server callback) -gt

在 Callback 裡面會找不到自身所以設定區域變數

self =

記錄一下這次的 Request

consolelog(rdquo[Comet] serverrdquo)

Callback 這邊先不丟進去要用另一種方式處理

request = consumerget(rdquohttpdomainpathrdquo token token secret null)

requeston rdquoresponserdquo (res) -gt

reson rdquodatardquo (chunk) -gt

parseResponse(chunk+rsquorsquo callback)

reson rdquoendrdquo (data) -gt

consolelog rdquoEnd Request pathrdquo

請求結束發出事件通知可以進行下一次請求

selfemit rdquonextPlurkrdquo

reson rdquoerrorrdquo (data) -gt

consolelog rdquoError rdquo + data

requestend()

處理資料

parseResponse = (data callback) -gt

if datalength gt 0

用 trycatch 避免失敗中斷

try

去掉 JavaScript 的 Callback

data = datamatch(CometChannelscriptCallback((+))s)

jsonData = rdquordquo

if data

83 建立 Robot跟 API 75

Nodejs中文電子書

jsonData = JSONparse(data[1])

else

如果沒有任何 Match 嘗試直接 parse

jsonData = JSONparse(data)

catch err

consolelog(rdquo[Comet] Errorrdquo data err)

用 TryCatch 避免處理 JSON 出錯導致整個中斷

try

只傳入 json 的 data 部分

callback null jsonDatadata

catch err

consolelog(rdquo[Comet]Error Parse JSONrdquo + data err)

繼續執行

callback null data ||

後面的 get跟 post就簡單多了

get (path callback) -gt

request(rdquoGETrdquo path null callback)

post (path body callback) -gt

request(rdquoPOSTrdquo path body callback)

接著處理取的 Comet網址的 getChannel

getChannel -gt

self =

get rdquoAPPRealtimegetUserChannelrdquo (error data) -gt

if error

檢查是否有 comet server

if datacomet server

selfchannel = datacomet server

如果沒有 Channel Ready 就嘗試連接會失敗

selfemit(rsquochannel readyrsquo)

那麼先來處理 Plurk Adaper好處理的部份

send (plruk id strings⋯)-gt

跟 Reply 一樣直接交給 reply 做

reply plurk id strings⋯

76 8 製作一個 Hubot的 Plurk Adapter

Nodejs中文電子書

reply (plurk id strings⋯) -gt

stringsforEach (message) =gt

botreply(plruk id message)

接著把 run處理好就可以上線運作摟

run -gt

self =

options =

key processenvHUBOT PLURK KEY

secret processenvHUBOT PLURK SECRET

token processenvHUBOT PLURK TOKEN

token secret processenvHUBOT PLURK TOKEN SECRET

創建剛剛的 API

bot = new PlurkStreaming(options)

依照 Twitter 的 new RobotTextMessage 會沒有反應所以參考 hubot-minecraft 的方式

r = robotconstructor

處理噗浪河道訊息

doPlurk = (data)-gt

檢查是否為回噗

if dataresponse

datacontent raw = dataresponsecontent raw

datauser id = dataresponseuser id

確定有噗浪 ID 跟訊息

if dataplurk id and datacontent raw

selfreceive new rTextMessage(dataplurk id datacontent raw)

取得 Comet Server 完成開始第一次 Comet 連接

boton rdquochannel readyrdquo () -gt

botplurk selfdoPlurk

上一次 Comet 完成繼續 Polling

boton rdquonextPlurkrdquo ()-gt

botplurk selfdoPlurk

定時接受好友邀請

do botacceptFriends

bot = bot

83 建立 Robot跟 API 77

Nodejs中文電子書

終於完成 AdapterHubot專案裡面的 scripts資料夾內是互動部分不需要像 Adapter如此大費周章處理只新增檔案並且設計好對白之後就會回噗了我開發用的機器人在此大家可以去跟他玩玩[httpplurkcomelct9620 bot](httpplurkcomelct9620 bot)

84 原始資料提供

bull [製 作 一 個 Hubot 的 噗 浪 Adapter](http revo-skillfrosttw blog20120318create-a-hubot-plurk-adapter)

78 8 製作一個 Hubot的 Plurk Adapter

9

Nodejs好用工具介紹

91 logio

Real-time log monitoring in your browser

httplogioorg

nodejs (gt=0412 lt=0611)

npm config set unsafe-perm true

npm install -g --prefix=usrlocal logio

logio server start

httplocalhost8998

79

Nodejs中文電子書

80 9 Nodejs好用工具介紹

10

精選文章

101 Express

bull 使用 NodeJS + Express從 GETPOST Request取值 [邀稿中]

bull link2 [邀稿中]

bull link3 [邀稿中]

81

Nodejs中文電子書

82 10 精選文章

11

參考資源

111 Nodejs書籍

bull Programming Nodejs April 2012 (est) Travis Swicegood Pragmatic Bookshelf

bull e Little Book on CoffeeScript January 2012 Alex MacCaw OrsquoReilly Media

bull Node for Front-End Developers January 2012 Garann Means OrsquoReilly Media

bull Building Hypermedia APIs with HTML5 and Node November 2011 MikeAmundsen OrsquoReilly Media

bull Node Web Development August 2011 David Herron PacktPub

bull CoffeeScript Accelerated JavaScript Development July 2011 Trevor BurnhamPragmatic Bookshelf

bull Getting Started with GEO CouchDB and Nodejs July 2011 Mick omp-son OrsquoReilly Media

bull What Is Node July 2011 Brett McLaughlin OrsquoReilly Media

bull Node Up and Running May 2011 Tom Hughes-Croucher Mike WilsonOrsquoReilly Media

83

Nodejs中文電子書

bull Hands-on Nodejs e Nodejs introduction and API reference May 2011 Pe-dro Teixeira LeanPub

bull e Node Beginner Book Manuel Kiessling

bull Mastering Nodejs visionmedia

112 Nodejs影音教學

bull e Node Sessions e Best of OSCON 2011 August 2011 OrsquoReilly Media(Video)

bull Tom Hughes-Croucher on Node March 2011 OrsquoReilly Media (Video)

113 Nodejs教學網站

bull Node Tuts

114 Nodejs課程

bull Introduction To Nodejs Van Nguyen CodeLesson

84 11 參考資源

Page 46: Nodejs Wiki

Nodejs中文電子書

serverlisten(port ip)

consolelog(rdquoServer running at httprdquo + ip + rdquordquo + port)

程式運作之後由瀏覽器輸入要求網址之後nodejs伺服器端回應資料為

Server running at http1270011337

send rsquo1rsquo test rsquo1rsquo

46 本章結語

前面所解說的部份一大部分主要是處理 http伺服器基本問題雖然在某些部分有牽扯到 http 伺服器基本運作原理主要還是希望可以藉由這些基本範例練習 nodejs練習回應函式與語法串接的特點習慣編寫javascript風格程式當然直接這樣開發 nodejs是非常辛苦的接下來在模組實戰開發的部份將會介紹特定的模組一步一步帶領各位從無到有進行

nodejs應用程式開發

40 4 Nodejs基礎

5

NPM套件管理工具

npm全名為 Node Package Manager是 Nodejs的套件(package)管理工具類似 Perl的 ppm或 PHP的 PEAR等安裝 npm後使用npm install

module name指令即可安裝新套件維護管理套件的工作會更加輕鬆

npm可以讓Nodejs的開發者直接利用擴充線上的套件庫(packagesregistry)加速軟體專案的開發npm提供很友善的搜尋功能可以快速找到安裝需要的套件當這些套件發行新版本時npm也可以協助開發者自動更新這些套件

npm不僅可用於安裝新的套件它也支援搜尋列出已安裝模組及更新的功能

51 安裝 NPM

Nodejs在 063版本開始內建 npm讀者安裝的版本若是此版本或更新的版本就可以略過以下安裝說明

若要檢查 npm是否正確安裝可以使用以下的指令

npm -v

41

Nodejs中文電子書

執行結果說明

若 npm正確安裝執行 npm -v將會看到類似 110-2的版本訊息

若讀者安裝的 Nodejs版本比較舊或是有興趣嘗試自己動手安裝 npm工具則可以參考以下的說明

安裝於Windows系統

Nodejs for Windows 於 062 版開始內建 npm使用 nodejsorg 官方提供的安裝程式不需要進一步的設定就可以立即使用 npm指令對於Windows的開發者來說大幅降低環境設定的問題與門檻

除了使用 Nodejs內建的 npm讀者也可以從 npm官方提供的以下網址

httpnpmjsorgdist

這是由 npm提供的 Fancy Windows Install版本請下載壓縮檔(例如npm-110-3zip)並將壓縮檔內容解壓縮至 Nodejs的安裝路徑(例如CProgram Filesnodejs)

解壓縮後在 Nodejs的安裝路徑下應該有以下的檔案及資料夾

bull npmcmd(檔案)

bull node modules(資料夾)

安裝於 Linux系統

Ubuntu Linux的使用者可以加入 NPM Unoffcial PPA這個 repository即可使用 apt-get完成 npm安裝

42 5 NPM套件管理工具

Nodejs中文電子書

Ubuntu Linux使用 apt-get安裝 npm

sudo apt-get install python-software-properties

sudo add-apt-repository ppagias-kay-leenpm

sudo apt-get update

sudo apt-get install npm

npm官方提供的安裝程式 installsh可以適用於大多數的 Linux系統使用這個安裝程式請先確認

1 系統已安裝 curl工具(請使用 curl --version查看版本訊息)

2 已安裝 Nodejs並且 PATH正確設置

3 Nodejs的版本必須大於 04x

以下為 npm提供的安裝指令

curl httpnpmjsorginstallsh | sh

安裝成功會看到如下訊息

installsh安裝成功的訊息

npm10105 homeUSERNAMElocalnodelibnode_modulesnpm

It worked

安裝於Mac OS X

建議採用與 Nodejs相同的方式進行 npm的安裝例如使用MacPorts安裝 Nodejs就同樣使用MacPorts安裝 npm這樣對日後的維護才會更方便容易

使用 MacPorts安裝 npm是本書比較建議的方式它可以讓 npm的安裝移除及更新工作自動化將會幫助開發者節省寶貴時間

51 安裝 NPM 43

Nodejs中文電子書

安裝MacPorts的提示

在MacPorts網站可以取得 OS X系統版本對應的安裝程式(例如 106或 107)httpwwwmacportsorg安裝過程會詢問系統管理者密碼使用預設的選項完成安裝即可安

裝MacPorts之後在終端機執行 port -v將會看到MacPorts的版本訊息

安裝 npm之前先更新MacPorts的套件清單以確保安裝的 npm是最新版本

sudo port -d selfupdate

接著安裝 npm

sudo port install npm

若讀者的 Nodejs並非使用MacPorts安裝則不建議使用MacPorts安裝npm因為 MacPorts會自動檢查並安裝相依套件而 npm相依 nodejs所以MacPorts也會一併將 nodejs套件安裝造成先前讀者使用其它方式安裝的 nodejs被覆蓋

讀者可以先使用 MacPorts安裝 curl(sudo port install curl)

再參考 Linux的 installsh安裝方式即可使用 npm官方提供的安裝程式

NPM安裝後測試

npm是指令列工具(command-line tool)使用時請先打開系統的文字終端機工具

測試 npm安裝與設定是否正確請輸入指令如下

npm -v

或是

npm --version

如果 npm已經正確安裝設定就會顯示版本訊息

44 5 NPM套件管理工具

Nodejs中文電子書

執行結果(範例)

110-2

52 使用 NPM安裝套件

npm目前擁有超過 6000種套件(packages)可以在 npm registry使用關鍵字搜尋套件

httpsearchnpmjsorg

舉例來說在關鍵字欄位輸入「coffee-script」下方的清單就會自動列出包含 coffee-script關鍵字的套件

接著我們回到終端機模式的操作npm的指令工具本身就可以完成套

件搜尋的任務

例如以下的指令同樣可以找出 coffee-script相關套件

npm search coffee-script

以下是搜尋結果的參考畫面

52 使用 NPM安裝套件 45

Nodejs中文電子書

找到需要的套件後(例如 express)即可使用以下指令安裝

npm install coffee-script

值得注意的一點是使用 npm install會將指定的套件安裝在工

作目錄(Working Directory)的 node modules資料夾下

以Windows為例如果執行 npm install的目錄位於

Cproject1

那麼 npm將會自動建立一個 node modules的子目錄(如果不存在)

Cproject1node modules

並且將下載的套件放置於這個子目錄例如

Cproject1node modulescoffee-script

這個設計讓專案可以個別管理相依的套件並且可以在專案佈署或發

行時將這些套件(位於 node modules)一併打包方便其它專案的使用者不必再重新下載套件

這個 npm install的預設安裝模式為 local(本地)只會變更當前專案的資料夾不會影響系統

另一種安裝模式稱為 global(全域)這種模式會將套件安裝到系統資

料夾也就是 npm安裝路徑的 node modules資料夾例如

CProgram Filesnodejsnode modules

46 5 NPM套件管理工具

Nodejs中文電子書

是否要使用全域安裝可以依照套件是否提供新指令來判斷舉例來說express 套件提供 express 這個指令而 coffee-script 則提供 coffee

指令

在 local 安 裝 模 式 中這 些 指 令 的 程 式 檔 案會 被 安 裝 到node modules的 bin這個隱藏資料夾下除非將bin的路徑加入 PATH環境變數否則要執行這些指令將會相當不便

為了方便指令的執行我們可以在 npm install 加上 -g 或 --

global參數啟用 global安裝模式例如

npm install -g coffee-script

npm install -g express

使用 global安裝模式需要注意執行權限的問題若權限不足可能會出現類似以下的錯誤訊息

npm ERR Error EACCES permission denied rsquorsquo

npm ERR

npm ERR Please try running this command again as rootAdministrator

要獲得足夠得執行權限請參考以下說明

bull Windows 7 或 2008 以上在「命令提示字元」的捷徑按右鍵選擇「以系統管理員身分執行」執行 npm指令時就會具有 Administrator身分

bull Mac OS X或 Linux系統可以使用 sudo指令例如

sudo npm install -g express

bull Linux系統可以使用 root權限登入或是以「sudo su -」切換成 root身分(使用 root權限操作系統相當危險因此並不建議使用這種方式)

若加上 -g參數使用 npm install -g coffee-script完成安裝

後就可以在終端機執行 coffee指令例如

coffee -v

52 使用 NPM安裝套件 47

Nodejs中文電子書

執行結果(範例)

CoffeeScript version 120

53 套件的更新及維護

除了前一節說明的 search 及 install 用法npm 還提供其他許多指令(commands)

使用 npm help可以查詢可用的指令

npm help

執行結果(部分)

where ltcommandgt is one of

adduser apihelp author bin bugs c cache completion

config deprecate docs edit explore faq find get

help help-search home i info init install la link

list ll ln login ls outdated owner pack prefix

prune publish r rb rebuild remove restart rm root

run-script s se search set show star start stop

submodule tag test un uninstall unlink unpublish

unstar up update version view whoami

使用 npm help command可以查詢指令的詳細用法例如

npm help list

接下來本節要介紹開發過程常用的 npm指令

使用 list可以列出已安裝套件

npm list

48 5 NPM套件管理工具

Nodejs中文電子書

執行結果(範例)

-- coffee-script120

- express256

- connect185

| -- formidable108

-- mime124

-- mkdirp007

-- qs041

檢視某個套件的詳細資訊例如

npm show express

升級所有套件(如果該套件已發佈更新版本)

npm update

升級指定的套件

npm update express

移除指定的套件

npm uninstall express

54 使用 packagejson

對於正式的 Nodejs專案可以建立一個命名為 packagejson的設

定檔(純文字格式)檔案內容參考範例如下

54 使用 packagejson 49

Nodejs中文電子書

packagejson(範例)

rdquonamerdquo rdquoapplication-namerdquo

rdquoversionrdquo rdquo001rdquo

rdquoprivaterdquo true

rdquodependenciesrdquo

rdquoexpressrdquo rdquo255rdquo

rdquocoffee-scriptrdquo rdquolatestrdquo

rdquomongooserdquo rdquogt= 253rdquo

其中 name與 version依照專案的需求設置

需要注意的是 dependencies的設定它用於指定專案相依的套件名

稱及版本

bull rdquoexpressrdquo rdquo255rdquo

代表此專案相依版本 255的 express套件

bull rdquocoffee-scriptrdquo rdquolatestrdquo

使用最新版的 coffee-script套件(每次更新都會檢查新版)

bull rdquomongooserdquo rdquogt= 253rdquo

使用版本大於 253的 mongoose套件

假設某個套件的新版可能造成專案無法正常運作就必須指定套件的

版本避免專案的程式碼來不及更新以相容新版套件通常在開發初期的

專案需要盡可能維持新套件的相容性(以取得套件的更新或修正)可以

用「gt=」設定最低相容的版本或是使用「latest」設定永遠保持最新

套件

50 5 NPM套件管理工具

6

Express介紹

在前面的 nodejs基礎當中介紹許多許多開設 http的使用方法及介紹以及許多基本的 nodejs基本應用

接下來要介紹一個套件稱為 express [Express](httpexpressjscom)這個套件主要幫忙解決許多 nodejs http server所需要的基本服務讓開發 httpservice變得更為容易不需要像之前需要透過層層模組(module)才有辦法開始編寫自己的程式

這個套件是由 TJ Holowaychuk 製作而成的套件裡面包含基本的路由處理 (route)http資料處理(GETPOSTPUT)另外還與樣板套件(jshtml template engine)搭配同時也可以處理許多複雜化的問題

61 Express安裝

安裝方式十分簡單只要透過之前介紹的 NPM就可以使用簡單的指令安裝指令如下

這邊建議需要將此套件安裝成為全域模組方便日後使用

51

Nodejs中文電子書

62 Express基本操作

express的使用也十分簡單先來建立一個基本的 hello world

var app = require(rsquoexpressrsquo)createServer()

port = 1337

applisten(port)

appget(rsquorsquo function(req res)

ressend(rsquohello worldrsquo)

)

consolelog(rsquostart express servernrsquo)

可以從上面的程式碼發現基本操作與 nodejs http的建立方式沒有太大差異主要差在當我們設定路由時可以直接透過 appget方式設定回應與接受方式

63 Express路由處理

Express對於 http服務上有許多包裝讓開發者使用及設定上更為方便例如有幾個路由設定那我們就統一藉由 appget來處理

Create http server

appget(rsquorsquo function(req res)

ressend(rsquohello worldrsquo)

)

appget(rsquotestrsquo function(req res)

ressend(rsquotest renderrsquo)

)

appget(rsquouserrsquo function(req res)

ressend(rsquouser pagersquo)

)

52 6 Express介紹

Nodejs中文電子書

如上面的程式碼所表示appget可以帶入兩個參數第一個是路徑名稱設定第二個為回應函式 (call back function)回應函式裡面就如同之前的 createServer方法裡面包含 requestresponse兩個物件可供使用使用者就可以透過瀏覽器輸入不同的 url切換到不同的頁面顯示不同的結果

路由設定上也有基本的配對方式讓使用者從瀏覽器輸入的網址可以

是一個變數只要符合型態就可以有對應的頁面產出例如

Create http server

appget(rsquouseridrsquo function(req res)

ressend(rsquouser rsquo + reqparamsid)

)

appget(rsquonumberrsquo function(req res)

ressend(rsquonumber rsquo + reqparamsnumber)

)

裡 面 使 用 到number 從 網 址 輸 入 之 後 就 可 以 直 接 使 用reqparamsnumber 取得所輸入的資料變成 url 參數使用當然前面也是可以加上路徑的設定userid在瀏覽器上路徑必須符合userxxx透

過 reqparamsid就可以取到 xxx這個字串值

另外express參數處理也提供了路由參數配對處理也可以透過正規表示法作為參數設定

var app = require(rsquoexpressrsquo)createServer()

port = 1337

applisten(port)

appget(^ip((d23)((d23))((d23))((d23))) function(req res)

ressend(reqparams)

)

上面程式碼可以發現後面路由設定的型態是正規表示法裡面設定

格式為ip之後必須要加上 ip型態才會符合資料格式同時取得 ip資料已經由正規表示法將資料做分群因此可以取得 ip的四個數字

此程式執行之後可以透過瀏覽器測試輸入網址為 localhost3000ip25525510010可以從頁面獲得資料

63 Express路由處理 53

Nodejs中文電子書

此章節全部範例程式碼如下

overview

author Caesar Chi

blog clonnblogspotcom

version 20120226

create server

var app = require(rsquoexpressrsquo)createServer()

port = 1337

applisten(port)

normal style

appget(rsquorsquo function(req res)

ressend(rsquohello worldrsquo)

)

appget(rsquotestrsquo function(req res)

ressend(rsquotest renderrsquo)

)

parameter style

appget(rsquouseridrsquo function(req res)

ressend(rsquouser rsquo + reqparamsid)

)

appget(rsquonumberrsquo function(req res)

ressend(rsquonumber rsquo + reqparamsnumber)

)

REGX style

appget(^ip((d23)((d23))((d23))) function(req res)

ressend(reqparams)

)

appget(rsquorsquo function(req res)

ressend(rsquoPage not foundrsquo 404)

54 6 Express介紹

Nodejs中文電子書

)

consolelog(rsquostart express servernrsquo)

64 Express middleware

Express 裡面有一個十分好用的應用概念稱為 middleware可以透過middleware做出複雜的效果同時上面也有介紹 next方法參數傳遞就是靠 middleware的概念來傳遞參數讓開發者可以明確的控制程式邏輯

create http server

appuse(expressbodyParser())

appuse(expressmethodOverride())

appuse(expresssession())

上面都是一種 middleware的使用方式透過 appuse方式裡面載入函式執行方法回應函式會包含三個基本參數responserequestnext其中next表示下一個 middleware執行函式同時會自動將預設三個參數繼續帶往下個函式執行底下有個實驗

上面的片段程式執行後開啟瀏覽器連結上 localhost1337會發現伺服器回應結果順序如下

first middle ware

second middle ware

execute middle ware

end middleware function

從上面的結果可以得知剛才設定的 middleware都生效了在 appuse設定的 middleware是所有 url皆會執行方法如果有指定特定方法就可以使用 appget的 middleware設定在 appget函式的第二個參數就可以帶入函式或者是匿名函式只要函式裡面最後會接受 request response next這三個參數同時也有正確指定 next函式的執行時機最後都會執行到最後一個方法當然開發者也可以評估程式邏輯要執行到哪一個階段讓邏輯

可以更為分明

64 Express middleware 55

Nodejs中文電子書

65 Express路由應用

在實際開發上可能會遇到需要使用參數等方式混和變數一起使用

express裡面提供了一個很棒的處理方法 appall這個方式可以先採用基本路由配對再將設定為每個不同的處理方式開發者可以透過這個方式簡

化自己的程式邏輯

overview

author

version 20120226

create server

var app = require(rsquoexpressrsquo)createServer()

port = 1337

users = [

name rsquoClonnrsquo

name rsquoChirsquo

]

applisten(port)

appall(rsquouseridoprsquo function(req res next)

requser = users[reqparamsid]

if (requser)

next()

else

next(new Error(rsquocannot find user rsquo + reqparamsid))

)

appget(rsquouseridrsquo function(req res)

ressend(rsquoviewing rsquo + requsername)

)

appget(rsquouserideditrsquo function(req res)

ressend(rsquoediting rsquo + requsername)

)

56 6 Express介紹

Nodejs中文電子書

appget(rsquouseriddeletersquo function(req res)

ressend(rsquodeleting rsquo + requsername)

)

appget(rsquorsquo function(req res)

ressend(rsquoPage not foundrsquo 404)

)

consolelog(rsquostart express servernrsquo)

內部宣告一組預設的使用者分別給予名稱設定藉由 appall這個方法可以先將路由雛形建立再接下來設定 appget的路徑格式只要符合格式就會分配進入對應的方法中像上面的程式當中如果使用者輸入路徑為user0除了執行 appall程式之後執行 next方法就會對應到路徑設定為userid的這個方法當中如果使用者輸入路徑為user0edit就會執行到useridedit的對應方法

66 Express GET應用範例

我們準備一個使用 GET方法傳送資料的表單

ltDOCTYPE htmlgt

lthtmlgt

ltheadgt

ltmeta http-equiv=rdquoContent-Typerdquo content=rdquotexthtml charset=utf8rdquogt

lttitlegtNodejs 菜鳥筆記 (1)lttitlegt

ltlink rel=rdquostylesheetrdquo href=rdquocssstylecssrdquo type=rdquotextcssrdquo media=rdquoallrdquo gt

ltheadgt

ltbodygt

lth1gtNodejs 菜鳥筆記-註冊lth1gt

ltform id=rdquosignuprdquo method=rdquoGETrdquo action=rdquohttplocalhost3000Signuprdquogt

ltlabelgt 使用者名稱ltlabelgtltinput type=rdquotextrdquo id=rdquousernamerdquo name=rdquousernamerdquo gtltbrgt

ltlabelgt 電子郵件ltlabelgtltinput type=rdquotextrdquo id=rdquoemailrdquo name=rdquoemailrdquo gtltbrgt

ltinput type=rdquosubmitrdquo value=rdquo 註冊我的帳號rdquo gtltbrgt

ltformgt

ltbodygt

lthtmlgt

66 Express GET應用範例 57

Nodejs中文電子書

這個表單沒有什麼特別的地方我們只需要看第 9 行form 使用的method是 GET然後 action是rdquohttplocalhost3000Signupldquo等一下我們要來撰寫Signup這個 URL Path的處理程式

處理 Signup行為

我們知道所謂的 GET方法會透過 URL來把表單的值給帶過去以上面的表單來說到時候 URL會以這樣的形式傳遞

httplocalhost3000Signupusername=xxxampemail=xxx

所以要能處理這樣的資料必須有以下功能

bull 解析 URL

bull 辨別動作是 Signup

bull 解析出 username和 email

一旦能取得 username和 email的值程式就能加以應用了

處理 Signup的程式碼雛形

load module

var url = require(rsquourlrsquo)

urlData = urlparse(requrltrue)

action = urlDatapathname

reswriteHead(200 rdquoContent-Typerdquordquotexthtml charset=utf-8rdquo)

if (action === rdquoSignuprdquo)

user = urlDataquery

resend(rdquolth1gtrdquo + userusername + rdquo 歡迎您的加入 lth1gtltpgt 我們已經將會員啟用信寄至rdquo + useremail + rdquoltpgtrdquo)

首先需要加載 url module它是用來協助我們解析 URL的模組接著使用 urlparse方法第一個傳入 url字串變數也就是 requrl另外第二個參數的用意是設為 ture則引進 querystring模組來協助處理預設是 false它影響到的是 urlDataquery設為 true會傳回物件不然就只是一般的字串urlparse會將字串內容整理成一個物件我們把它指定給 urlData

action變數作為記錄 pathname這是我們稍後要來判斷目前網頁的動作是什麼接著先將 html表頭資訊 (Header)準備好再來判斷路徑邏輯如

58 6 Express介紹

Nodejs中文電子書

果是 Signup這個動作就把 urlDataquery裡的資料指定給 user然後輸出userusername和 useremail把使用者從表單註冊的資料顯示於頁面中

最後進行程式測試啟動 nodejs主程式之後開啟瀏覽器就會看到表單填寫完畢按下送出就可以看到結果了

完整 nodejs程式碼如下

var http = require(rsquohttprsquo)

url = require(rsquourlrsquo)

fs = require(rdquofsrdquo)

server

server = httpcreateServer(function (reqres)

var urlData

encode = rdquoutf8rdquo

filePath = rdquoviewexpress_get_example_formhtmlrdquo

action

urlData = urlparse(requrltrue)

action = urlDatapathname

reswriteHead(200 rdquoContent-Typerdquordquotexthtml charset=utf-8rdquo)

if (action === rdquoSignuprdquo)

user = urlDataquery

resend(rdquolth1gtrdquo + userusername + rdquo 歡迎您的加入 lth1gtltpgt 我們已經將會員啟用信寄至rdquo + useremail + rdquoltpgtrdquo)

else

fsreadFile(filePath encode function(err file)

reswrite(file)

resend()

)

)

serverlisten(3000)

consolelog(rsquoServer 跑起來了現在時間是rsquo + new Date())

66 Express GET應用範例 59

Nodejs中文電子書

67 Express POST應用範例

一開始準備基本的 html表單傳送內容以 POST方式form的 action屬性設定為 POST其餘 html內容與前一個範例應用相同

ltDOCTYPE htmlgt

lthtmlgt

ltheadgt

ltmeta http-equiv=rdquoContent-Typerdquo content=rdquotexthtml charset=utf8rdquogt

lttitlegtNodejs 菜鳥筆記 (1)lttitlegt

ltlink rel=rdquostylesheetrdquo href=rdquocssstylecssrdquo type=rdquotextcssrdquo media=rdquoallrdquo gt

ltheadgt

ltbodygt

lth1gtNodejs 菜鳥筆記-註冊lth1gt

ltform id=rdquosignuprdquo method=rdquoPOSTrdquo action=rdquohttplocalhost3000Signuprdquogt

ltlabelgt 使用者名稱ltlabelgtltinput type=rdquotextrdquo id=rdquousernamerdquo name=rdquousernamerdquo gtltbrgt

ltlabelgt 電子郵件ltlabelgtltinput type=rdquotextrdquo id=rdquoemailrdquo name=rdquoemailrdquo gtltbrgt

ltinput type=rdquosubmitrdquo value=rdquo 註冊我的帳號rdquo gtltbrgt

ltformgt

ltbodygt

lthtmlgt

nodejs的程式處理邏輯與前面 GET範例類似部分程式碼如下

qs = require(rsquoquerystringrsquo)

if (action === rdquoSignuprdquo)

formData = rsquorsquo

reqon(rdquodatardquo function (data)

formData += data

)

reqon(rdquoendrdquo function ()

user = qsparse(formData)

resend(rdquolth1gtrdquo + userusername + rdquo 歡迎您的加入 lth1gtltpgt 我們已經將會員啟用信寄至rdquo + useremail + rdquoltpgtrdquo)

)

60 6 Express介紹

Nodejs中文電子書

主要加入了rsquoquerystringrsquo這個moduel方便我們等一下解析由表單 POST回來的資料另外加入一個 formData的變數用來搜集待等一下表單回傳的資料前面的 GET範例我們只從 req拿出 url的資料這次要在利用req身上的事件處理

JavaScript 在訂閱事件時使用 addEventListener而 nodejs 使用的則是on這邊加上了監聽 data的事件會在瀏覽器傳送資料到Web Server時被執行參數是它所接收到的資料型態是字串

接著再增加 end的事件當瀏覽器的請求事件結束時它就會動作

由於瀏覽器使用 POST在上傳資料時會將資料一塊塊地上傳因為我們在監聽 data事件時透過 formData變數將它累加起來lt不過由於我們

上傳的資料很少一次就結束不過如果日後需要傳的是資料比較大的檔

案這個累加動作就很重要

當資料傳完就進到 end 事件中會用到 qsparse 來解析 formDataformData的內容是字串內容是

username=wordsmithampemail=wordsmith40somewhere

而 qsparse可以幫我們把這個 querystring轉成物件的格式也就是

username=wordsmithampemail=wordsmith40somewhere

一旦轉成物件並指定給 user之後其他的事情就和 GET方法時操作的一樣寫 response的表頭將內容回傳並將 userusername和 useremail代入到內容中

修改完成後接著執行 nodejs程式啟動 web server開啟瀏覽器進入表單測試看看POST的方式能否順利運作

完整程式碼如下

var http = require(rsquohttprsquo)

url = require(rsquourlrsquo)

fs = require(rdquofsrdquo)

qs = require(rsquoquerystringrsquo)

server

server = httpcreateServer(function (reqres)

var urlData

67 Express POST應用範例 61

Nodejs中文電子書

encode = rdquoutf8rdquo

filePath = rdquoviewexpress_post_example_formhtmlrdquo

formData

action

urlData = urlparse(requrltrue)

action = urlDatapathname

reswriteHead(200 rdquoContent-Typerdquordquotexthtml charset=utf-8rdquo)

if (action === rdquoSignuprdquo)

formData = rsquorsquo

reqon(rdquodatardquo function (data)

formData += data

)

reqon(rdquoendrdquo function ()

user = qsparse(formData)

resend(rdquolth1gtrdquo + userusername + rdquo 歡迎您的加入 lth1gtltpgt 我們已經將會員啟用信寄至rdquo + useremail + rdquoltpgtrdquo)

)

else

fsreadFile(filePath encode function(err file)

reswrite(file)

resend()

)

)

serverlisten(3000)

consolelog(rsquoServer 跑起來了現在時間是rsquo + new Date())

62 6 Express介紹

Nodejs中文電子書

68 Express AJAX應用範例

在 Nodejs要使用 Ajax傳送資料並且與之互動在接受資料的部份沒有太大的差別client端不是用 GET就是用 POST來傳資料重點在處理完後用 JSON格式回傳當然 Ajax不見得只傳 JSON格式有時是回傳一段 HTML碼不過後者對伺服器來說基本上就和前兩篇沒有差別了所以我們還是以回傳 JSON做為這一回的主題

這一回其實大多數的工作都會落在前端 Ajax上面前端要負責發送與接收資料並在接收資料後撤掉原先發送資料的表單並將取得的資料

改成 HTML格式之後放上頁面

首先先準備 HTML靜態頁面資料

ltDOCTYPE htmlgt

lthtmlgt

ltheadgt

lttitlegtNodejs 菜鳥筆記 (3)lttitlegt

ltlink rel=rdquostylesheetrdquo href=rdquocssstylecssrdquo type=rdquotextcssrdquo media=rdquoallrdquo gt

ltscript src=rdquohttpsajaxgoogleapiscomajaxlibsjquery171jqueryminjsrdquogtltscriptgt

ltheadgt

ltbodygt

lth1gtNodejs 菜鳥筆記-註冊 (用 Ajax)lth1gt

ltform id=rdquosignuprdquo action=rdquoSignuprdquo method=rdquoPOSTrdquogt

ltlabelgt 使用者名稱ltlabelgtltinput type=rdquotextrdquo id=rdquousernamerdquo name=rdquousernamerdquo gtltbrgt

ltlabelgt 電子郵件ltlabelgtltinput type=rdquotextrdquo id=rdquoemailrdquo name=rdquoemailrdquo gtltbrgt

ltinput type=rdquosubmitrdquo value=rdquo 註冊我的帳號rdquo gtltbrgt

ltformgt

ltdiv id=rdquoresultrdquogt

ltdivgt

ltscript type=rdquotextjavascriptrdquogt

$(function()$(rsquosignup input[type=rdquosubmitrdquo]rsquo)click(function(e)

var user =

userusername = $(rdquousernamerdquo)val()useremail = $(rdquoemailrdquo)val()

$post(rdquoSignuprdquouserfunction(data)greet(data)

)

68 Express AJAX應用範例 63

Nodejs中文電子書

epreventDefault()

)

var greet = function(msg)

var resultNode = $(rsquoresultrsquo)greeting = $(rsquolth2gtHirsquo+msgusername+rsquo 你的會員 id 是rsquo+ msgid +rsquo 我們會將會員啟動信寄至rsquo+msgemail+rsquolth2gtrsquo)

$(rsquosignuprsquo)hide()resultNodehtml(rdquordquo)

resultNodeappend(greeting)

)

ltscriptgt

ltbodygt

lthtmlgt

HTML頁面上準備了一個表單用來傳送註冊資料接著直接引用了Google CDN來載入 jQuery用來幫我們處理 Ajax的工作這次要傳送和接收的工作很大的變動都在 HTML頁面上的 JavaScript當中我們要做的事有 (相關 jQuery處理這邊不多做贅述指提起主要功能解說)

bull 用 jQUery取得 submit按鈕綁定它的 click動作

bull 取得表單 username和 email的值存放在 user這個物件中

bull 用 jQuery的 $post方法將 user的資料傳到 Server

bull 一旦成功取得資料後透過 greet這個 function組成回報給 user的訊息

bull 清空原本給使用者填資料的表單

bull 將 Server回傳的 usernameemail和 id這 3個資料組成回應的訊息

bull 將訊息放到原本表單的位置

經過以上的處理後一個 Ajax的表單的基本功能已經完成

接著進行 nodejs主要程式的編輯部分程式碼如下

var fs = require(rdquofsrdquo)

64 6 Express介紹

Nodejs中文電子書

qs = require(rsquoquerystringrsquo)

if (action === rdquoSignuprdquo)

formData = rsquorsquo

reqon(rdquodatardquo function (data)

formData += data

)

reqon(rdquoendrdquo function ()

var msg

user = qsparse(formData)

userid = rdquo123456rdquo

msg = JSONstringify(user)

reswriteHead(200 rdquoContent-Typerdquordquoapplicationjson charset=utf-8rdquordquoContent-Lengthrdquomsglength)

resend(msg)

)

這裡的程式和前面 POST範例基本上大同小異差別在

bull 幫 user的資料加上 id隨意存放一些文字進去讓 Server回傳的資料多於 Client端傳上來的不然會覺得 Server都沒做事

bull 增加了 msg 這個變數存放將 user 物件 JSON 文字化的結果JSONstringify這個轉換函式是 V8引擎所提供的如果你好奇的話

bull 大重點來了我們要告訴 Client端這次回傳的資料格式是 JSON所在 Content-type和 Content-Length要提供給 Client

Server很輕鬆就完成任務了最後進行程式測試啟動 nodejs主程式之後開啟瀏覽器就會看到表單填寫完畢按下送出就可以看到結果了

最後 nodejs本篇範例程式碼如下

var http = require(rsquohttprsquo)

url = require(rsquourlrsquo)

fs = require(rdquofsrdquo)

qs = require(rsquoquerystringrsquo)

server

68 Express AJAX應用範例 65

Nodejs中文電子書

server = httpcreateServer(function (reqres)

var urlData

encode = rdquoutf8rdquo

filePath = rdquoviewexpress_ajax_example_formhtmlrdquo

formData

action

urlData = urlparse(requrltrue)

action = urlDatapathname

if (action === rdquoSignuprdquo)

formData = rsquorsquo

reqon(rdquodatardquo function (data)

formData += data

)

reqon(rdquoendrdquo function ()

var msg

user = qsparse(formData)

userid = rdquo123456rdquo

msg = JSONstringify(user)

reswriteHead(200 rdquoContent-TyperdquordquoapplicationjsonrdquordquoContent-Lengthrdquomsglength)

resend(msg)

)

else

fsreadFile(filePath encode function(err file)

reswriteHead(200 rdquoContent-Typerdquordquotexthtml charset=utf-8rdquo)

reswrite(file)

resend()

)

)

serverlisten(3000)

66 6 Express介紹

Nodejs中文電子書

consolelog(rsquoServer 跑起來了現在時間是rsquo + new Date())

69 原始資料提供

bull [NodeJS初學者筆記 (1)-用 GET傳送資料] (httpithelpithomecomtwquestion10087402)

bull [NodeJS初學者筆記 (2)-用 POST傳送資料] (httpithelpithomecomtwquestion10087489)

bull [NodeJS 初學者筆記 (3)-用 Ajax 傳送資料] (httpithelpithomecomtwquestion10087627)

69 原始資料提供 67

Nodejs中文電子書

68 6 Express介紹

7

CoffeeScript

bull Spine

bull Hem

bull Spineapp

Letrsquos Have a Cup of CoffeeScript

bull es5-shim ECMAScript 5 compatibility shims for legacy JavaScript engines

ESnext

node-iform - A middleware for node-validator httpsgithubcomguileennode-iform

69

Nodejs中文電子書

70 7 CoffeeScript

8

製作一個 Hubot的 Plurk Adapter

81 應用事項提醒

此應用範例以CoffeeScript編寫主要程式架構採用Github釋放的Hubot專案以下有幾個主要注意事項請讀者注意

bull Hubot 一開始的架構除了內建的兩個 Adapter 之外其餘都要以Nodejs的Module方式才能運作

bull Hubot的 binhubot裡面寫著 npm install所以不管你怎麼改原始碼也不能改變第一點的狀況

其實上述都在討論同一件事情NodeJS的模組而且模組不能設定相對路徑之類的來安裝一定要包裝成 npm相符和格式才有辦法正確執行

82 建立 Adapter

首先需要準備兩個檔案

bull packagejson

bull plurkcoffee

71

Nodejs中文電子書

有這兩個就足以變成 NodeJS的模組了在開始 Coding之前先來設定一下 packagejson相依性

rdquonamerdquo rdquohubot-plurkrdquo

rdquoversionrdquo rdquo011rdquo

rdquomainrdquo rdquoplurkrdquo

rdquodependenciesrdquo

rdquohubotrdquo rdquogt=205rdquo

rdquooauthrdquo rdquordquo

rdquocronrdquordquordquo

這邊會使用到NodeJS的 cron模組(Module)而登入 Plurk需要OAuth標準授權才行所以也需要加載 OAuth模組

注意Hubot在讀取非內建模組時會自動在前面加上 hubot-的前置

83 建立 Robot跟 API

本篇應用基本上程式碼大部分參考 Hubot - Twitter Adapter來製作主要差異只有在使用 OAuth這個模組Twitter有 Streaming可用而 Plurk則得用 Comet方式來達到即時讀取

plurkcoffee程式碼

Robot = require(rdquohubotrdquo)robot()

Adapter = require(rdquohubtordquo)adapter()

EventEmitter = require(rdquoeventsrdquo)EventEmitter

oauth = require(rdquooauthrdquo)

cronJob = require(rdquocronrdquo)CronJob

class Plurk exntends Adapter

class PlurkStreaming exnteds EventEmitter

72 8 製作一個 Hubot的 Plurk Adapter

Nodejs中文電子書

先弄個基本架構主要 Class皆參考 Twitter Adapter命名方式接著再將 Plurk這個 Class增加幾個Method基本上只要有 run send reply就夠了而 run用來做初始化的部分

class Plurk entends Adapter

send (plurk id strings⋯) -gt

reply (plurk id strings⋯) -gt

run -gt

看起來有點東西接著來處理主要的 Plurk API結合部分

class PlurkStreaming extends EventEmitter

consuctor (options) -gt

plurk (callback) -gt

觀察河道

getChannel -gt

取得 Comet 網址

reply (plurk id message) -gt

回噗

acceptFriends -gt

接受好友

get (path callback) -gt

GET 請求

post (path body callback)-gt

POST 請求(其實是裝飾)

request (method path body callback)-gt

主要的 OAuth 請求

comet (server callback)-gt

噗浪的 Comet 傳回是 JavaScript Callback 要另外處理後才會變成 JSON

然後把注意力集中到 constructor上先把建構子弄好

constructor (options) -gt

super()

if optionskey and optionssecret and optionstoken and optionstoken secret

key = optionskey

secret = optionssecret

token = optionstoken

token secret = optionstoken secret

83 建立 Robot跟 API 73

Nodejs中文電子書

建立 OAuth 連接

consumer = new oauthOAuth(

rdquohttpwwwplurkcomOAuthrequest tokenrdquo

rdquohttpwwwplurkcomOAuthaccess tokenrdquo

key

secret

rdquo10rdquo

rdquohttpwwwplurkcomOAuthauthorizerdquo

rdquoHMAC-SHA1rdquo

)

domain = rdquowwwplurkcomrdquo

初始化取得 Comet 網址

do getChannel

else

throw new Error(rdquo 參數不足需要 Key Secret Token Token Secretrdquo)

接著來處理 request這個 method

request (method path body callback) -gt

記錄一下這次的 Request

consolelog(rdquohttpdomainpathrdquo)

Callback 這邊先不丟進去要用另一種方式處理

request = consumerget(rdquohttpdomainpathrdquo token token secret null)

requeston rdquoresponserdquo (res) -gt

reson rdquodatardquo (chunk) -gt

parseResponse(chunk+rsquorsquo callback)

reson rdquoendrdquo (data) -gt

consolelog rdquoEnd Request pathrdquo

reson rdquoerrorrdquo (data) -gt

consolelog rdquoError rdquo + data

requestend()

處理資料

parseResponse = (data callback) -gt

if datalength gt 0

用 TryCatch 避免處理 JSON 出錯導致整個中斷

try

callback null JSONparse(data)

catch err

74 8 製作一個 Hubot的 Plurk Adapter

Nodejs中文電子書

consolelog(rdquoError Parse JSONrdquo + data err)

繼續執行

callback null data ||

大致上就是這樣上面程式的架構已經將整個 Hubot Plurk Adapter完成因為在測試時竟然因為噗浪 Lag而沒讀到完整的 Comet資料然後造成程式異常為了避免這個問題發生因此需要加上為了完美呈現需要再

加上 Comet的處理所以要使用到 EventEmitter的功能

comet (server callback) -gt

在 Callback 裡面會找不到自身所以設定區域變數

self =

記錄一下這次的 Request

consolelog(rdquo[Comet] serverrdquo)

Callback 這邊先不丟進去要用另一種方式處理

request = consumerget(rdquohttpdomainpathrdquo token token secret null)

requeston rdquoresponserdquo (res) -gt

reson rdquodatardquo (chunk) -gt

parseResponse(chunk+rsquorsquo callback)

reson rdquoendrdquo (data) -gt

consolelog rdquoEnd Request pathrdquo

請求結束發出事件通知可以進行下一次請求

selfemit rdquonextPlurkrdquo

reson rdquoerrorrdquo (data) -gt

consolelog rdquoError rdquo + data

requestend()

處理資料

parseResponse = (data callback) -gt

if datalength gt 0

用 trycatch 避免失敗中斷

try

去掉 JavaScript 的 Callback

data = datamatch(CometChannelscriptCallback((+))s)

jsonData = rdquordquo

if data

83 建立 Robot跟 API 75

Nodejs中文電子書

jsonData = JSONparse(data[1])

else

如果沒有任何 Match 嘗試直接 parse

jsonData = JSONparse(data)

catch err

consolelog(rdquo[Comet] Errorrdquo data err)

用 TryCatch 避免處理 JSON 出錯導致整個中斷

try

只傳入 json 的 data 部分

callback null jsonDatadata

catch err

consolelog(rdquo[Comet]Error Parse JSONrdquo + data err)

繼續執行

callback null data ||

後面的 get跟 post就簡單多了

get (path callback) -gt

request(rdquoGETrdquo path null callback)

post (path body callback) -gt

request(rdquoPOSTrdquo path body callback)

接著處理取的 Comet網址的 getChannel

getChannel -gt

self =

get rdquoAPPRealtimegetUserChannelrdquo (error data) -gt

if error

檢查是否有 comet server

if datacomet server

selfchannel = datacomet server

如果沒有 Channel Ready 就嘗試連接會失敗

selfemit(rsquochannel readyrsquo)

那麼先來處理 Plurk Adaper好處理的部份

send (plruk id strings⋯)-gt

跟 Reply 一樣直接交給 reply 做

reply plurk id strings⋯

76 8 製作一個 Hubot的 Plurk Adapter

Nodejs中文電子書

reply (plurk id strings⋯) -gt

stringsforEach (message) =gt

botreply(plruk id message)

接著把 run處理好就可以上線運作摟

run -gt

self =

options =

key processenvHUBOT PLURK KEY

secret processenvHUBOT PLURK SECRET

token processenvHUBOT PLURK TOKEN

token secret processenvHUBOT PLURK TOKEN SECRET

創建剛剛的 API

bot = new PlurkStreaming(options)

依照 Twitter 的 new RobotTextMessage 會沒有反應所以參考 hubot-minecraft 的方式

r = robotconstructor

處理噗浪河道訊息

doPlurk = (data)-gt

檢查是否為回噗

if dataresponse

datacontent raw = dataresponsecontent raw

datauser id = dataresponseuser id

確定有噗浪 ID 跟訊息

if dataplurk id and datacontent raw

selfreceive new rTextMessage(dataplurk id datacontent raw)

取得 Comet Server 完成開始第一次 Comet 連接

boton rdquochannel readyrdquo () -gt

botplurk selfdoPlurk

上一次 Comet 完成繼續 Polling

boton rdquonextPlurkrdquo ()-gt

botplurk selfdoPlurk

定時接受好友邀請

do botacceptFriends

bot = bot

83 建立 Robot跟 API 77

Nodejs中文電子書

終於完成 AdapterHubot專案裡面的 scripts資料夾內是互動部分不需要像 Adapter如此大費周章處理只新增檔案並且設計好對白之後就會回噗了我開發用的機器人在此大家可以去跟他玩玩[httpplurkcomelct9620 bot](httpplurkcomelct9620 bot)

84 原始資料提供

bull [製 作 一 個 Hubot 的 噗 浪 Adapter](http revo-skillfrosttw blog20120318create-a-hubot-plurk-adapter)

78 8 製作一個 Hubot的 Plurk Adapter

9

Nodejs好用工具介紹

91 logio

Real-time log monitoring in your browser

httplogioorg

nodejs (gt=0412 lt=0611)

npm config set unsafe-perm true

npm install -g --prefix=usrlocal logio

logio server start

httplocalhost8998

79

Nodejs中文電子書

80 9 Nodejs好用工具介紹

10

精選文章

101 Express

bull 使用 NodeJS + Express從 GETPOST Request取值 [邀稿中]

bull link2 [邀稿中]

bull link3 [邀稿中]

81

Nodejs中文電子書

82 10 精選文章

11

參考資源

111 Nodejs書籍

bull Programming Nodejs April 2012 (est) Travis Swicegood Pragmatic Bookshelf

bull e Little Book on CoffeeScript January 2012 Alex MacCaw OrsquoReilly Media

bull Node for Front-End Developers January 2012 Garann Means OrsquoReilly Media

bull Building Hypermedia APIs with HTML5 and Node November 2011 MikeAmundsen OrsquoReilly Media

bull Node Web Development August 2011 David Herron PacktPub

bull CoffeeScript Accelerated JavaScript Development July 2011 Trevor BurnhamPragmatic Bookshelf

bull Getting Started with GEO CouchDB and Nodejs July 2011 Mick omp-son OrsquoReilly Media

bull What Is Node July 2011 Brett McLaughlin OrsquoReilly Media

bull Node Up and Running May 2011 Tom Hughes-Croucher Mike WilsonOrsquoReilly Media

83

Nodejs中文電子書

bull Hands-on Nodejs e Nodejs introduction and API reference May 2011 Pe-dro Teixeira LeanPub

bull e Node Beginner Book Manuel Kiessling

bull Mastering Nodejs visionmedia

112 Nodejs影音教學

bull e Node Sessions e Best of OSCON 2011 August 2011 OrsquoReilly Media(Video)

bull Tom Hughes-Croucher on Node March 2011 OrsquoReilly Media (Video)

113 Nodejs教學網站

bull Node Tuts

114 Nodejs課程

bull Introduction To Nodejs Van Nguyen CodeLesson

84 11 參考資源

Page 47: Nodejs Wiki

5

NPM套件管理工具

npm全名為 Node Package Manager是 Nodejs的套件(package)管理工具類似 Perl的 ppm或 PHP的 PEAR等安裝 npm後使用npm install

module name指令即可安裝新套件維護管理套件的工作會更加輕鬆

npm可以讓Nodejs的開發者直接利用擴充線上的套件庫(packagesregistry)加速軟體專案的開發npm提供很友善的搜尋功能可以快速找到安裝需要的套件當這些套件發行新版本時npm也可以協助開發者自動更新這些套件

npm不僅可用於安裝新的套件它也支援搜尋列出已安裝模組及更新的功能

51 安裝 NPM

Nodejs在 063版本開始內建 npm讀者安裝的版本若是此版本或更新的版本就可以略過以下安裝說明

若要檢查 npm是否正確安裝可以使用以下的指令

npm -v

41

Nodejs中文電子書

執行結果說明

若 npm正確安裝執行 npm -v將會看到類似 110-2的版本訊息

若讀者安裝的 Nodejs版本比較舊或是有興趣嘗試自己動手安裝 npm工具則可以參考以下的說明

安裝於Windows系統

Nodejs for Windows 於 062 版開始內建 npm使用 nodejsorg 官方提供的安裝程式不需要進一步的設定就可以立即使用 npm指令對於Windows的開發者來說大幅降低環境設定的問題與門檻

除了使用 Nodejs內建的 npm讀者也可以從 npm官方提供的以下網址

httpnpmjsorgdist

這是由 npm提供的 Fancy Windows Install版本請下載壓縮檔(例如npm-110-3zip)並將壓縮檔內容解壓縮至 Nodejs的安裝路徑(例如CProgram Filesnodejs)

解壓縮後在 Nodejs的安裝路徑下應該有以下的檔案及資料夾

bull npmcmd(檔案)

bull node modules(資料夾)

安裝於 Linux系統

Ubuntu Linux的使用者可以加入 NPM Unoffcial PPA這個 repository即可使用 apt-get完成 npm安裝

42 5 NPM套件管理工具

Nodejs中文電子書

Ubuntu Linux使用 apt-get安裝 npm

sudo apt-get install python-software-properties

sudo add-apt-repository ppagias-kay-leenpm

sudo apt-get update

sudo apt-get install npm

npm官方提供的安裝程式 installsh可以適用於大多數的 Linux系統使用這個安裝程式請先確認

1 系統已安裝 curl工具(請使用 curl --version查看版本訊息)

2 已安裝 Nodejs並且 PATH正確設置

3 Nodejs的版本必須大於 04x

以下為 npm提供的安裝指令

curl httpnpmjsorginstallsh | sh

安裝成功會看到如下訊息

installsh安裝成功的訊息

npm10105 homeUSERNAMElocalnodelibnode_modulesnpm

It worked

安裝於Mac OS X

建議採用與 Nodejs相同的方式進行 npm的安裝例如使用MacPorts安裝 Nodejs就同樣使用MacPorts安裝 npm這樣對日後的維護才會更方便容易

使用 MacPorts安裝 npm是本書比較建議的方式它可以讓 npm的安裝移除及更新工作自動化將會幫助開發者節省寶貴時間

51 安裝 NPM 43

Nodejs中文電子書

安裝MacPorts的提示

在MacPorts網站可以取得 OS X系統版本對應的安裝程式(例如 106或 107)httpwwwmacportsorg安裝過程會詢問系統管理者密碼使用預設的選項完成安裝即可安

裝MacPorts之後在終端機執行 port -v將會看到MacPorts的版本訊息

安裝 npm之前先更新MacPorts的套件清單以確保安裝的 npm是最新版本

sudo port -d selfupdate

接著安裝 npm

sudo port install npm

若讀者的 Nodejs並非使用MacPorts安裝則不建議使用MacPorts安裝npm因為 MacPorts會自動檢查並安裝相依套件而 npm相依 nodejs所以MacPorts也會一併將 nodejs套件安裝造成先前讀者使用其它方式安裝的 nodejs被覆蓋

讀者可以先使用 MacPorts安裝 curl(sudo port install curl)

再參考 Linux的 installsh安裝方式即可使用 npm官方提供的安裝程式

NPM安裝後測試

npm是指令列工具(command-line tool)使用時請先打開系統的文字終端機工具

測試 npm安裝與設定是否正確請輸入指令如下

npm -v

或是

npm --version

如果 npm已經正確安裝設定就會顯示版本訊息

44 5 NPM套件管理工具

Nodejs中文電子書

執行結果(範例)

110-2

52 使用 NPM安裝套件

npm目前擁有超過 6000種套件(packages)可以在 npm registry使用關鍵字搜尋套件

httpsearchnpmjsorg

舉例來說在關鍵字欄位輸入「coffee-script」下方的清單就會自動列出包含 coffee-script關鍵字的套件

接著我們回到終端機模式的操作npm的指令工具本身就可以完成套

件搜尋的任務

例如以下的指令同樣可以找出 coffee-script相關套件

npm search coffee-script

以下是搜尋結果的參考畫面

52 使用 NPM安裝套件 45

Nodejs中文電子書

找到需要的套件後(例如 express)即可使用以下指令安裝

npm install coffee-script

值得注意的一點是使用 npm install會將指定的套件安裝在工

作目錄(Working Directory)的 node modules資料夾下

以Windows為例如果執行 npm install的目錄位於

Cproject1

那麼 npm將會自動建立一個 node modules的子目錄(如果不存在)

Cproject1node modules

並且將下載的套件放置於這個子目錄例如

Cproject1node modulescoffee-script

這個設計讓專案可以個別管理相依的套件並且可以在專案佈署或發

行時將這些套件(位於 node modules)一併打包方便其它專案的使用者不必再重新下載套件

這個 npm install的預設安裝模式為 local(本地)只會變更當前專案的資料夾不會影響系統

另一種安裝模式稱為 global(全域)這種模式會將套件安裝到系統資

料夾也就是 npm安裝路徑的 node modules資料夾例如

CProgram Filesnodejsnode modules

46 5 NPM套件管理工具

Nodejs中文電子書

是否要使用全域安裝可以依照套件是否提供新指令來判斷舉例來說express 套件提供 express 這個指令而 coffee-script 則提供 coffee

指令

在 local 安 裝 模 式 中這 些 指 令 的 程 式 檔 案會 被 安 裝 到node modules的 bin這個隱藏資料夾下除非將bin的路徑加入 PATH環境變數否則要執行這些指令將會相當不便

為了方便指令的執行我們可以在 npm install 加上 -g 或 --

global參數啟用 global安裝模式例如

npm install -g coffee-script

npm install -g express

使用 global安裝模式需要注意執行權限的問題若權限不足可能會出現類似以下的錯誤訊息

npm ERR Error EACCES permission denied rsquorsquo

npm ERR

npm ERR Please try running this command again as rootAdministrator

要獲得足夠得執行權限請參考以下說明

bull Windows 7 或 2008 以上在「命令提示字元」的捷徑按右鍵選擇「以系統管理員身分執行」執行 npm指令時就會具有 Administrator身分

bull Mac OS X或 Linux系統可以使用 sudo指令例如

sudo npm install -g express

bull Linux系統可以使用 root權限登入或是以「sudo su -」切換成 root身分(使用 root權限操作系統相當危險因此並不建議使用這種方式)

若加上 -g參數使用 npm install -g coffee-script完成安裝

後就可以在終端機執行 coffee指令例如

coffee -v

52 使用 NPM安裝套件 47

Nodejs中文電子書

執行結果(範例)

CoffeeScript version 120

53 套件的更新及維護

除了前一節說明的 search 及 install 用法npm 還提供其他許多指令(commands)

使用 npm help可以查詢可用的指令

npm help

執行結果(部分)

where ltcommandgt is one of

adduser apihelp author bin bugs c cache completion

config deprecate docs edit explore faq find get

help help-search home i info init install la link

list ll ln login ls outdated owner pack prefix

prune publish r rb rebuild remove restart rm root

run-script s se search set show star start stop

submodule tag test un uninstall unlink unpublish

unstar up update version view whoami

使用 npm help command可以查詢指令的詳細用法例如

npm help list

接下來本節要介紹開發過程常用的 npm指令

使用 list可以列出已安裝套件

npm list

48 5 NPM套件管理工具

Nodejs中文電子書

執行結果(範例)

-- coffee-script120

- express256

- connect185

| -- formidable108

-- mime124

-- mkdirp007

-- qs041

檢視某個套件的詳細資訊例如

npm show express

升級所有套件(如果該套件已發佈更新版本)

npm update

升級指定的套件

npm update express

移除指定的套件

npm uninstall express

54 使用 packagejson

對於正式的 Nodejs專案可以建立一個命名為 packagejson的設

定檔(純文字格式)檔案內容參考範例如下

54 使用 packagejson 49

Nodejs中文電子書

packagejson(範例)

rdquonamerdquo rdquoapplication-namerdquo

rdquoversionrdquo rdquo001rdquo

rdquoprivaterdquo true

rdquodependenciesrdquo

rdquoexpressrdquo rdquo255rdquo

rdquocoffee-scriptrdquo rdquolatestrdquo

rdquomongooserdquo rdquogt= 253rdquo

其中 name與 version依照專案的需求設置

需要注意的是 dependencies的設定它用於指定專案相依的套件名

稱及版本

bull rdquoexpressrdquo rdquo255rdquo

代表此專案相依版本 255的 express套件

bull rdquocoffee-scriptrdquo rdquolatestrdquo

使用最新版的 coffee-script套件(每次更新都會檢查新版)

bull rdquomongooserdquo rdquogt= 253rdquo

使用版本大於 253的 mongoose套件

假設某個套件的新版可能造成專案無法正常運作就必須指定套件的

版本避免專案的程式碼來不及更新以相容新版套件通常在開發初期的

專案需要盡可能維持新套件的相容性(以取得套件的更新或修正)可以

用「gt=」設定最低相容的版本或是使用「latest」設定永遠保持最新

套件

50 5 NPM套件管理工具

6

Express介紹

在前面的 nodejs基礎當中介紹許多許多開設 http的使用方法及介紹以及許多基本的 nodejs基本應用

接下來要介紹一個套件稱為 express [Express](httpexpressjscom)這個套件主要幫忙解決許多 nodejs http server所需要的基本服務讓開發 httpservice變得更為容易不需要像之前需要透過層層模組(module)才有辦法開始編寫自己的程式

這個套件是由 TJ Holowaychuk 製作而成的套件裡面包含基本的路由處理 (route)http資料處理(GETPOSTPUT)另外還與樣板套件(jshtml template engine)搭配同時也可以處理許多複雜化的問題

61 Express安裝

安裝方式十分簡單只要透過之前介紹的 NPM就可以使用簡單的指令安裝指令如下

這邊建議需要將此套件安裝成為全域模組方便日後使用

51

Nodejs中文電子書

62 Express基本操作

express的使用也十分簡單先來建立一個基本的 hello world

var app = require(rsquoexpressrsquo)createServer()

port = 1337

applisten(port)

appget(rsquorsquo function(req res)

ressend(rsquohello worldrsquo)

)

consolelog(rsquostart express servernrsquo)

可以從上面的程式碼發現基本操作與 nodejs http的建立方式沒有太大差異主要差在當我們設定路由時可以直接透過 appget方式設定回應與接受方式

63 Express路由處理

Express對於 http服務上有許多包裝讓開發者使用及設定上更為方便例如有幾個路由設定那我們就統一藉由 appget來處理

Create http server

appget(rsquorsquo function(req res)

ressend(rsquohello worldrsquo)

)

appget(rsquotestrsquo function(req res)

ressend(rsquotest renderrsquo)

)

appget(rsquouserrsquo function(req res)

ressend(rsquouser pagersquo)

)

52 6 Express介紹

Nodejs中文電子書

如上面的程式碼所表示appget可以帶入兩個參數第一個是路徑名稱設定第二個為回應函式 (call back function)回應函式裡面就如同之前的 createServer方法裡面包含 requestresponse兩個物件可供使用使用者就可以透過瀏覽器輸入不同的 url切換到不同的頁面顯示不同的結果

路由設定上也有基本的配對方式讓使用者從瀏覽器輸入的網址可以

是一個變數只要符合型態就可以有對應的頁面產出例如

Create http server

appget(rsquouseridrsquo function(req res)

ressend(rsquouser rsquo + reqparamsid)

)

appget(rsquonumberrsquo function(req res)

ressend(rsquonumber rsquo + reqparamsnumber)

)

裡 面 使 用 到number 從 網 址 輸 入 之 後 就 可 以 直 接 使 用reqparamsnumber 取得所輸入的資料變成 url 參數使用當然前面也是可以加上路徑的設定userid在瀏覽器上路徑必須符合userxxx透

過 reqparamsid就可以取到 xxx這個字串值

另外express參數處理也提供了路由參數配對處理也可以透過正規表示法作為參數設定

var app = require(rsquoexpressrsquo)createServer()

port = 1337

applisten(port)

appget(^ip((d23)((d23))((d23))((d23))) function(req res)

ressend(reqparams)

)

上面程式碼可以發現後面路由設定的型態是正規表示法裡面設定

格式為ip之後必須要加上 ip型態才會符合資料格式同時取得 ip資料已經由正規表示法將資料做分群因此可以取得 ip的四個數字

此程式執行之後可以透過瀏覽器測試輸入網址為 localhost3000ip25525510010可以從頁面獲得資料

63 Express路由處理 53

Nodejs中文電子書

此章節全部範例程式碼如下

overview

author Caesar Chi

blog clonnblogspotcom

version 20120226

create server

var app = require(rsquoexpressrsquo)createServer()

port = 1337

applisten(port)

normal style

appget(rsquorsquo function(req res)

ressend(rsquohello worldrsquo)

)

appget(rsquotestrsquo function(req res)

ressend(rsquotest renderrsquo)

)

parameter style

appget(rsquouseridrsquo function(req res)

ressend(rsquouser rsquo + reqparamsid)

)

appget(rsquonumberrsquo function(req res)

ressend(rsquonumber rsquo + reqparamsnumber)

)

REGX style

appget(^ip((d23)((d23))((d23))) function(req res)

ressend(reqparams)

)

appget(rsquorsquo function(req res)

ressend(rsquoPage not foundrsquo 404)

54 6 Express介紹

Nodejs中文電子書

)

consolelog(rsquostart express servernrsquo)

64 Express middleware

Express 裡面有一個十分好用的應用概念稱為 middleware可以透過middleware做出複雜的效果同時上面也有介紹 next方法參數傳遞就是靠 middleware的概念來傳遞參數讓開發者可以明確的控制程式邏輯

create http server

appuse(expressbodyParser())

appuse(expressmethodOverride())

appuse(expresssession())

上面都是一種 middleware的使用方式透過 appuse方式裡面載入函式執行方法回應函式會包含三個基本參數responserequestnext其中next表示下一個 middleware執行函式同時會自動將預設三個參數繼續帶往下個函式執行底下有個實驗

上面的片段程式執行後開啟瀏覽器連結上 localhost1337會發現伺服器回應結果順序如下

first middle ware

second middle ware

execute middle ware

end middleware function

從上面的結果可以得知剛才設定的 middleware都生效了在 appuse設定的 middleware是所有 url皆會執行方法如果有指定特定方法就可以使用 appget的 middleware設定在 appget函式的第二個參數就可以帶入函式或者是匿名函式只要函式裡面最後會接受 request response next這三個參數同時也有正確指定 next函式的執行時機最後都會執行到最後一個方法當然開發者也可以評估程式邏輯要執行到哪一個階段讓邏輯

可以更為分明

64 Express middleware 55

Nodejs中文電子書

65 Express路由應用

在實際開發上可能會遇到需要使用參數等方式混和變數一起使用

express裡面提供了一個很棒的處理方法 appall這個方式可以先採用基本路由配對再將設定為每個不同的處理方式開發者可以透過這個方式簡

化自己的程式邏輯

overview

author

version 20120226

create server

var app = require(rsquoexpressrsquo)createServer()

port = 1337

users = [

name rsquoClonnrsquo

name rsquoChirsquo

]

applisten(port)

appall(rsquouseridoprsquo function(req res next)

requser = users[reqparamsid]

if (requser)

next()

else

next(new Error(rsquocannot find user rsquo + reqparamsid))

)

appget(rsquouseridrsquo function(req res)

ressend(rsquoviewing rsquo + requsername)

)

appget(rsquouserideditrsquo function(req res)

ressend(rsquoediting rsquo + requsername)

)

56 6 Express介紹

Nodejs中文電子書

appget(rsquouseriddeletersquo function(req res)

ressend(rsquodeleting rsquo + requsername)

)

appget(rsquorsquo function(req res)

ressend(rsquoPage not foundrsquo 404)

)

consolelog(rsquostart express servernrsquo)

內部宣告一組預設的使用者分別給予名稱設定藉由 appall這個方法可以先將路由雛形建立再接下來設定 appget的路徑格式只要符合格式就會分配進入對應的方法中像上面的程式當中如果使用者輸入路徑為user0除了執行 appall程式之後執行 next方法就會對應到路徑設定為userid的這個方法當中如果使用者輸入路徑為user0edit就會執行到useridedit的對應方法

66 Express GET應用範例

我們準備一個使用 GET方法傳送資料的表單

ltDOCTYPE htmlgt

lthtmlgt

ltheadgt

ltmeta http-equiv=rdquoContent-Typerdquo content=rdquotexthtml charset=utf8rdquogt

lttitlegtNodejs 菜鳥筆記 (1)lttitlegt

ltlink rel=rdquostylesheetrdquo href=rdquocssstylecssrdquo type=rdquotextcssrdquo media=rdquoallrdquo gt

ltheadgt

ltbodygt

lth1gtNodejs 菜鳥筆記-註冊lth1gt

ltform id=rdquosignuprdquo method=rdquoGETrdquo action=rdquohttplocalhost3000Signuprdquogt

ltlabelgt 使用者名稱ltlabelgtltinput type=rdquotextrdquo id=rdquousernamerdquo name=rdquousernamerdquo gtltbrgt

ltlabelgt 電子郵件ltlabelgtltinput type=rdquotextrdquo id=rdquoemailrdquo name=rdquoemailrdquo gtltbrgt

ltinput type=rdquosubmitrdquo value=rdquo 註冊我的帳號rdquo gtltbrgt

ltformgt

ltbodygt

lthtmlgt

66 Express GET應用範例 57

Nodejs中文電子書

這個表單沒有什麼特別的地方我們只需要看第 9 行form 使用的method是 GET然後 action是rdquohttplocalhost3000Signupldquo等一下我們要來撰寫Signup這個 URL Path的處理程式

處理 Signup行為

我們知道所謂的 GET方法會透過 URL來把表單的值給帶過去以上面的表單來說到時候 URL會以這樣的形式傳遞

httplocalhost3000Signupusername=xxxampemail=xxx

所以要能處理這樣的資料必須有以下功能

bull 解析 URL

bull 辨別動作是 Signup

bull 解析出 username和 email

一旦能取得 username和 email的值程式就能加以應用了

處理 Signup的程式碼雛形

load module

var url = require(rsquourlrsquo)

urlData = urlparse(requrltrue)

action = urlDatapathname

reswriteHead(200 rdquoContent-Typerdquordquotexthtml charset=utf-8rdquo)

if (action === rdquoSignuprdquo)

user = urlDataquery

resend(rdquolth1gtrdquo + userusername + rdquo 歡迎您的加入 lth1gtltpgt 我們已經將會員啟用信寄至rdquo + useremail + rdquoltpgtrdquo)

首先需要加載 url module它是用來協助我們解析 URL的模組接著使用 urlparse方法第一個傳入 url字串變數也就是 requrl另外第二個參數的用意是設為 ture則引進 querystring模組來協助處理預設是 false它影響到的是 urlDataquery設為 true會傳回物件不然就只是一般的字串urlparse會將字串內容整理成一個物件我們把它指定給 urlData

action變數作為記錄 pathname這是我們稍後要來判斷目前網頁的動作是什麼接著先將 html表頭資訊 (Header)準備好再來判斷路徑邏輯如

58 6 Express介紹

Nodejs中文電子書

果是 Signup這個動作就把 urlDataquery裡的資料指定給 user然後輸出userusername和 useremail把使用者從表單註冊的資料顯示於頁面中

最後進行程式測試啟動 nodejs主程式之後開啟瀏覽器就會看到表單填寫完畢按下送出就可以看到結果了

完整 nodejs程式碼如下

var http = require(rsquohttprsquo)

url = require(rsquourlrsquo)

fs = require(rdquofsrdquo)

server

server = httpcreateServer(function (reqres)

var urlData

encode = rdquoutf8rdquo

filePath = rdquoviewexpress_get_example_formhtmlrdquo

action

urlData = urlparse(requrltrue)

action = urlDatapathname

reswriteHead(200 rdquoContent-Typerdquordquotexthtml charset=utf-8rdquo)

if (action === rdquoSignuprdquo)

user = urlDataquery

resend(rdquolth1gtrdquo + userusername + rdquo 歡迎您的加入 lth1gtltpgt 我們已經將會員啟用信寄至rdquo + useremail + rdquoltpgtrdquo)

else

fsreadFile(filePath encode function(err file)

reswrite(file)

resend()

)

)

serverlisten(3000)

consolelog(rsquoServer 跑起來了現在時間是rsquo + new Date())

66 Express GET應用範例 59

Nodejs中文電子書

67 Express POST應用範例

一開始準備基本的 html表單傳送內容以 POST方式form的 action屬性設定為 POST其餘 html內容與前一個範例應用相同

ltDOCTYPE htmlgt

lthtmlgt

ltheadgt

ltmeta http-equiv=rdquoContent-Typerdquo content=rdquotexthtml charset=utf8rdquogt

lttitlegtNodejs 菜鳥筆記 (1)lttitlegt

ltlink rel=rdquostylesheetrdquo href=rdquocssstylecssrdquo type=rdquotextcssrdquo media=rdquoallrdquo gt

ltheadgt

ltbodygt

lth1gtNodejs 菜鳥筆記-註冊lth1gt

ltform id=rdquosignuprdquo method=rdquoPOSTrdquo action=rdquohttplocalhost3000Signuprdquogt

ltlabelgt 使用者名稱ltlabelgtltinput type=rdquotextrdquo id=rdquousernamerdquo name=rdquousernamerdquo gtltbrgt

ltlabelgt 電子郵件ltlabelgtltinput type=rdquotextrdquo id=rdquoemailrdquo name=rdquoemailrdquo gtltbrgt

ltinput type=rdquosubmitrdquo value=rdquo 註冊我的帳號rdquo gtltbrgt

ltformgt

ltbodygt

lthtmlgt

nodejs的程式處理邏輯與前面 GET範例類似部分程式碼如下

qs = require(rsquoquerystringrsquo)

if (action === rdquoSignuprdquo)

formData = rsquorsquo

reqon(rdquodatardquo function (data)

formData += data

)

reqon(rdquoendrdquo function ()

user = qsparse(formData)

resend(rdquolth1gtrdquo + userusername + rdquo 歡迎您的加入 lth1gtltpgt 我們已經將會員啟用信寄至rdquo + useremail + rdquoltpgtrdquo)

)

60 6 Express介紹

Nodejs中文電子書

主要加入了rsquoquerystringrsquo這個moduel方便我們等一下解析由表單 POST回來的資料另外加入一個 formData的變數用來搜集待等一下表單回傳的資料前面的 GET範例我們只從 req拿出 url的資料這次要在利用req身上的事件處理

JavaScript 在訂閱事件時使用 addEventListener而 nodejs 使用的則是on這邊加上了監聽 data的事件會在瀏覽器傳送資料到Web Server時被執行參數是它所接收到的資料型態是字串

接著再增加 end的事件當瀏覽器的請求事件結束時它就會動作

由於瀏覽器使用 POST在上傳資料時會將資料一塊塊地上傳因為我們在監聽 data事件時透過 formData變數將它累加起來lt不過由於我們

上傳的資料很少一次就結束不過如果日後需要傳的是資料比較大的檔

案這個累加動作就很重要

當資料傳完就進到 end 事件中會用到 qsparse 來解析 formDataformData的內容是字串內容是

username=wordsmithampemail=wordsmith40somewhere

而 qsparse可以幫我們把這個 querystring轉成物件的格式也就是

username=wordsmithampemail=wordsmith40somewhere

一旦轉成物件並指定給 user之後其他的事情就和 GET方法時操作的一樣寫 response的表頭將內容回傳並將 userusername和 useremail代入到內容中

修改完成後接著執行 nodejs程式啟動 web server開啟瀏覽器進入表單測試看看POST的方式能否順利運作

完整程式碼如下

var http = require(rsquohttprsquo)

url = require(rsquourlrsquo)

fs = require(rdquofsrdquo)

qs = require(rsquoquerystringrsquo)

server

server = httpcreateServer(function (reqres)

var urlData

67 Express POST應用範例 61

Nodejs中文電子書

encode = rdquoutf8rdquo

filePath = rdquoviewexpress_post_example_formhtmlrdquo

formData

action

urlData = urlparse(requrltrue)

action = urlDatapathname

reswriteHead(200 rdquoContent-Typerdquordquotexthtml charset=utf-8rdquo)

if (action === rdquoSignuprdquo)

formData = rsquorsquo

reqon(rdquodatardquo function (data)

formData += data

)

reqon(rdquoendrdquo function ()

user = qsparse(formData)

resend(rdquolth1gtrdquo + userusername + rdquo 歡迎您的加入 lth1gtltpgt 我們已經將會員啟用信寄至rdquo + useremail + rdquoltpgtrdquo)

)

else

fsreadFile(filePath encode function(err file)

reswrite(file)

resend()

)

)

serverlisten(3000)

consolelog(rsquoServer 跑起來了現在時間是rsquo + new Date())

62 6 Express介紹

Nodejs中文電子書

68 Express AJAX應用範例

在 Nodejs要使用 Ajax傳送資料並且與之互動在接受資料的部份沒有太大的差別client端不是用 GET就是用 POST來傳資料重點在處理完後用 JSON格式回傳當然 Ajax不見得只傳 JSON格式有時是回傳一段 HTML碼不過後者對伺服器來說基本上就和前兩篇沒有差別了所以我們還是以回傳 JSON做為這一回的主題

這一回其實大多數的工作都會落在前端 Ajax上面前端要負責發送與接收資料並在接收資料後撤掉原先發送資料的表單並將取得的資料

改成 HTML格式之後放上頁面

首先先準備 HTML靜態頁面資料

ltDOCTYPE htmlgt

lthtmlgt

ltheadgt

lttitlegtNodejs 菜鳥筆記 (3)lttitlegt

ltlink rel=rdquostylesheetrdquo href=rdquocssstylecssrdquo type=rdquotextcssrdquo media=rdquoallrdquo gt

ltscript src=rdquohttpsajaxgoogleapiscomajaxlibsjquery171jqueryminjsrdquogtltscriptgt

ltheadgt

ltbodygt

lth1gtNodejs 菜鳥筆記-註冊 (用 Ajax)lth1gt

ltform id=rdquosignuprdquo action=rdquoSignuprdquo method=rdquoPOSTrdquogt

ltlabelgt 使用者名稱ltlabelgtltinput type=rdquotextrdquo id=rdquousernamerdquo name=rdquousernamerdquo gtltbrgt

ltlabelgt 電子郵件ltlabelgtltinput type=rdquotextrdquo id=rdquoemailrdquo name=rdquoemailrdquo gtltbrgt

ltinput type=rdquosubmitrdquo value=rdquo 註冊我的帳號rdquo gtltbrgt

ltformgt

ltdiv id=rdquoresultrdquogt

ltdivgt

ltscript type=rdquotextjavascriptrdquogt

$(function()$(rsquosignup input[type=rdquosubmitrdquo]rsquo)click(function(e)

var user =

userusername = $(rdquousernamerdquo)val()useremail = $(rdquoemailrdquo)val()

$post(rdquoSignuprdquouserfunction(data)greet(data)

)

68 Express AJAX應用範例 63

Nodejs中文電子書

epreventDefault()

)

var greet = function(msg)

var resultNode = $(rsquoresultrsquo)greeting = $(rsquolth2gtHirsquo+msgusername+rsquo 你的會員 id 是rsquo+ msgid +rsquo 我們會將會員啟動信寄至rsquo+msgemail+rsquolth2gtrsquo)

$(rsquosignuprsquo)hide()resultNodehtml(rdquordquo)

resultNodeappend(greeting)

)

ltscriptgt

ltbodygt

lthtmlgt

HTML頁面上準備了一個表單用來傳送註冊資料接著直接引用了Google CDN來載入 jQuery用來幫我們處理 Ajax的工作這次要傳送和接收的工作很大的變動都在 HTML頁面上的 JavaScript當中我們要做的事有 (相關 jQuery處理這邊不多做贅述指提起主要功能解說)

bull 用 jQUery取得 submit按鈕綁定它的 click動作

bull 取得表單 username和 email的值存放在 user這個物件中

bull 用 jQuery的 $post方法將 user的資料傳到 Server

bull 一旦成功取得資料後透過 greet這個 function組成回報給 user的訊息

bull 清空原本給使用者填資料的表單

bull 將 Server回傳的 usernameemail和 id這 3個資料組成回應的訊息

bull 將訊息放到原本表單的位置

經過以上的處理後一個 Ajax的表單的基本功能已經完成

接著進行 nodejs主要程式的編輯部分程式碼如下

var fs = require(rdquofsrdquo)

64 6 Express介紹

Nodejs中文電子書

qs = require(rsquoquerystringrsquo)

if (action === rdquoSignuprdquo)

formData = rsquorsquo

reqon(rdquodatardquo function (data)

formData += data

)

reqon(rdquoendrdquo function ()

var msg

user = qsparse(formData)

userid = rdquo123456rdquo

msg = JSONstringify(user)

reswriteHead(200 rdquoContent-Typerdquordquoapplicationjson charset=utf-8rdquordquoContent-Lengthrdquomsglength)

resend(msg)

)

這裡的程式和前面 POST範例基本上大同小異差別在

bull 幫 user的資料加上 id隨意存放一些文字進去讓 Server回傳的資料多於 Client端傳上來的不然會覺得 Server都沒做事

bull 增加了 msg 這個變數存放將 user 物件 JSON 文字化的結果JSONstringify這個轉換函式是 V8引擎所提供的如果你好奇的話

bull 大重點來了我們要告訴 Client端這次回傳的資料格式是 JSON所在 Content-type和 Content-Length要提供給 Client

Server很輕鬆就完成任務了最後進行程式測試啟動 nodejs主程式之後開啟瀏覽器就會看到表單填寫完畢按下送出就可以看到結果了

最後 nodejs本篇範例程式碼如下

var http = require(rsquohttprsquo)

url = require(rsquourlrsquo)

fs = require(rdquofsrdquo)

qs = require(rsquoquerystringrsquo)

server

68 Express AJAX應用範例 65

Nodejs中文電子書

server = httpcreateServer(function (reqres)

var urlData

encode = rdquoutf8rdquo

filePath = rdquoviewexpress_ajax_example_formhtmlrdquo

formData

action

urlData = urlparse(requrltrue)

action = urlDatapathname

if (action === rdquoSignuprdquo)

formData = rsquorsquo

reqon(rdquodatardquo function (data)

formData += data

)

reqon(rdquoendrdquo function ()

var msg

user = qsparse(formData)

userid = rdquo123456rdquo

msg = JSONstringify(user)

reswriteHead(200 rdquoContent-TyperdquordquoapplicationjsonrdquordquoContent-Lengthrdquomsglength)

resend(msg)

)

else

fsreadFile(filePath encode function(err file)

reswriteHead(200 rdquoContent-Typerdquordquotexthtml charset=utf-8rdquo)

reswrite(file)

resend()

)

)

serverlisten(3000)

66 6 Express介紹

Nodejs中文電子書

consolelog(rsquoServer 跑起來了現在時間是rsquo + new Date())

69 原始資料提供

bull [NodeJS初學者筆記 (1)-用 GET傳送資料] (httpithelpithomecomtwquestion10087402)

bull [NodeJS初學者筆記 (2)-用 POST傳送資料] (httpithelpithomecomtwquestion10087489)

bull [NodeJS 初學者筆記 (3)-用 Ajax 傳送資料] (httpithelpithomecomtwquestion10087627)

69 原始資料提供 67

Nodejs中文電子書

68 6 Express介紹

7

CoffeeScript

bull Spine

bull Hem

bull Spineapp

Letrsquos Have a Cup of CoffeeScript

bull es5-shim ECMAScript 5 compatibility shims for legacy JavaScript engines

ESnext

node-iform - A middleware for node-validator httpsgithubcomguileennode-iform

69

Nodejs中文電子書

70 7 CoffeeScript

8

製作一個 Hubot的 Plurk Adapter

81 應用事項提醒

此應用範例以CoffeeScript編寫主要程式架構採用Github釋放的Hubot專案以下有幾個主要注意事項請讀者注意

bull Hubot 一開始的架構除了內建的兩個 Adapter 之外其餘都要以Nodejs的Module方式才能運作

bull Hubot的 binhubot裡面寫著 npm install所以不管你怎麼改原始碼也不能改變第一點的狀況

其實上述都在討論同一件事情NodeJS的模組而且模組不能設定相對路徑之類的來安裝一定要包裝成 npm相符和格式才有辦法正確執行

82 建立 Adapter

首先需要準備兩個檔案

bull packagejson

bull plurkcoffee

71

Nodejs中文電子書

有這兩個就足以變成 NodeJS的模組了在開始 Coding之前先來設定一下 packagejson相依性

rdquonamerdquo rdquohubot-plurkrdquo

rdquoversionrdquo rdquo011rdquo

rdquomainrdquo rdquoplurkrdquo

rdquodependenciesrdquo

rdquohubotrdquo rdquogt=205rdquo

rdquooauthrdquo rdquordquo

rdquocronrdquordquordquo

這邊會使用到NodeJS的 cron模組(Module)而登入 Plurk需要OAuth標準授權才行所以也需要加載 OAuth模組

注意Hubot在讀取非內建模組時會自動在前面加上 hubot-的前置

83 建立 Robot跟 API

本篇應用基本上程式碼大部分參考 Hubot - Twitter Adapter來製作主要差異只有在使用 OAuth這個模組Twitter有 Streaming可用而 Plurk則得用 Comet方式來達到即時讀取

plurkcoffee程式碼

Robot = require(rdquohubotrdquo)robot()

Adapter = require(rdquohubtordquo)adapter()

EventEmitter = require(rdquoeventsrdquo)EventEmitter

oauth = require(rdquooauthrdquo)

cronJob = require(rdquocronrdquo)CronJob

class Plurk exntends Adapter

class PlurkStreaming exnteds EventEmitter

72 8 製作一個 Hubot的 Plurk Adapter

Nodejs中文電子書

先弄個基本架構主要 Class皆參考 Twitter Adapter命名方式接著再將 Plurk這個 Class增加幾個Method基本上只要有 run send reply就夠了而 run用來做初始化的部分

class Plurk entends Adapter

send (plurk id strings⋯) -gt

reply (plurk id strings⋯) -gt

run -gt

看起來有點東西接著來處理主要的 Plurk API結合部分

class PlurkStreaming extends EventEmitter

consuctor (options) -gt

plurk (callback) -gt

觀察河道

getChannel -gt

取得 Comet 網址

reply (plurk id message) -gt

回噗

acceptFriends -gt

接受好友

get (path callback) -gt

GET 請求

post (path body callback)-gt

POST 請求(其實是裝飾)

request (method path body callback)-gt

主要的 OAuth 請求

comet (server callback)-gt

噗浪的 Comet 傳回是 JavaScript Callback 要另外處理後才會變成 JSON

然後把注意力集中到 constructor上先把建構子弄好

constructor (options) -gt

super()

if optionskey and optionssecret and optionstoken and optionstoken secret

key = optionskey

secret = optionssecret

token = optionstoken

token secret = optionstoken secret

83 建立 Robot跟 API 73

Nodejs中文電子書

建立 OAuth 連接

consumer = new oauthOAuth(

rdquohttpwwwplurkcomOAuthrequest tokenrdquo

rdquohttpwwwplurkcomOAuthaccess tokenrdquo

key

secret

rdquo10rdquo

rdquohttpwwwplurkcomOAuthauthorizerdquo

rdquoHMAC-SHA1rdquo

)

domain = rdquowwwplurkcomrdquo

初始化取得 Comet 網址

do getChannel

else

throw new Error(rdquo 參數不足需要 Key Secret Token Token Secretrdquo)

接著來處理 request這個 method

request (method path body callback) -gt

記錄一下這次的 Request

consolelog(rdquohttpdomainpathrdquo)

Callback 這邊先不丟進去要用另一種方式處理

request = consumerget(rdquohttpdomainpathrdquo token token secret null)

requeston rdquoresponserdquo (res) -gt

reson rdquodatardquo (chunk) -gt

parseResponse(chunk+rsquorsquo callback)

reson rdquoendrdquo (data) -gt

consolelog rdquoEnd Request pathrdquo

reson rdquoerrorrdquo (data) -gt

consolelog rdquoError rdquo + data

requestend()

處理資料

parseResponse = (data callback) -gt

if datalength gt 0

用 TryCatch 避免處理 JSON 出錯導致整個中斷

try

callback null JSONparse(data)

catch err

74 8 製作一個 Hubot的 Plurk Adapter

Nodejs中文電子書

consolelog(rdquoError Parse JSONrdquo + data err)

繼續執行

callback null data ||

大致上就是這樣上面程式的架構已經將整個 Hubot Plurk Adapter完成因為在測試時竟然因為噗浪 Lag而沒讀到完整的 Comet資料然後造成程式異常為了避免這個問題發生因此需要加上為了完美呈現需要再

加上 Comet的處理所以要使用到 EventEmitter的功能

comet (server callback) -gt

在 Callback 裡面會找不到自身所以設定區域變數

self =

記錄一下這次的 Request

consolelog(rdquo[Comet] serverrdquo)

Callback 這邊先不丟進去要用另一種方式處理

request = consumerget(rdquohttpdomainpathrdquo token token secret null)

requeston rdquoresponserdquo (res) -gt

reson rdquodatardquo (chunk) -gt

parseResponse(chunk+rsquorsquo callback)

reson rdquoendrdquo (data) -gt

consolelog rdquoEnd Request pathrdquo

請求結束發出事件通知可以進行下一次請求

selfemit rdquonextPlurkrdquo

reson rdquoerrorrdquo (data) -gt

consolelog rdquoError rdquo + data

requestend()

處理資料

parseResponse = (data callback) -gt

if datalength gt 0

用 trycatch 避免失敗中斷

try

去掉 JavaScript 的 Callback

data = datamatch(CometChannelscriptCallback((+))s)

jsonData = rdquordquo

if data

83 建立 Robot跟 API 75

Nodejs中文電子書

jsonData = JSONparse(data[1])

else

如果沒有任何 Match 嘗試直接 parse

jsonData = JSONparse(data)

catch err

consolelog(rdquo[Comet] Errorrdquo data err)

用 TryCatch 避免處理 JSON 出錯導致整個中斷

try

只傳入 json 的 data 部分

callback null jsonDatadata

catch err

consolelog(rdquo[Comet]Error Parse JSONrdquo + data err)

繼續執行

callback null data ||

後面的 get跟 post就簡單多了

get (path callback) -gt

request(rdquoGETrdquo path null callback)

post (path body callback) -gt

request(rdquoPOSTrdquo path body callback)

接著處理取的 Comet網址的 getChannel

getChannel -gt

self =

get rdquoAPPRealtimegetUserChannelrdquo (error data) -gt

if error

檢查是否有 comet server

if datacomet server

selfchannel = datacomet server

如果沒有 Channel Ready 就嘗試連接會失敗

selfemit(rsquochannel readyrsquo)

那麼先來處理 Plurk Adaper好處理的部份

send (plruk id strings⋯)-gt

跟 Reply 一樣直接交給 reply 做

reply plurk id strings⋯

76 8 製作一個 Hubot的 Plurk Adapter

Nodejs中文電子書

reply (plurk id strings⋯) -gt

stringsforEach (message) =gt

botreply(plruk id message)

接著把 run處理好就可以上線運作摟

run -gt

self =

options =

key processenvHUBOT PLURK KEY

secret processenvHUBOT PLURK SECRET

token processenvHUBOT PLURK TOKEN

token secret processenvHUBOT PLURK TOKEN SECRET

創建剛剛的 API

bot = new PlurkStreaming(options)

依照 Twitter 的 new RobotTextMessage 會沒有反應所以參考 hubot-minecraft 的方式

r = robotconstructor

處理噗浪河道訊息

doPlurk = (data)-gt

檢查是否為回噗

if dataresponse

datacontent raw = dataresponsecontent raw

datauser id = dataresponseuser id

確定有噗浪 ID 跟訊息

if dataplurk id and datacontent raw

selfreceive new rTextMessage(dataplurk id datacontent raw)

取得 Comet Server 完成開始第一次 Comet 連接

boton rdquochannel readyrdquo () -gt

botplurk selfdoPlurk

上一次 Comet 完成繼續 Polling

boton rdquonextPlurkrdquo ()-gt

botplurk selfdoPlurk

定時接受好友邀請

do botacceptFriends

bot = bot

83 建立 Robot跟 API 77

Nodejs中文電子書

終於完成 AdapterHubot專案裡面的 scripts資料夾內是互動部分不需要像 Adapter如此大費周章處理只新增檔案並且設計好對白之後就會回噗了我開發用的機器人在此大家可以去跟他玩玩[httpplurkcomelct9620 bot](httpplurkcomelct9620 bot)

84 原始資料提供

bull [製 作 一 個 Hubot 的 噗 浪 Adapter](http revo-skillfrosttw blog20120318create-a-hubot-plurk-adapter)

78 8 製作一個 Hubot的 Plurk Adapter

9

Nodejs好用工具介紹

91 logio

Real-time log monitoring in your browser

httplogioorg

nodejs (gt=0412 lt=0611)

npm config set unsafe-perm true

npm install -g --prefix=usrlocal logio

logio server start

httplocalhost8998

79

Nodejs中文電子書

80 9 Nodejs好用工具介紹

10

精選文章

101 Express

bull 使用 NodeJS + Express從 GETPOST Request取值 [邀稿中]

bull link2 [邀稿中]

bull link3 [邀稿中]

81

Nodejs中文電子書

82 10 精選文章

11

參考資源

111 Nodejs書籍

bull Programming Nodejs April 2012 (est) Travis Swicegood Pragmatic Bookshelf

bull e Little Book on CoffeeScript January 2012 Alex MacCaw OrsquoReilly Media

bull Node for Front-End Developers January 2012 Garann Means OrsquoReilly Media

bull Building Hypermedia APIs with HTML5 and Node November 2011 MikeAmundsen OrsquoReilly Media

bull Node Web Development August 2011 David Herron PacktPub

bull CoffeeScript Accelerated JavaScript Development July 2011 Trevor BurnhamPragmatic Bookshelf

bull Getting Started with GEO CouchDB and Nodejs July 2011 Mick omp-son OrsquoReilly Media

bull What Is Node July 2011 Brett McLaughlin OrsquoReilly Media

bull Node Up and Running May 2011 Tom Hughes-Croucher Mike WilsonOrsquoReilly Media

83

Nodejs中文電子書

bull Hands-on Nodejs e Nodejs introduction and API reference May 2011 Pe-dro Teixeira LeanPub

bull e Node Beginner Book Manuel Kiessling

bull Mastering Nodejs visionmedia

112 Nodejs影音教學

bull e Node Sessions e Best of OSCON 2011 August 2011 OrsquoReilly Media(Video)

bull Tom Hughes-Croucher on Node March 2011 OrsquoReilly Media (Video)

113 Nodejs教學網站

bull Node Tuts

114 Nodejs課程

bull Introduction To Nodejs Van Nguyen CodeLesson

84 11 參考資源

Page 48: Nodejs Wiki

Nodejs中文電子書

執行結果說明

若 npm正確安裝執行 npm -v將會看到類似 110-2的版本訊息

若讀者安裝的 Nodejs版本比較舊或是有興趣嘗試自己動手安裝 npm工具則可以參考以下的說明

安裝於Windows系統

Nodejs for Windows 於 062 版開始內建 npm使用 nodejsorg 官方提供的安裝程式不需要進一步的設定就可以立即使用 npm指令對於Windows的開發者來說大幅降低環境設定的問題與門檻

除了使用 Nodejs內建的 npm讀者也可以從 npm官方提供的以下網址

httpnpmjsorgdist

這是由 npm提供的 Fancy Windows Install版本請下載壓縮檔(例如npm-110-3zip)並將壓縮檔內容解壓縮至 Nodejs的安裝路徑(例如CProgram Filesnodejs)

解壓縮後在 Nodejs的安裝路徑下應該有以下的檔案及資料夾

bull npmcmd(檔案)

bull node modules(資料夾)

安裝於 Linux系統

Ubuntu Linux的使用者可以加入 NPM Unoffcial PPA這個 repository即可使用 apt-get完成 npm安裝

42 5 NPM套件管理工具

Nodejs中文電子書

Ubuntu Linux使用 apt-get安裝 npm

sudo apt-get install python-software-properties

sudo add-apt-repository ppagias-kay-leenpm

sudo apt-get update

sudo apt-get install npm

npm官方提供的安裝程式 installsh可以適用於大多數的 Linux系統使用這個安裝程式請先確認

1 系統已安裝 curl工具(請使用 curl --version查看版本訊息)

2 已安裝 Nodejs並且 PATH正確設置

3 Nodejs的版本必須大於 04x

以下為 npm提供的安裝指令

curl httpnpmjsorginstallsh | sh

安裝成功會看到如下訊息

installsh安裝成功的訊息

npm10105 homeUSERNAMElocalnodelibnode_modulesnpm

It worked

安裝於Mac OS X

建議採用與 Nodejs相同的方式進行 npm的安裝例如使用MacPorts安裝 Nodejs就同樣使用MacPorts安裝 npm這樣對日後的維護才會更方便容易

使用 MacPorts安裝 npm是本書比較建議的方式它可以讓 npm的安裝移除及更新工作自動化將會幫助開發者節省寶貴時間

51 安裝 NPM 43

Nodejs中文電子書

安裝MacPorts的提示

在MacPorts網站可以取得 OS X系統版本對應的安裝程式(例如 106或 107)httpwwwmacportsorg安裝過程會詢問系統管理者密碼使用預設的選項完成安裝即可安

裝MacPorts之後在終端機執行 port -v將會看到MacPorts的版本訊息

安裝 npm之前先更新MacPorts的套件清單以確保安裝的 npm是最新版本

sudo port -d selfupdate

接著安裝 npm

sudo port install npm

若讀者的 Nodejs並非使用MacPorts安裝則不建議使用MacPorts安裝npm因為 MacPorts會自動檢查並安裝相依套件而 npm相依 nodejs所以MacPorts也會一併將 nodejs套件安裝造成先前讀者使用其它方式安裝的 nodejs被覆蓋

讀者可以先使用 MacPorts安裝 curl(sudo port install curl)

再參考 Linux的 installsh安裝方式即可使用 npm官方提供的安裝程式

NPM安裝後測試

npm是指令列工具(command-line tool)使用時請先打開系統的文字終端機工具

測試 npm安裝與設定是否正確請輸入指令如下

npm -v

或是

npm --version

如果 npm已經正確安裝設定就會顯示版本訊息

44 5 NPM套件管理工具

Nodejs中文電子書

執行結果(範例)

110-2

52 使用 NPM安裝套件

npm目前擁有超過 6000種套件(packages)可以在 npm registry使用關鍵字搜尋套件

httpsearchnpmjsorg

舉例來說在關鍵字欄位輸入「coffee-script」下方的清單就會自動列出包含 coffee-script關鍵字的套件

接著我們回到終端機模式的操作npm的指令工具本身就可以完成套

件搜尋的任務

例如以下的指令同樣可以找出 coffee-script相關套件

npm search coffee-script

以下是搜尋結果的參考畫面

52 使用 NPM安裝套件 45

Nodejs中文電子書

找到需要的套件後(例如 express)即可使用以下指令安裝

npm install coffee-script

值得注意的一點是使用 npm install會將指定的套件安裝在工

作目錄(Working Directory)的 node modules資料夾下

以Windows為例如果執行 npm install的目錄位於

Cproject1

那麼 npm將會自動建立一個 node modules的子目錄(如果不存在)

Cproject1node modules

並且將下載的套件放置於這個子目錄例如

Cproject1node modulescoffee-script

這個設計讓專案可以個別管理相依的套件並且可以在專案佈署或發

行時將這些套件(位於 node modules)一併打包方便其它專案的使用者不必再重新下載套件

這個 npm install的預設安裝模式為 local(本地)只會變更當前專案的資料夾不會影響系統

另一種安裝模式稱為 global(全域)這種模式會將套件安裝到系統資

料夾也就是 npm安裝路徑的 node modules資料夾例如

CProgram Filesnodejsnode modules

46 5 NPM套件管理工具

Nodejs中文電子書

是否要使用全域安裝可以依照套件是否提供新指令來判斷舉例來說express 套件提供 express 這個指令而 coffee-script 則提供 coffee

指令

在 local 安 裝 模 式 中這 些 指 令 的 程 式 檔 案會 被 安 裝 到node modules的 bin這個隱藏資料夾下除非將bin的路徑加入 PATH環境變數否則要執行這些指令將會相當不便

為了方便指令的執行我們可以在 npm install 加上 -g 或 --

global參數啟用 global安裝模式例如

npm install -g coffee-script

npm install -g express

使用 global安裝模式需要注意執行權限的問題若權限不足可能會出現類似以下的錯誤訊息

npm ERR Error EACCES permission denied rsquorsquo

npm ERR

npm ERR Please try running this command again as rootAdministrator

要獲得足夠得執行權限請參考以下說明

bull Windows 7 或 2008 以上在「命令提示字元」的捷徑按右鍵選擇「以系統管理員身分執行」執行 npm指令時就會具有 Administrator身分

bull Mac OS X或 Linux系統可以使用 sudo指令例如

sudo npm install -g express

bull Linux系統可以使用 root權限登入或是以「sudo su -」切換成 root身分(使用 root權限操作系統相當危險因此並不建議使用這種方式)

若加上 -g參數使用 npm install -g coffee-script完成安裝

後就可以在終端機執行 coffee指令例如

coffee -v

52 使用 NPM安裝套件 47

Nodejs中文電子書

執行結果(範例)

CoffeeScript version 120

53 套件的更新及維護

除了前一節說明的 search 及 install 用法npm 還提供其他許多指令(commands)

使用 npm help可以查詢可用的指令

npm help

執行結果(部分)

where ltcommandgt is one of

adduser apihelp author bin bugs c cache completion

config deprecate docs edit explore faq find get

help help-search home i info init install la link

list ll ln login ls outdated owner pack prefix

prune publish r rb rebuild remove restart rm root

run-script s se search set show star start stop

submodule tag test un uninstall unlink unpublish

unstar up update version view whoami

使用 npm help command可以查詢指令的詳細用法例如

npm help list

接下來本節要介紹開發過程常用的 npm指令

使用 list可以列出已安裝套件

npm list

48 5 NPM套件管理工具

Nodejs中文電子書

執行結果(範例)

-- coffee-script120

- express256

- connect185

| -- formidable108

-- mime124

-- mkdirp007

-- qs041

檢視某個套件的詳細資訊例如

npm show express

升級所有套件(如果該套件已發佈更新版本)

npm update

升級指定的套件

npm update express

移除指定的套件

npm uninstall express

54 使用 packagejson

對於正式的 Nodejs專案可以建立一個命名為 packagejson的設

定檔(純文字格式)檔案內容參考範例如下

54 使用 packagejson 49

Nodejs中文電子書

packagejson(範例)

rdquonamerdquo rdquoapplication-namerdquo

rdquoversionrdquo rdquo001rdquo

rdquoprivaterdquo true

rdquodependenciesrdquo

rdquoexpressrdquo rdquo255rdquo

rdquocoffee-scriptrdquo rdquolatestrdquo

rdquomongooserdquo rdquogt= 253rdquo

其中 name與 version依照專案的需求設置

需要注意的是 dependencies的設定它用於指定專案相依的套件名

稱及版本

bull rdquoexpressrdquo rdquo255rdquo

代表此專案相依版本 255的 express套件

bull rdquocoffee-scriptrdquo rdquolatestrdquo

使用最新版的 coffee-script套件(每次更新都會檢查新版)

bull rdquomongooserdquo rdquogt= 253rdquo

使用版本大於 253的 mongoose套件

假設某個套件的新版可能造成專案無法正常運作就必須指定套件的

版本避免專案的程式碼來不及更新以相容新版套件通常在開發初期的

專案需要盡可能維持新套件的相容性(以取得套件的更新或修正)可以

用「gt=」設定最低相容的版本或是使用「latest」設定永遠保持最新

套件

50 5 NPM套件管理工具

6

Express介紹

在前面的 nodejs基礎當中介紹許多許多開設 http的使用方法及介紹以及許多基本的 nodejs基本應用

接下來要介紹一個套件稱為 express [Express](httpexpressjscom)這個套件主要幫忙解決許多 nodejs http server所需要的基本服務讓開發 httpservice變得更為容易不需要像之前需要透過層層模組(module)才有辦法開始編寫自己的程式

這個套件是由 TJ Holowaychuk 製作而成的套件裡面包含基本的路由處理 (route)http資料處理(GETPOSTPUT)另外還與樣板套件(jshtml template engine)搭配同時也可以處理許多複雜化的問題

61 Express安裝

安裝方式十分簡單只要透過之前介紹的 NPM就可以使用簡單的指令安裝指令如下

這邊建議需要將此套件安裝成為全域模組方便日後使用

51

Nodejs中文電子書

62 Express基本操作

express的使用也十分簡單先來建立一個基本的 hello world

var app = require(rsquoexpressrsquo)createServer()

port = 1337

applisten(port)

appget(rsquorsquo function(req res)

ressend(rsquohello worldrsquo)

)

consolelog(rsquostart express servernrsquo)

可以從上面的程式碼發現基本操作與 nodejs http的建立方式沒有太大差異主要差在當我們設定路由時可以直接透過 appget方式設定回應與接受方式

63 Express路由處理

Express對於 http服務上有許多包裝讓開發者使用及設定上更為方便例如有幾個路由設定那我們就統一藉由 appget來處理

Create http server

appget(rsquorsquo function(req res)

ressend(rsquohello worldrsquo)

)

appget(rsquotestrsquo function(req res)

ressend(rsquotest renderrsquo)

)

appget(rsquouserrsquo function(req res)

ressend(rsquouser pagersquo)

)

52 6 Express介紹

Nodejs中文電子書

如上面的程式碼所表示appget可以帶入兩個參數第一個是路徑名稱設定第二個為回應函式 (call back function)回應函式裡面就如同之前的 createServer方法裡面包含 requestresponse兩個物件可供使用使用者就可以透過瀏覽器輸入不同的 url切換到不同的頁面顯示不同的結果

路由設定上也有基本的配對方式讓使用者從瀏覽器輸入的網址可以

是一個變數只要符合型態就可以有對應的頁面產出例如

Create http server

appget(rsquouseridrsquo function(req res)

ressend(rsquouser rsquo + reqparamsid)

)

appget(rsquonumberrsquo function(req res)

ressend(rsquonumber rsquo + reqparamsnumber)

)

裡 面 使 用 到number 從 網 址 輸 入 之 後 就 可 以 直 接 使 用reqparamsnumber 取得所輸入的資料變成 url 參數使用當然前面也是可以加上路徑的設定userid在瀏覽器上路徑必須符合userxxx透

過 reqparamsid就可以取到 xxx這個字串值

另外express參數處理也提供了路由參數配對處理也可以透過正規表示法作為參數設定

var app = require(rsquoexpressrsquo)createServer()

port = 1337

applisten(port)

appget(^ip((d23)((d23))((d23))((d23))) function(req res)

ressend(reqparams)

)

上面程式碼可以發現後面路由設定的型態是正規表示法裡面設定

格式為ip之後必須要加上 ip型態才會符合資料格式同時取得 ip資料已經由正規表示法將資料做分群因此可以取得 ip的四個數字

此程式執行之後可以透過瀏覽器測試輸入網址為 localhost3000ip25525510010可以從頁面獲得資料

63 Express路由處理 53

Nodejs中文電子書

此章節全部範例程式碼如下

overview

author Caesar Chi

blog clonnblogspotcom

version 20120226

create server

var app = require(rsquoexpressrsquo)createServer()

port = 1337

applisten(port)

normal style

appget(rsquorsquo function(req res)

ressend(rsquohello worldrsquo)

)

appget(rsquotestrsquo function(req res)

ressend(rsquotest renderrsquo)

)

parameter style

appget(rsquouseridrsquo function(req res)

ressend(rsquouser rsquo + reqparamsid)

)

appget(rsquonumberrsquo function(req res)

ressend(rsquonumber rsquo + reqparamsnumber)

)

REGX style

appget(^ip((d23)((d23))((d23))) function(req res)

ressend(reqparams)

)

appget(rsquorsquo function(req res)

ressend(rsquoPage not foundrsquo 404)

54 6 Express介紹

Nodejs中文電子書

)

consolelog(rsquostart express servernrsquo)

64 Express middleware

Express 裡面有一個十分好用的應用概念稱為 middleware可以透過middleware做出複雜的效果同時上面也有介紹 next方法參數傳遞就是靠 middleware的概念來傳遞參數讓開發者可以明確的控制程式邏輯

create http server

appuse(expressbodyParser())

appuse(expressmethodOverride())

appuse(expresssession())

上面都是一種 middleware的使用方式透過 appuse方式裡面載入函式執行方法回應函式會包含三個基本參數responserequestnext其中next表示下一個 middleware執行函式同時會自動將預設三個參數繼續帶往下個函式執行底下有個實驗

上面的片段程式執行後開啟瀏覽器連結上 localhost1337會發現伺服器回應結果順序如下

first middle ware

second middle ware

execute middle ware

end middleware function

從上面的結果可以得知剛才設定的 middleware都生效了在 appuse設定的 middleware是所有 url皆會執行方法如果有指定特定方法就可以使用 appget的 middleware設定在 appget函式的第二個參數就可以帶入函式或者是匿名函式只要函式裡面最後會接受 request response next這三個參數同時也有正確指定 next函式的執行時機最後都會執行到最後一個方法當然開發者也可以評估程式邏輯要執行到哪一個階段讓邏輯

可以更為分明

64 Express middleware 55

Nodejs中文電子書

65 Express路由應用

在實際開發上可能會遇到需要使用參數等方式混和變數一起使用

express裡面提供了一個很棒的處理方法 appall這個方式可以先採用基本路由配對再將設定為每個不同的處理方式開發者可以透過這個方式簡

化自己的程式邏輯

overview

author

version 20120226

create server

var app = require(rsquoexpressrsquo)createServer()

port = 1337

users = [

name rsquoClonnrsquo

name rsquoChirsquo

]

applisten(port)

appall(rsquouseridoprsquo function(req res next)

requser = users[reqparamsid]

if (requser)

next()

else

next(new Error(rsquocannot find user rsquo + reqparamsid))

)

appget(rsquouseridrsquo function(req res)

ressend(rsquoviewing rsquo + requsername)

)

appget(rsquouserideditrsquo function(req res)

ressend(rsquoediting rsquo + requsername)

)

56 6 Express介紹

Nodejs中文電子書

appget(rsquouseriddeletersquo function(req res)

ressend(rsquodeleting rsquo + requsername)

)

appget(rsquorsquo function(req res)

ressend(rsquoPage not foundrsquo 404)

)

consolelog(rsquostart express servernrsquo)

內部宣告一組預設的使用者分別給予名稱設定藉由 appall這個方法可以先將路由雛形建立再接下來設定 appget的路徑格式只要符合格式就會分配進入對應的方法中像上面的程式當中如果使用者輸入路徑為user0除了執行 appall程式之後執行 next方法就會對應到路徑設定為userid的這個方法當中如果使用者輸入路徑為user0edit就會執行到useridedit的對應方法

66 Express GET應用範例

我們準備一個使用 GET方法傳送資料的表單

ltDOCTYPE htmlgt

lthtmlgt

ltheadgt

ltmeta http-equiv=rdquoContent-Typerdquo content=rdquotexthtml charset=utf8rdquogt

lttitlegtNodejs 菜鳥筆記 (1)lttitlegt

ltlink rel=rdquostylesheetrdquo href=rdquocssstylecssrdquo type=rdquotextcssrdquo media=rdquoallrdquo gt

ltheadgt

ltbodygt

lth1gtNodejs 菜鳥筆記-註冊lth1gt

ltform id=rdquosignuprdquo method=rdquoGETrdquo action=rdquohttplocalhost3000Signuprdquogt

ltlabelgt 使用者名稱ltlabelgtltinput type=rdquotextrdquo id=rdquousernamerdquo name=rdquousernamerdquo gtltbrgt

ltlabelgt 電子郵件ltlabelgtltinput type=rdquotextrdquo id=rdquoemailrdquo name=rdquoemailrdquo gtltbrgt

ltinput type=rdquosubmitrdquo value=rdquo 註冊我的帳號rdquo gtltbrgt

ltformgt

ltbodygt

lthtmlgt

66 Express GET應用範例 57

Nodejs中文電子書

這個表單沒有什麼特別的地方我們只需要看第 9 行form 使用的method是 GET然後 action是rdquohttplocalhost3000Signupldquo等一下我們要來撰寫Signup這個 URL Path的處理程式

處理 Signup行為

我們知道所謂的 GET方法會透過 URL來把表單的值給帶過去以上面的表單來說到時候 URL會以這樣的形式傳遞

httplocalhost3000Signupusername=xxxampemail=xxx

所以要能處理這樣的資料必須有以下功能

bull 解析 URL

bull 辨別動作是 Signup

bull 解析出 username和 email

一旦能取得 username和 email的值程式就能加以應用了

處理 Signup的程式碼雛形

load module

var url = require(rsquourlrsquo)

urlData = urlparse(requrltrue)

action = urlDatapathname

reswriteHead(200 rdquoContent-Typerdquordquotexthtml charset=utf-8rdquo)

if (action === rdquoSignuprdquo)

user = urlDataquery

resend(rdquolth1gtrdquo + userusername + rdquo 歡迎您的加入 lth1gtltpgt 我們已經將會員啟用信寄至rdquo + useremail + rdquoltpgtrdquo)

首先需要加載 url module它是用來協助我們解析 URL的模組接著使用 urlparse方法第一個傳入 url字串變數也就是 requrl另外第二個參數的用意是設為 ture則引進 querystring模組來協助處理預設是 false它影響到的是 urlDataquery設為 true會傳回物件不然就只是一般的字串urlparse會將字串內容整理成一個物件我們把它指定給 urlData

action變數作為記錄 pathname這是我們稍後要來判斷目前網頁的動作是什麼接著先將 html表頭資訊 (Header)準備好再來判斷路徑邏輯如

58 6 Express介紹

Nodejs中文電子書

果是 Signup這個動作就把 urlDataquery裡的資料指定給 user然後輸出userusername和 useremail把使用者從表單註冊的資料顯示於頁面中

最後進行程式測試啟動 nodejs主程式之後開啟瀏覽器就會看到表單填寫完畢按下送出就可以看到結果了

完整 nodejs程式碼如下

var http = require(rsquohttprsquo)

url = require(rsquourlrsquo)

fs = require(rdquofsrdquo)

server

server = httpcreateServer(function (reqres)

var urlData

encode = rdquoutf8rdquo

filePath = rdquoviewexpress_get_example_formhtmlrdquo

action

urlData = urlparse(requrltrue)

action = urlDatapathname

reswriteHead(200 rdquoContent-Typerdquordquotexthtml charset=utf-8rdquo)

if (action === rdquoSignuprdquo)

user = urlDataquery

resend(rdquolth1gtrdquo + userusername + rdquo 歡迎您的加入 lth1gtltpgt 我們已經將會員啟用信寄至rdquo + useremail + rdquoltpgtrdquo)

else

fsreadFile(filePath encode function(err file)

reswrite(file)

resend()

)

)

serverlisten(3000)

consolelog(rsquoServer 跑起來了現在時間是rsquo + new Date())

66 Express GET應用範例 59

Nodejs中文電子書

67 Express POST應用範例

一開始準備基本的 html表單傳送內容以 POST方式form的 action屬性設定為 POST其餘 html內容與前一個範例應用相同

ltDOCTYPE htmlgt

lthtmlgt

ltheadgt

ltmeta http-equiv=rdquoContent-Typerdquo content=rdquotexthtml charset=utf8rdquogt

lttitlegtNodejs 菜鳥筆記 (1)lttitlegt

ltlink rel=rdquostylesheetrdquo href=rdquocssstylecssrdquo type=rdquotextcssrdquo media=rdquoallrdquo gt

ltheadgt

ltbodygt

lth1gtNodejs 菜鳥筆記-註冊lth1gt

ltform id=rdquosignuprdquo method=rdquoPOSTrdquo action=rdquohttplocalhost3000Signuprdquogt

ltlabelgt 使用者名稱ltlabelgtltinput type=rdquotextrdquo id=rdquousernamerdquo name=rdquousernamerdquo gtltbrgt

ltlabelgt 電子郵件ltlabelgtltinput type=rdquotextrdquo id=rdquoemailrdquo name=rdquoemailrdquo gtltbrgt

ltinput type=rdquosubmitrdquo value=rdquo 註冊我的帳號rdquo gtltbrgt

ltformgt

ltbodygt

lthtmlgt

nodejs的程式處理邏輯與前面 GET範例類似部分程式碼如下

qs = require(rsquoquerystringrsquo)

if (action === rdquoSignuprdquo)

formData = rsquorsquo

reqon(rdquodatardquo function (data)

formData += data

)

reqon(rdquoendrdquo function ()

user = qsparse(formData)

resend(rdquolth1gtrdquo + userusername + rdquo 歡迎您的加入 lth1gtltpgt 我們已經將會員啟用信寄至rdquo + useremail + rdquoltpgtrdquo)

)

60 6 Express介紹

Nodejs中文電子書

主要加入了rsquoquerystringrsquo這個moduel方便我們等一下解析由表單 POST回來的資料另外加入一個 formData的變數用來搜集待等一下表單回傳的資料前面的 GET範例我們只從 req拿出 url的資料這次要在利用req身上的事件處理

JavaScript 在訂閱事件時使用 addEventListener而 nodejs 使用的則是on這邊加上了監聽 data的事件會在瀏覽器傳送資料到Web Server時被執行參數是它所接收到的資料型態是字串

接著再增加 end的事件當瀏覽器的請求事件結束時它就會動作

由於瀏覽器使用 POST在上傳資料時會將資料一塊塊地上傳因為我們在監聽 data事件時透過 formData變數將它累加起來lt不過由於我們

上傳的資料很少一次就結束不過如果日後需要傳的是資料比較大的檔

案這個累加動作就很重要

當資料傳完就進到 end 事件中會用到 qsparse 來解析 formDataformData的內容是字串內容是

username=wordsmithampemail=wordsmith40somewhere

而 qsparse可以幫我們把這個 querystring轉成物件的格式也就是

username=wordsmithampemail=wordsmith40somewhere

一旦轉成物件並指定給 user之後其他的事情就和 GET方法時操作的一樣寫 response的表頭將內容回傳並將 userusername和 useremail代入到內容中

修改完成後接著執行 nodejs程式啟動 web server開啟瀏覽器進入表單測試看看POST的方式能否順利運作

完整程式碼如下

var http = require(rsquohttprsquo)

url = require(rsquourlrsquo)

fs = require(rdquofsrdquo)

qs = require(rsquoquerystringrsquo)

server

server = httpcreateServer(function (reqres)

var urlData

67 Express POST應用範例 61

Nodejs中文電子書

encode = rdquoutf8rdquo

filePath = rdquoviewexpress_post_example_formhtmlrdquo

formData

action

urlData = urlparse(requrltrue)

action = urlDatapathname

reswriteHead(200 rdquoContent-Typerdquordquotexthtml charset=utf-8rdquo)

if (action === rdquoSignuprdquo)

formData = rsquorsquo

reqon(rdquodatardquo function (data)

formData += data

)

reqon(rdquoendrdquo function ()

user = qsparse(formData)

resend(rdquolth1gtrdquo + userusername + rdquo 歡迎您的加入 lth1gtltpgt 我們已經將會員啟用信寄至rdquo + useremail + rdquoltpgtrdquo)

)

else

fsreadFile(filePath encode function(err file)

reswrite(file)

resend()

)

)

serverlisten(3000)

consolelog(rsquoServer 跑起來了現在時間是rsquo + new Date())

62 6 Express介紹

Nodejs中文電子書

68 Express AJAX應用範例

在 Nodejs要使用 Ajax傳送資料並且與之互動在接受資料的部份沒有太大的差別client端不是用 GET就是用 POST來傳資料重點在處理完後用 JSON格式回傳當然 Ajax不見得只傳 JSON格式有時是回傳一段 HTML碼不過後者對伺服器來說基本上就和前兩篇沒有差別了所以我們還是以回傳 JSON做為這一回的主題

這一回其實大多數的工作都會落在前端 Ajax上面前端要負責發送與接收資料並在接收資料後撤掉原先發送資料的表單並將取得的資料

改成 HTML格式之後放上頁面

首先先準備 HTML靜態頁面資料

ltDOCTYPE htmlgt

lthtmlgt

ltheadgt

lttitlegtNodejs 菜鳥筆記 (3)lttitlegt

ltlink rel=rdquostylesheetrdquo href=rdquocssstylecssrdquo type=rdquotextcssrdquo media=rdquoallrdquo gt

ltscript src=rdquohttpsajaxgoogleapiscomajaxlibsjquery171jqueryminjsrdquogtltscriptgt

ltheadgt

ltbodygt

lth1gtNodejs 菜鳥筆記-註冊 (用 Ajax)lth1gt

ltform id=rdquosignuprdquo action=rdquoSignuprdquo method=rdquoPOSTrdquogt

ltlabelgt 使用者名稱ltlabelgtltinput type=rdquotextrdquo id=rdquousernamerdquo name=rdquousernamerdquo gtltbrgt

ltlabelgt 電子郵件ltlabelgtltinput type=rdquotextrdquo id=rdquoemailrdquo name=rdquoemailrdquo gtltbrgt

ltinput type=rdquosubmitrdquo value=rdquo 註冊我的帳號rdquo gtltbrgt

ltformgt

ltdiv id=rdquoresultrdquogt

ltdivgt

ltscript type=rdquotextjavascriptrdquogt

$(function()$(rsquosignup input[type=rdquosubmitrdquo]rsquo)click(function(e)

var user =

userusername = $(rdquousernamerdquo)val()useremail = $(rdquoemailrdquo)val()

$post(rdquoSignuprdquouserfunction(data)greet(data)

)

68 Express AJAX應用範例 63

Nodejs中文電子書

epreventDefault()

)

var greet = function(msg)

var resultNode = $(rsquoresultrsquo)greeting = $(rsquolth2gtHirsquo+msgusername+rsquo 你的會員 id 是rsquo+ msgid +rsquo 我們會將會員啟動信寄至rsquo+msgemail+rsquolth2gtrsquo)

$(rsquosignuprsquo)hide()resultNodehtml(rdquordquo)

resultNodeappend(greeting)

)

ltscriptgt

ltbodygt

lthtmlgt

HTML頁面上準備了一個表單用來傳送註冊資料接著直接引用了Google CDN來載入 jQuery用來幫我們處理 Ajax的工作這次要傳送和接收的工作很大的變動都在 HTML頁面上的 JavaScript當中我們要做的事有 (相關 jQuery處理這邊不多做贅述指提起主要功能解說)

bull 用 jQUery取得 submit按鈕綁定它的 click動作

bull 取得表單 username和 email的值存放在 user這個物件中

bull 用 jQuery的 $post方法將 user的資料傳到 Server

bull 一旦成功取得資料後透過 greet這個 function組成回報給 user的訊息

bull 清空原本給使用者填資料的表單

bull 將 Server回傳的 usernameemail和 id這 3個資料組成回應的訊息

bull 將訊息放到原本表單的位置

經過以上的處理後一個 Ajax的表單的基本功能已經完成

接著進行 nodejs主要程式的編輯部分程式碼如下

var fs = require(rdquofsrdquo)

64 6 Express介紹

Nodejs中文電子書

qs = require(rsquoquerystringrsquo)

if (action === rdquoSignuprdquo)

formData = rsquorsquo

reqon(rdquodatardquo function (data)

formData += data

)

reqon(rdquoendrdquo function ()

var msg

user = qsparse(formData)

userid = rdquo123456rdquo

msg = JSONstringify(user)

reswriteHead(200 rdquoContent-Typerdquordquoapplicationjson charset=utf-8rdquordquoContent-Lengthrdquomsglength)

resend(msg)

)

這裡的程式和前面 POST範例基本上大同小異差別在

bull 幫 user的資料加上 id隨意存放一些文字進去讓 Server回傳的資料多於 Client端傳上來的不然會覺得 Server都沒做事

bull 增加了 msg 這個變數存放將 user 物件 JSON 文字化的結果JSONstringify這個轉換函式是 V8引擎所提供的如果你好奇的話

bull 大重點來了我們要告訴 Client端這次回傳的資料格式是 JSON所在 Content-type和 Content-Length要提供給 Client

Server很輕鬆就完成任務了最後進行程式測試啟動 nodejs主程式之後開啟瀏覽器就會看到表單填寫完畢按下送出就可以看到結果了

最後 nodejs本篇範例程式碼如下

var http = require(rsquohttprsquo)

url = require(rsquourlrsquo)

fs = require(rdquofsrdquo)

qs = require(rsquoquerystringrsquo)

server

68 Express AJAX應用範例 65

Nodejs中文電子書

server = httpcreateServer(function (reqres)

var urlData

encode = rdquoutf8rdquo

filePath = rdquoviewexpress_ajax_example_formhtmlrdquo

formData

action

urlData = urlparse(requrltrue)

action = urlDatapathname

if (action === rdquoSignuprdquo)

formData = rsquorsquo

reqon(rdquodatardquo function (data)

formData += data

)

reqon(rdquoendrdquo function ()

var msg

user = qsparse(formData)

userid = rdquo123456rdquo

msg = JSONstringify(user)

reswriteHead(200 rdquoContent-TyperdquordquoapplicationjsonrdquordquoContent-Lengthrdquomsglength)

resend(msg)

)

else

fsreadFile(filePath encode function(err file)

reswriteHead(200 rdquoContent-Typerdquordquotexthtml charset=utf-8rdquo)

reswrite(file)

resend()

)

)

serverlisten(3000)

66 6 Express介紹

Nodejs中文電子書

consolelog(rsquoServer 跑起來了現在時間是rsquo + new Date())

69 原始資料提供

bull [NodeJS初學者筆記 (1)-用 GET傳送資料] (httpithelpithomecomtwquestion10087402)

bull [NodeJS初學者筆記 (2)-用 POST傳送資料] (httpithelpithomecomtwquestion10087489)

bull [NodeJS 初學者筆記 (3)-用 Ajax 傳送資料] (httpithelpithomecomtwquestion10087627)

69 原始資料提供 67

Nodejs中文電子書

68 6 Express介紹

7

CoffeeScript

bull Spine

bull Hem

bull Spineapp

Letrsquos Have a Cup of CoffeeScript

bull es5-shim ECMAScript 5 compatibility shims for legacy JavaScript engines

ESnext

node-iform - A middleware for node-validator httpsgithubcomguileennode-iform

69

Nodejs中文電子書

70 7 CoffeeScript

8

製作一個 Hubot的 Plurk Adapter

81 應用事項提醒

此應用範例以CoffeeScript編寫主要程式架構採用Github釋放的Hubot專案以下有幾個主要注意事項請讀者注意

bull Hubot 一開始的架構除了內建的兩個 Adapter 之外其餘都要以Nodejs的Module方式才能運作

bull Hubot的 binhubot裡面寫著 npm install所以不管你怎麼改原始碼也不能改變第一點的狀況

其實上述都在討論同一件事情NodeJS的模組而且模組不能設定相對路徑之類的來安裝一定要包裝成 npm相符和格式才有辦法正確執行

82 建立 Adapter

首先需要準備兩個檔案

bull packagejson

bull plurkcoffee

71

Nodejs中文電子書

有這兩個就足以變成 NodeJS的模組了在開始 Coding之前先來設定一下 packagejson相依性

rdquonamerdquo rdquohubot-plurkrdquo

rdquoversionrdquo rdquo011rdquo

rdquomainrdquo rdquoplurkrdquo

rdquodependenciesrdquo

rdquohubotrdquo rdquogt=205rdquo

rdquooauthrdquo rdquordquo

rdquocronrdquordquordquo

這邊會使用到NodeJS的 cron模組(Module)而登入 Plurk需要OAuth標準授權才行所以也需要加載 OAuth模組

注意Hubot在讀取非內建模組時會自動在前面加上 hubot-的前置

83 建立 Robot跟 API

本篇應用基本上程式碼大部分參考 Hubot - Twitter Adapter來製作主要差異只有在使用 OAuth這個模組Twitter有 Streaming可用而 Plurk則得用 Comet方式來達到即時讀取

plurkcoffee程式碼

Robot = require(rdquohubotrdquo)robot()

Adapter = require(rdquohubtordquo)adapter()

EventEmitter = require(rdquoeventsrdquo)EventEmitter

oauth = require(rdquooauthrdquo)

cronJob = require(rdquocronrdquo)CronJob

class Plurk exntends Adapter

class PlurkStreaming exnteds EventEmitter

72 8 製作一個 Hubot的 Plurk Adapter

Nodejs中文電子書

先弄個基本架構主要 Class皆參考 Twitter Adapter命名方式接著再將 Plurk這個 Class增加幾個Method基本上只要有 run send reply就夠了而 run用來做初始化的部分

class Plurk entends Adapter

send (plurk id strings⋯) -gt

reply (plurk id strings⋯) -gt

run -gt

看起來有點東西接著來處理主要的 Plurk API結合部分

class PlurkStreaming extends EventEmitter

consuctor (options) -gt

plurk (callback) -gt

觀察河道

getChannel -gt

取得 Comet 網址

reply (plurk id message) -gt

回噗

acceptFriends -gt

接受好友

get (path callback) -gt

GET 請求

post (path body callback)-gt

POST 請求(其實是裝飾)

request (method path body callback)-gt

主要的 OAuth 請求

comet (server callback)-gt

噗浪的 Comet 傳回是 JavaScript Callback 要另外處理後才會變成 JSON

然後把注意力集中到 constructor上先把建構子弄好

constructor (options) -gt

super()

if optionskey and optionssecret and optionstoken and optionstoken secret

key = optionskey

secret = optionssecret

token = optionstoken

token secret = optionstoken secret

83 建立 Robot跟 API 73

Nodejs中文電子書

建立 OAuth 連接

consumer = new oauthOAuth(

rdquohttpwwwplurkcomOAuthrequest tokenrdquo

rdquohttpwwwplurkcomOAuthaccess tokenrdquo

key

secret

rdquo10rdquo

rdquohttpwwwplurkcomOAuthauthorizerdquo

rdquoHMAC-SHA1rdquo

)

domain = rdquowwwplurkcomrdquo

初始化取得 Comet 網址

do getChannel

else

throw new Error(rdquo 參數不足需要 Key Secret Token Token Secretrdquo)

接著來處理 request這個 method

request (method path body callback) -gt

記錄一下這次的 Request

consolelog(rdquohttpdomainpathrdquo)

Callback 這邊先不丟進去要用另一種方式處理

request = consumerget(rdquohttpdomainpathrdquo token token secret null)

requeston rdquoresponserdquo (res) -gt

reson rdquodatardquo (chunk) -gt

parseResponse(chunk+rsquorsquo callback)

reson rdquoendrdquo (data) -gt

consolelog rdquoEnd Request pathrdquo

reson rdquoerrorrdquo (data) -gt

consolelog rdquoError rdquo + data

requestend()

處理資料

parseResponse = (data callback) -gt

if datalength gt 0

用 TryCatch 避免處理 JSON 出錯導致整個中斷

try

callback null JSONparse(data)

catch err

74 8 製作一個 Hubot的 Plurk Adapter

Nodejs中文電子書

consolelog(rdquoError Parse JSONrdquo + data err)

繼續執行

callback null data ||

大致上就是這樣上面程式的架構已經將整個 Hubot Plurk Adapter完成因為在測試時竟然因為噗浪 Lag而沒讀到完整的 Comet資料然後造成程式異常為了避免這個問題發生因此需要加上為了完美呈現需要再

加上 Comet的處理所以要使用到 EventEmitter的功能

comet (server callback) -gt

在 Callback 裡面會找不到自身所以設定區域變數

self =

記錄一下這次的 Request

consolelog(rdquo[Comet] serverrdquo)

Callback 這邊先不丟進去要用另一種方式處理

request = consumerget(rdquohttpdomainpathrdquo token token secret null)

requeston rdquoresponserdquo (res) -gt

reson rdquodatardquo (chunk) -gt

parseResponse(chunk+rsquorsquo callback)

reson rdquoendrdquo (data) -gt

consolelog rdquoEnd Request pathrdquo

請求結束發出事件通知可以進行下一次請求

selfemit rdquonextPlurkrdquo

reson rdquoerrorrdquo (data) -gt

consolelog rdquoError rdquo + data

requestend()

處理資料

parseResponse = (data callback) -gt

if datalength gt 0

用 trycatch 避免失敗中斷

try

去掉 JavaScript 的 Callback

data = datamatch(CometChannelscriptCallback((+))s)

jsonData = rdquordquo

if data

83 建立 Robot跟 API 75

Nodejs中文電子書

jsonData = JSONparse(data[1])

else

如果沒有任何 Match 嘗試直接 parse

jsonData = JSONparse(data)

catch err

consolelog(rdquo[Comet] Errorrdquo data err)

用 TryCatch 避免處理 JSON 出錯導致整個中斷

try

只傳入 json 的 data 部分

callback null jsonDatadata

catch err

consolelog(rdquo[Comet]Error Parse JSONrdquo + data err)

繼續執行

callback null data ||

後面的 get跟 post就簡單多了

get (path callback) -gt

request(rdquoGETrdquo path null callback)

post (path body callback) -gt

request(rdquoPOSTrdquo path body callback)

接著處理取的 Comet網址的 getChannel

getChannel -gt

self =

get rdquoAPPRealtimegetUserChannelrdquo (error data) -gt

if error

檢查是否有 comet server

if datacomet server

selfchannel = datacomet server

如果沒有 Channel Ready 就嘗試連接會失敗

selfemit(rsquochannel readyrsquo)

那麼先來處理 Plurk Adaper好處理的部份

send (plruk id strings⋯)-gt

跟 Reply 一樣直接交給 reply 做

reply plurk id strings⋯

76 8 製作一個 Hubot的 Plurk Adapter

Nodejs中文電子書

reply (plurk id strings⋯) -gt

stringsforEach (message) =gt

botreply(plruk id message)

接著把 run處理好就可以上線運作摟

run -gt

self =

options =

key processenvHUBOT PLURK KEY

secret processenvHUBOT PLURK SECRET

token processenvHUBOT PLURK TOKEN

token secret processenvHUBOT PLURK TOKEN SECRET

創建剛剛的 API

bot = new PlurkStreaming(options)

依照 Twitter 的 new RobotTextMessage 會沒有反應所以參考 hubot-minecraft 的方式

r = robotconstructor

處理噗浪河道訊息

doPlurk = (data)-gt

檢查是否為回噗

if dataresponse

datacontent raw = dataresponsecontent raw

datauser id = dataresponseuser id

確定有噗浪 ID 跟訊息

if dataplurk id and datacontent raw

selfreceive new rTextMessage(dataplurk id datacontent raw)

取得 Comet Server 完成開始第一次 Comet 連接

boton rdquochannel readyrdquo () -gt

botplurk selfdoPlurk

上一次 Comet 完成繼續 Polling

boton rdquonextPlurkrdquo ()-gt

botplurk selfdoPlurk

定時接受好友邀請

do botacceptFriends

bot = bot

83 建立 Robot跟 API 77

Nodejs中文電子書

終於完成 AdapterHubot專案裡面的 scripts資料夾內是互動部分不需要像 Adapter如此大費周章處理只新增檔案並且設計好對白之後就會回噗了我開發用的機器人在此大家可以去跟他玩玩[httpplurkcomelct9620 bot](httpplurkcomelct9620 bot)

84 原始資料提供

bull [製 作 一 個 Hubot 的 噗 浪 Adapter](http revo-skillfrosttw blog20120318create-a-hubot-plurk-adapter)

78 8 製作一個 Hubot的 Plurk Adapter

9

Nodejs好用工具介紹

91 logio

Real-time log monitoring in your browser

httplogioorg

nodejs (gt=0412 lt=0611)

npm config set unsafe-perm true

npm install -g --prefix=usrlocal logio

logio server start

httplocalhost8998

79

Nodejs中文電子書

80 9 Nodejs好用工具介紹

10

精選文章

101 Express

bull 使用 NodeJS + Express從 GETPOST Request取值 [邀稿中]

bull link2 [邀稿中]

bull link3 [邀稿中]

81

Nodejs中文電子書

82 10 精選文章

11

參考資源

111 Nodejs書籍

bull Programming Nodejs April 2012 (est) Travis Swicegood Pragmatic Bookshelf

bull e Little Book on CoffeeScript January 2012 Alex MacCaw OrsquoReilly Media

bull Node for Front-End Developers January 2012 Garann Means OrsquoReilly Media

bull Building Hypermedia APIs with HTML5 and Node November 2011 MikeAmundsen OrsquoReilly Media

bull Node Web Development August 2011 David Herron PacktPub

bull CoffeeScript Accelerated JavaScript Development July 2011 Trevor BurnhamPragmatic Bookshelf

bull Getting Started with GEO CouchDB and Nodejs July 2011 Mick omp-son OrsquoReilly Media

bull What Is Node July 2011 Brett McLaughlin OrsquoReilly Media

bull Node Up and Running May 2011 Tom Hughes-Croucher Mike WilsonOrsquoReilly Media

83

Nodejs中文電子書

bull Hands-on Nodejs e Nodejs introduction and API reference May 2011 Pe-dro Teixeira LeanPub

bull e Node Beginner Book Manuel Kiessling

bull Mastering Nodejs visionmedia

112 Nodejs影音教學

bull e Node Sessions e Best of OSCON 2011 August 2011 OrsquoReilly Media(Video)

bull Tom Hughes-Croucher on Node March 2011 OrsquoReilly Media (Video)

113 Nodejs教學網站

bull Node Tuts

114 Nodejs課程

bull Introduction To Nodejs Van Nguyen CodeLesson

84 11 參考資源

Page 49: Nodejs Wiki

Nodejs中文電子書

Ubuntu Linux使用 apt-get安裝 npm

sudo apt-get install python-software-properties

sudo add-apt-repository ppagias-kay-leenpm

sudo apt-get update

sudo apt-get install npm

npm官方提供的安裝程式 installsh可以適用於大多數的 Linux系統使用這個安裝程式請先確認

1 系統已安裝 curl工具(請使用 curl --version查看版本訊息)

2 已安裝 Nodejs並且 PATH正確設置

3 Nodejs的版本必須大於 04x

以下為 npm提供的安裝指令

curl httpnpmjsorginstallsh | sh

安裝成功會看到如下訊息

installsh安裝成功的訊息

npm10105 homeUSERNAMElocalnodelibnode_modulesnpm

It worked

安裝於Mac OS X

建議採用與 Nodejs相同的方式進行 npm的安裝例如使用MacPorts安裝 Nodejs就同樣使用MacPorts安裝 npm這樣對日後的維護才會更方便容易

使用 MacPorts安裝 npm是本書比較建議的方式它可以讓 npm的安裝移除及更新工作自動化將會幫助開發者節省寶貴時間

51 安裝 NPM 43

Nodejs中文電子書

安裝MacPorts的提示

在MacPorts網站可以取得 OS X系統版本對應的安裝程式(例如 106或 107)httpwwwmacportsorg安裝過程會詢問系統管理者密碼使用預設的選項完成安裝即可安

裝MacPorts之後在終端機執行 port -v將會看到MacPorts的版本訊息

安裝 npm之前先更新MacPorts的套件清單以確保安裝的 npm是最新版本

sudo port -d selfupdate

接著安裝 npm

sudo port install npm

若讀者的 Nodejs並非使用MacPorts安裝則不建議使用MacPorts安裝npm因為 MacPorts會自動檢查並安裝相依套件而 npm相依 nodejs所以MacPorts也會一併將 nodejs套件安裝造成先前讀者使用其它方式安裝的 nodejs被覆蓋

讀者可以先使用 MacPorts安裝 curl(sudo port install curl)

再參考 Linux的 installsh安裝方式即可使用 npm官方提供的安裝程式

NPM安裝後測試

npm是指令列工具(command-line tool)使用時請先打開系統的文字終端機工具

測試 npm安裝與設定是否正確請輸入指令如下

npm -v

或是

npm --version

如果 npm已經正確安裝設定就會顯示版本訊息

44 5 NPM套件管理工具

Nodejs中文電子書

執行結果(範例)

110-2

52 使用 NPM安裝套件

npm目前擁有超過 6000種套件(packages)可以在 npm registry使用關鍵字搜尋套件

httpsearchnpmjsorg

舉例來說在關鍵字欄位輸入「coffee-script」下方的清單就會自動列出包含 coffee-script關鍵字的套件

接著我們回到終端機模式的操作npm的指令工具本身就可以完成套

件搜尋的任務

例如以下的指令同樣可以找出 coffee-script相關套件

npm search coffee-script

以下是搜尋結果的參考畫面

52 使用 NPM安裝套件 45

Nodejs中文電子書

找到需要的套件後(例如 express)即可使用以下指令安裝

npm install coffee-script

值得注意的一點是使用 npm install會將指定的套件安裝在工

作目錄(Working Directory)的 node modules資料夾下

以Windows為例如果執行 npm install的目錄位於

Cproject1

那麼 npm將會自動建立一個 node modules的子目錄(如果不存在)

Cproject1node modules

並且將下載的套件放置於這個子目錄例如

Cproject1node modulescoffee-script

這個設計讓專案可以個別管理相依的套件並且可以在專案佈署或發

行時將這些套件(位於 node modules)一併打包方便其它專案的使用者不必再重新下載套件

這個 npm install的預設安裝模式為 local(本地)只會變更當前專案的資料夾不會影響系統

另一種安裝模式稱為 global(全域)這種模式會將套件安裝到系統資

料夾也就是 npm安裝路徑的 node modules資料夾例如

CProgram Filesnodejsnode modules

46 5 NPM套件管理工具

Nodejs中文電子書

是否要使用全域安裝可以依照套件是否提供新指令來判斷舉例來說express 套件提供 express 這個指令而 coffee-script 則提供 coffee

指令

在 local 安 裝 模 式 中這 些 指 令 的 程 式 檔 案會 被 安 裝 到node modules的 bin這個隱藏資料夾下除非將bin的路徑加入 PATH環境變數否則要執行這些指令將會相當不便

為了方便指令的執行我們可以在 npm install 加上 -g 或 --

global參數啟用 global安裝模式例如

npm install -g coffee-script

npm install -g express

使用 global安裝模式需要注意執行權限的問題若權限不足可能會出現類似以下的錯誤訊息

npm ERR Error EACCES permission denied rsquorsquo

npm ERR

npm ERR Please try running this command again as rootAdministrator

要獲得足夠得執行權限請參考以下說明

bull Windows 7 或 2008 以上在「命令提示字元」的捷徑按右鍵選擇「以系統管理員身分執行」執行 npm指令時就會具有 Administrator身分

bull Mac OS X或 Linux系統可以使用 sudo指令例如

sudo npm install -g express

bull Linux系統可以使用 root權限登入或是以「sudo su -」切換成 root身分(使用 root權限操作系統相當危險因此並不建議使用這種方式)

若加上 -g參數使用 npm install -g coffee-script完成安裝

後就可以在終端機執行 coffee指令例如

coffee -v

52 使用 NPM安裝套件 47

Nodejs中文電子書

執行結果(範例)

CoffeeScript version 120

53 套件的更新及維護

除了前一節說明的 search 及 install 用法npm 還提供其他許多指令(commands)

使用 npm help可以查詢可用的指令

npm help

執行結果(部分)

where ltcommandgt is one of

adduser apihelp author bin bugs c cache completion

config deprecate docs edit explore faq find get

help help-search home i info init install la link

list ll ln login ls outdated owner pack prefix

prune publish r rb rebuild remove restart rm root

run-script s se search set show star start stop

submodule tag test un uninstall unlink unpublish

unstar up update version view whoami

使用 npm help command可以查詢指令的詳細用法例如

npm help list

接下來本節要介紹開發過程常用的 npm指令

使用 list可以列出已安裝套件

npm list

48 5 NPM套件管理工具

Nodejs中文電子書

執行結果(範例)

-- coffee-script120

- express256

- connect185

| -- formidable108

-- mime124

-- mkdirp007

-- qs041

檢視某個套件的詳細資訊例如

npm show express

升級所有套件(如果該套件已發佈更新版本)

npm update

升級指定的套件

npm update express

移除指定的套件

npm uninstall express

54 使用 packagejson

對於正式的 Nodejs專案可以建立一個命名為 packagejson的設

定檔(純文字格式)檔案內容參考範例如下

54 使用 packagejson 49

Nodejs中文電子書

packagejson(範例)

rdquonamerdquo rdquoapplication-namerdquo

rdquoversionrdquo rdquo001rdquo

rdquoprivaterdquo true

rdquodependenciesrdquo

rdquoexpressrdquo rdquo255rdquo

rdquocoffee-scriptrdquo rdquolatestrdquo

rdquomongooserdquo rdquogt= 253rdquo

其中 name與 version依照專案的需求設置

需要注意的是 dependencies的設定它用於指定專案相依的套件名

稱及版本

bull rdquoexpressrdquo rdquo255rdquo

代表此專案相依版本 255的 express套件

bull rdquocoffee-scriptrdquo rdquolatestrdquo

使用最新版的 coffee-script套件(每次更新都會檢查新版)

bull rdquomongooserdquo rdquogt= 253rdquo

使用版本大於 253的 mongoose套件

假設某個套件的新版可能造成專案無法正常運作就必須指定套件的

版本避免專案的程式碼來不及更新以相容新版套件通常在開發初期的

專案需要盡可能維持新套件的相容性(以取得套件的更新或修正)可以

用「gt=」設定最低相容的版本或是使用「latest」設定永遠保持最新

套件

50 5 NPM套件管理工具

6

Express介紹

在前面的 nodejs基礎當中介紹許多許多開設 http的使用方法及介紹以及許多基本的 nodejs基本應用

接下來要介紹一個套件稱為 express [Express](httpexpressjscom)這個套件主要幫忙解決許多 nodejs http server所需要的基本服務讓開發 httpservice變得更為容易不需要像之前需要透過層層模組(module)才有辦法開始編寫自己的程式

這個套件是由 TJ Holowaychuk 製作而成的套件裡面包含基本的路由處理 (route)http資料處理(GETPOSTPUT)另外還與樣板套件(jshtml template engine)搭配同時也可以處理許多複雜化的問題

61 Express安裝

安裝方式十分簡單只要透過之前介紹的 NPM就可以使用簡單的指令安裝指令如下

這邊建議需要將此套件安裝成為全域模組方便日後使用

51

Nodejs中文電子書

62 Express基本操作

express的使用也十分簡單先來建立一個基本的 hello world

var app = require(rsquoexpressrsquo)createServer()

port = 1337

applisten(port)

appget(rsquorsquo function(req res)

ressend(rsquohello worldrsquo)

)

consolelog(rsquostart express servernrsquo)

可以從上面的程式碼發現基本操作與 nodejs http的建立方式沒有太大差異主要差在當我們設定路由時可以直接透過 appget方式設定回應與接受方式

63 Express路由處理

Express對於 http服務上有許多包裝讓開發者使用及設定上更為方便例如有幾個路由設定那我們就統一藉由 appget來處理

Create http server

appget(rsquorsquo function(req res)

ressend(rsquohello worldrsquo)

)

appget(rsquotestrsquo function(req res)

ressend(rsquotest renderrsquo)

)

appget(rsquouserrsquo function(req res)

ressend(rsquouser pagersquo)

)

52 6 Express介紹

Nodejs中文電子書

如上面的程式碼所表示appget可以帶入兩個參數第一個是路徑名稱設定第二個為回應函式 (call back function)回應函式裡面就如同之前的 createServer方法裡面包含 requestresponse兩個物件可供使用使用者就可以透過瀏覽器輸入不同的 url切換到不同的頁面顯示不同的結果

路由設定上也有基本的配對方式讓使用者從瀏覽器輸入的網址可以

是一個變數只要符合型態就可以有對應的頁面產出例如

Create http server

appget(rsquouseridrsquo function(req res)

ressend(rsquouser rsquo + reqparamsid)

)

appget(rsquonumberrsquo function(req res)

ressend(rsquonumber rsquo + reqparamsnumber)

)

裡 面 使 用 到number 從 網 址 輸 入 之 後 就 可 以 直 接 使 用reqparamsnumber 取得所輸入的資料變成 url 參數使用當然前面也是可以加上路徑的設定userid在瀏覽器上路徑必須符合userxxx透

過 reqparamsid就可以取到 xxx這個字串值

另外express參數處理也提供了路由參數配對處理也可以透過正規表示法作為參數設定

var app = require(rsquoexpressrsquo)createServer()

port = 1337

applisten(port)

appget(^ip((d23)((d23))((d23))((d23))) function(req res)

ressend(reqparams)

)

上面程式碼可以發現後面路由設定的型態是正規表示法裡面設定

格式為ip之後必須要加上 ip型態才會符合資料格式同時取得 ip資料已經由正規表示法將資料做分群因此可以取得 ip的四個數字

此程式執行之後可以透過瀏覽器測試輸入網址為 localhost3000ip25525510010可以從頁面獲得資料

63 Express路由處理 53

Nodejs中文電子書

此章節全部範例程式碼如下

overview

author Caesar Chi

blog clonnblogspotcom

version 20120226

create server

var app = require(rsquoexpressrsquo)createServer()

port = 1337

applisten(port)

normal style

appget(rsquorsquo function(req res)

ressend(rsquohello worldrsquo)

)

appget(rsquotestrsquo function(req res)

ressend(rsquotest renderrsquo)

)

parameter style

appget(rsquouseridrsquo function(req res)

ressend(rsquouser rsquo + reqparamsid)

)

appget(rsquonumberrsquo function(req res)

ressend(rsquonumber rsquo + reqparamsnumber)

)

REGX style

appget(^ip((d23)((d23))((d23))) function(req res)

ressend(reqparams)

)

appget(rsquorsquo function(req res)

ressend(rsquoPage not foundrsquo 404)

54 6 Express介紹

Nodejs中文電子書

)

consolelog(rsquostart express servernrsquo)

64 Express middleware

Express 裡面有一個十分好用的應用概念稱為 middleware可以透過middleware做出複雜的效果同時上面也有介紹 next方法參數傳遞就是靠 middleware的概念來傳遞參數讓開發者可以明確的控制程式邏輯

create http server

appuse(expressbodyParser())

appuse(expressmethodOverride())

appuse(expresssession())

上面都是一種 middleware的使用方式透過 appuse方式裡面載入函式執行方法回應函式會包含三個基本參數responserequestnext其中next表示下一個 middleware執行函式同時會自動將預設三個參數繼續帶往下個函式執行底下有個實驗

上面的片段程式執行後開啟瀏覽器連結上 localhost1337會發現伺服器回應結果順序如下

first middle ware

second middle ware

execute middle ware

end middleware function

從上面的結果可以得知剛才設定的 middleware都生效了在 appuse設定的 middleware是所有 url皆會執行方法如果有指定特定方法就可以使用 appget的 middleware設定在 appget函式的第二個參數就可以帶入函式或者是匿名函式只要函式裡面最後會接受 request response next這三個參數同時也有正確指定 next函式的執行時機最後都會執行到最後一個方法當然開發者也可以評估程式邏輯要執行到哪一個階段讓邏輯

可以更為分明

64 Express middleware 55

Nodejs中文電子書

65 Express路由應用

在實際開發上可能會遇到需要使用參數等方式混和變數一起使用

express裡面提供了一個很棒的處理方法 appall這個方式可以先採用基本路由配對再將設定為每個不同的處理方式開發者可以透過這個方式簡

化自己的程式邏輯

overview

author

version 20120226

create server

var app = require(rsquoexpressrsquo)createServer()

port = 1337

users = [

name rsquoClonnrsquo

name rsquoChirsquo

]

applisten(port)

appall(rsquouseridoprsquo function(req res next)

requser = users[reqparamsid]

if (requser)

next()

else

next(new Error(rsquocannot find user rsquo + reqparamsid))

)

appget(rsquouseridrsquo function(req res)

ressend(rsquoviewing rsquo + requsername)

)

appget(rsquouserideditrsquo function(req res)

ressend(rsquoediting rsquo + requsername)

)

56 6 Express介紹

Nodejs中文電子書

appget(rsquouseriddeletersquo function(req res)

ressend(rsquodeleting rsquo + requsername)

)

appget(rsquorsquo function(req res)

ressend(rsquoPage not foundrsquo 404)

)

consolelog(rsquostart express servernrsquo)

內部宣告一組預設的使用者分別給予名稱設定藉由 appall這個方法可以先將路由雛形建立再接下來設定 appget的路徑格式只要符合格式就會分配進入對應的方法中像上面的程式當中如果使用者輸入路徑為user0除了執行 appall程式之後執行 next方法就會對應到路徑設定為userid的這個方法當中如果使用者輸入路徑為user0edit就會執行到useridedit的對應方法

66 Express GET應用範例

我們準備一個使用 GET方法傳送資料的表單

ltDOCTYPE htmlgt

lthtmlgt

ltheadgt

ltmeta http-equiv=rdquoContent-Typerdquo content=rdquotexthtml charset=utf8rdquogt

lttitlegtNodejs 菜鳥筆記 (1)lttitlegt

ltlink rel=rdquostylesheetrdquo href=rdquocssstylecssrdquo type=rdquotextcssrdquo media=rdquoallrdquo gt

ltheadgt

ltbodygt

lth1gtNodejs 菜鳥筆記-註冊lth1gt

ltform id=rdquosignuprdquo method=rdquoGETrdquo action=rdquohttplocalhost3000Signuprdquogt

ltlabelgt 使用者名稱ltlabelgtltinput type=rdquotextrdquo id=rdquousernamerdquo name=rdquousernamerdquo gtltbrgt

ltlabelgt 電子郵件ltlabelgtltinput type=rdquotextrdquo id=rdquoemailrdquo name=rdquoemailrdquo gtltbrgt

ltinput type=rdquosubmitrdquo value=rdquo 註冊我的帳號rdquo gtltbrgt

ltformgt

ltbodygt

lthtmlgt

66 Express GET應用範例 57

Nodejs中文電子書

這個表單沒有什麼特別的地方我們只需要看第 9 行form 使用的method是 GET然後 action是rdquohttplocalhost3000Signupldquo等一下我們要來撰寫Signup這個 URL Path的處理程式

處理 Signup行為

我們知道所謂的 GET方法會透過 URL來把表單的值給帶過去以上面的表單來說到時候 URL會以這樣的形式傳遞

httplocalhost3000Signupusername=xxxampemail=xxx

所以要能處理這樣的資料必須有以下功能

bull 解析 URL

bull 辨別動作是 Signup

bull 解析出 username和 email

一旦能取得 username和 email的值程式就能加以應用了

處理 Signup的程式碼雛形

load module

var url = require(rsquourlrsquo)

urlData = urlparse(requrltrue)

action = urlDatapathname

reswriteHead(200 rdquoContent-Typerdquordquotexthtml charset=utf-8rdquo)

if (action === rdquoSignuprdquo)

user = urlDataquery

resend(rdquolth1gtrdquo + userusername + rdquo 歡迎您的加入 lth1gtltpgt 我們已經將會員啟用信寄至rdquo + useremail + rdquoltpgtrdquo)

首先需要加載 url module它是用來協助我們解析 URL的模組接著使用 urlparse方法第一個傳入 url字串變數也就是 requrl另外第二個參數的用意是設為 ture則引進 querystring模組來協助處理預設是 false它影響到的是 urlDataquery設為 true會傳回物件不然就只是一般的字串urlparse會將字串內容整理成一個物件我們把它指定給 urlData

action變數作為記錄 pathname這是我們稍後要來判斷目前網頁的動作是什麼接著先將 html表頭資訊 (Header)準備好再來判斷路徑邏輯如

58 6 Express介紹

Nodejs中文電子書

果是 Signup這個動作就把 urlDataquery裡的資料指定給 user然後輸出userusername和 useremail把使用者從表單註冊的資料顯示於頁面中

最後進行程式測試啟動 nodejs主程式之後開啟瀏覽器就會看到表單填寫完畢按下送出就可以看到結果了

完整 nodejs程式碼如下

var http = require(rsquohttprsquo)

url = require(rsquourlrsquo)

fs = require(rdquofsrdquo)

server

server = httpcreateServer(function (reqres)

var urlData

encode = rdquoutf8rdquo

filePath = rdquoviewexpress_get_example_formhtmlrdquo

action

urlData = urlparse(requrltrue)

action = urlDatapathname

reswriteHead(200 rdquoContent-Typerdquordquotexthtml charset=utf-8rdquo)

if (action === rdquoSignuprdquo)

user = urlDataquery

resend(rdquolth1gtrdquo + userusername + rdquo 歡迎您的加入 lth1gtltpgt 我們已經將會員啟用信寄至rdquo + useremail + rdquoltpgtrdquo)

else

fsreadFile(filePath encode function(err file)

reswrite(file)

resend()

)

)

serverlisten(3000)

consolelog(rsquoServer 跑起來了現在時間是rsquo + new Date())

66 Express GET應用範例 59

Nodejs中文電子書

67 Express POST應用範例

一開始準備基本的 html表單傳送內容以 POST方式form的 action屬性設定為 POST其餘 html內容與前一個範例應用相同

ltDOCTYPE htmlgt

lthtmlgt

ltheadgt

ltmeta http-equiv=rdquoContent-Typerdquo content=rdquotexthtml charset=utf8rdquogt

lttitlegtNodejs 菜鳥筆記 (1)lttitlegt

ltlink rel=rdquostylesheetrdquo href=rdquocssstylecssrdquo type=rdquotextcssrdquo media=rdquoallrdquo gt

ltheadgt

ltbodygt

lth1gtNodejs 菜鳥筆記-註冊lth1gt

ltform id=rdquosignuprdquo method=rdquoPOSTrdquo action=rdquohttplocalhost3000Signuprdquogt

ltlabelgt 使用者名稱ltlabelgtltinput type=rdquotextrdquo id=rdquousernamerdquo name=rdquousernamerdquo gtltbrgt

ltlabelgt 電子郵件ltlabelgtltinput type=rdquotextrdquo id=rdquoemailrdquo name=rdquoemailrdquo gtltbrgt

ltinput type=rdquosubmitrdquo value=rdquo 註冊我的帳號rdquo gtltbrgt

ltformgt

ltbodygt

lthtmlgt

nodejs的程式處理邏輯與前面 GET範例類似部分程式碼如下

qs = require(rsquoquerystringrsquo)

if (action === rdquoSignuprdquo)

formData = rsquorsquo

reqon(rdquodatardquo function (data)

formData += data

)

reqon(rdquoendrdquo function ()

user = qsparse(formData)

resend(rdquolth1gtrdquo + userusername + rdquo 歡迎您的加入 lth1gtltpgt 我們已經將會員啟用信寄至rdquo + useremail + rdquoltpgtrdquo)

)

60 6 Express介紹

Nodejs中文電子書

主要加入了rsquoquerystringrsquo這個moduel方便我們等一下解析由表單 POST回來的資料另外加入一個 formData的變數用來搜集待等一下表單回傳的資料前面的 GET範例我們只從 req拿出 url的資料這次要在利用req身上的事件處理

JavaScript 在訂閱事件時使用 addEventListener而 nodejs 使用的則是on這邊加上了監聽 data的事件會在瀏覽器傳送資料到Web Server時被執行參數是它所接收到的資料型態是字串

接著再增加 end的事件當瀏覽器的請求事件結束時它就會動作

由於瀏覽器使用 POST在上傳資料時會將資料一塊塊地上傳因為我們在監聽 data事件時透過 formData變數將它累加起來lt不過由於我們

上傳的資料很少一次就結束不過如果日後需要傳的是資料比較大的檔

案這個累加動作就很重要

當資料傳完就進到 end 事件中會用到 qsparse 來解析 formDataformData的內容是字串內容是

username=wordsmithampemail=wordsmith40somewhere

而 qsparse可以幫我們把這個 querystring轉成物件的格式也就是

username=wordsmithampemail=wordsmith40somewhere

一旦轉成物件並指定給 user之後其他的事情就和 GET方法時操作的一樣寫 response的表頭將內容回傳並將 userusername和 useremail代入到內容中

修改完成後接著執行 nodejs程式啟動 web server開啟瀏覽器進入表單測試看看POST的方式能否順利運作

完整程式碼如下

var http = require(rsquohttprsquo)

url = require(rsquourlrsquo)

fs = require(rdquofsrdquo)

qs = require(rsquoquerystringrsquo)

server

server = httpcreateServer(function (reqres)

var urlData

67 Express POST應用範例 61

Nodejs中文電子書

encode = rdquoutf8rdquo

filePath = rdquoviewexpress_post_example_formhtmlrdquo

formData

action

urlData = urlparse(requrltrue)

action = urlDatapathname

reswriteHead(200 rdquoContent-Typerdquordquotexthtml charset=utf-8rdquo)

if (action === rdquoSignuprdquo)

formData = rsquorsquo

reqon(rdquodatardquo function (data)

formData += data

)

reqon(rdquoendrdquo function ()

user = qsparse(formData)

resend(rdquolth1gtrdquo + userusername + rdquo 歡迎您的加入 lth1gtltpgt 我們已經將會員啟用信寄至rdquo + useremail + rdquoltpgtrdquo)

)

else

fsreadFile(filePath encode function(err file)

reswrite(file)

resend()

)

)

serverlisten(3000)

consolelog(rsquoServer 跑起來了現在時間是rsquo + new Date())

62 6 Express介紹

Nodejs中文電子書

68 Express AJAX應用範例

在 Nodejs要使用 Ajax傳送資料並且與之互動在接受資料的部份沒有太大的差別client端不是用 GET就是用 POST來傳資料重點在處理完後用 JSON格式回傳當然 Ajax不見得只傳 JSON格式有時是回傳一段 HTML碼不過後者對伺服器來說基本上就和前兩篇沒有差別了所以我們還是以回傳 JSON做為這一回的主題

這一回其實大多數的工作都會落在前端 Ajax上面前端要負責發送與接收資料並在接收資料後撤掉原先發送資料的表單並將取得的資料

改成 HTML格式之後放上頁面

首先先準備 HTML靜態頁面資料

ltDOCTYPE htmlgt

lthtmlgt

ltheadgt

lttitlegtNodejs 菜鳥筆記 (3)lttitlegt

ltlink rel=rdquostylesheetrdquo href=rdquocssstylecssrdquo type=rdquotextcssrdquo media=rdquoallrdquo gt

ltscript src=rdquohttpsajaxgoogleapiscomajaxlibsjquery171jqueryminjsrdquogtltscriptgt

ltheadgt

ltbodygt

lth1gtNodejs 菜鳥筆記-註冊 (用 Ajax)lth1gt

ltform id=rdquosignuprdquo action=rdquoSignuprdquo method=rdquoPOSTrdquogt

ltlabelgt 使用者名稱ltlabelgtltinput type=rdquotextrdquo id=rdquousernamerdquo name=rdquousernamerdquo gtltbrgt

ltlabelgt 電子郵件ltlabelgtltinput type=rdquotextrdquo id=rdquoemailrdquo name=rdquoemailrdquo gtltbrgt

ltinput type=rdquosubmitrdquo value=rdquo 註冊我的帳號rdquo gtltbrgt

ltformgt

ltdiv id=rdquoresultrdquogt

ltdivgt

ltscript type=rdquotextjavascriptrdquogt

$(function()$(rsquosignup input[type=rdquosubmitrdquo]rsquo)click(function(e)

var user =

userusername = $(rdquousernamerdquo)val()useremail = $(rdquoemailrdquo)val()

$post(rdquoSignuprdquouserfunction(data)greet(data)

)

68 Express AJAX應用範例 63

Nodejs中文電子書

epreventDefault()

)

var greet = function(msg)

var resultNode = $(rsquoresultrsquo)greeting = $(rsquolth2gtHirsquo+msgusername+rsquo 你的會員 id 是rsquo+ msgid +rsquo 我們會將會員啟動信寄至rsquo+msgemail+rsquolth2gtrsquo)

$(rsquosignuprsquo)hide()resultNodehtml(rdquordquo)

resultNodeappend(greeting)

)

ltscriptgt

ltbodygt

lthtmlgt

HTML頁面上準備了一個表單用來傳送註冊資料接著直接引用了Google CDN來載入 jQuery用來幫我們處理 Ajax的工作這次要傳送和接收的工作很大的變動都在 HTML頁面上的 JavaScript當中我們要做的事有 (相關 jQuery處理這邊不多做贅述指提起主要功能解說)

bull 用 jQUery取得 submit按鈕綁定它的 click動作

bull 取得表單 username和 email的值存放在 user這個物件中

bull 用 jQuery的 $post方法將 user的資料傳到 Server

bull 一旦成功取得資料後透過 greet這個 function組成回報給 user的訊息

bull 清空原本給使用者填資料的表單

bull 將 Server回傳的 usernameemail和 id這 3個資料組成回應的訊息

bull 將訊息放到原本表單的位置

經過以上的處理後一個 Ajax的表單的基本功能已經完成

接著進行 nodejs主要程式的編輯部分程式碼如下

var fs = require(rdquofsrdquo)

64 6 Express介紹

Nodejs中文電子書

qs = require(rsquoquerystringrsquo)

if (action === rdquoSignuprdquo)

formData = rsquorsquo

reqon(rdquodatardquo function (data)

formData += data

)

reqon(rdquoendrdquo function ()

var msg

user = qsparse(formData)

userid = rdquo123456rdquo

msg = JSONstringify(user)

reswriteHead(200 rdquoContent-Typerdquordquoapplicationjson charset=utf-8rdquordquoContent-Lengthrdquomsglength)

resend(msg)

)

這裡的程式和前面 POST範例基本上大同小異差別在

bull 幫 user的資料加上 id隨意存放一些文字進去讓 Server回傳的資料多於 Client端傳上來的不然會覺得 Server都沒做事

bull 增加了 msg 這個變數存放將 user 物件 JSON 文字化的結果JSONstringify這個轉換函式是 V8引擎所提供的如果你好奇的話

bull 大重點來了我們要告訴 Client端這次回傳的資料格式是 JSON所在 Content-type和 Content-Length要提供給 Client

Server很輕鬆就完成任務了最後進行程式測試啟動 nodejs主程式之後開啟瀏覽器就會看到表單填寫完畢按下送出就可以看到結果了

最後 nodejs本篇範例程式碼如下

var http = require(rsquohttprsquo)

url = require(rsquourlrsquo)

fs = require(rdquofsrdquo)

qs = require(rsquoquerystringrsquo)

server

68 Express AJAX應用範例 65

Nodejs中文電子書

server = httpcreateServer(function (reqres)

var urlData

encode = rdquoutf8rdquo

filePath = rdquoviewexpress_ajax_example_formhtmlrdquo

formData

action

urlData = urlparse(requrltrue)

action = urlDatapathname

if (action === rdquoSignuprdquo)

formData = rsquorsquo

reqon(rdquodatardquo function (data)

formData += data

)

reqon(rdquoendrdquo function ()

var msg

user = qsparse(formData)

userid = rdquo123456rdquo

msg = JSONstringify(user)

reswriteHead(200 rdquoContent-TyperdquordquoapplicationjsonrdquordquoContent-Lengthrdquomsglength)

resend(msg)

)

else

fsreadFile(filePath encode function(err file)

reswriteHead(200 rdquoContent-Typerdquordquotexthtml charset=utf-8rdquo)

reswrite(file)

resend()

)

)

serverlisten(3000)

66 6 Express介紹

Nodejs中文電子書

consolelog(rsquoServer 跑起來了現在時間是rsquo + new Date())

69 原始資料提供

bull [NodeJS初學者筆記 (1)-用 GET傳送資料] (httpithelpithomecomtwquestion10087402)

bull [NodeJS初學者筆記 (2)-用 POST傳送資料] (httpithelpithomecomtwquestion10087489)

bull [NodeJS 初學者筆記 (3)-用 Ajax 傳送資料] (httpithelpithomecomtwquestion10087627)

69 原始資料提供 67

Nodejs中文電子書

68 6 Express介紹

7

CoffeeScript

bull Spine

bull Hem

bull Spineapp

Letrsquos Have a Cup of CoffeeScript

bull es5-shim ECMAScript 5 compatibility shims for legacy JavaScript engines

ESnext

node-iform - A middleware for node-validator httpsgithubcomguileennode-iform

69

Nodejs中文電子書

70 7 CoffeeScript

8

製作一個 Hubot的 Plurk Adapter

81 應用事項提醒

此應用範例以CoffeeScript編寫主要程式架構採用Github釋放的Hubot專案以下有幾個主要注意事項請讀者注意

bull Hubot 一開始的架構除了內建的兩個 Adapter 之外其餘都要以Nodejs的Module方式才能運作

bull Hubot的 binhubot裡面寫著 npm install所以不管你怎麼改原始碼也不能改變第一點的狀況

其實上述都在討論同一件事情NodeJS的模組而且模組不能設定相對路徑之類的來安裝一定要包裝成 npm相符和格式才有辦法正確執行

82 建立 Adapter

首先需要準備兩個檔案

bull packagejson

bull plurkcoffee

71

Nodejs中文電子書

有這兩個就足以變成 NodeJS的模組了在開始 Coding之前先來設定一下 packagejson相依性

rdquonamerdquo rdquohubot-plurkrdquo

rdquoversionrdquo rdquo011rdquo

rdquomainrdquo rdquoplurkrdquo

rdquodependenciesrdquo

rdquohubotrdquo rdquogt=205rdquo

rdquooauthrdquo rdquordquo

rdquocronrdquordquordquo

這邊會使用到NodeJS的 cron模組(Module)而登入 Plurk需要OAuth標準授權才行所以也需要加載 OAuth模組

注意Hubot在讀取非內建模組時會自動在前面加上 hubot-的前置

83 建立 Robot跟 API

本篇應用基本上程式碼大部分參考 Hubot - Twitter Adapter來製作主要差異只有在使用 OAuth這個模組Twitter有 Streaming可用而 Plurk則得用 Comet方式來達到即時讀取

plurkcoffee程式碼

Robot = require(rdquohubotrdquo)robot()

Adapter = require(rdquohubtordquo)adapter()

EventEmitter = require(rdquoeventsrdquo)EventEmitter

oauth = require(rdquooauthrdquo)

cronJob = require(rdquocronrdquo)CronJob

class Plurk exntends Adapter

class PlurkStreaming exnteds EventEmitter

72 8 製作一個 Hubot的 Plurk Adapter

Nodejs中文電子書

先弄個基本架構主要 Class皆參考 Twitter Adapter命名方式接著再將 Plurk這個 Class增加幾個Method基本上只要有 run send reply就夠了而 run用來做初始化的部分

class Plurk entends Adapter

send (plurk id strings⋯) -gt

reply (plurk id strings⋯) -gt

run -gt

看起來有點東西接著來處理主要的 Plurk API結合部分

class PlurkStreaming extends EventEmitter

consuctor (options) -gt

plurk (callback) -gt

觀察河道

getChannel -gt

取得 Comet 網址

reply (plurk id message) -gt

回噗

acceptFriends -gt

接受好友

get (path callback) -gt

GET 請求

post (path body callback)-gt

POST 請求(其實是裝飾)

request (method path body callback)-gt

主要的 OAuth 請求

comet (server callback)-gt

噗浪的 Comet 傳回是 JavaScript Callback 要另外處理後才會變成 JSON

然後把注意力集中到 constructor上先把建構子弄好

constructor (options) -gt

super()

if optionskey and optionssecret and optionstoken and optionstoken secret

key = optionskey

secret = optionssecret

token = optionstoken

token secret = optionstoken secret

83 建立 Robot跟 API 73

Nodejs中文電子書

建立 OAuth 連接

consumer = new oauthOAuth(

rdquohttpwwwplurkcomOAuthrequest tokenrdquo

rdquohttpwwwplurkcomOAuthaccess tokenrdquo

key

secret

rdquo10rdquo

rdquohttpwwwplurkcomOAuthauthorizerdquo

rdquoHMAC-SHA1rdquo

)

domain = rdquowwwplurkcomrdquo

初始化取得 Comet 網址

do getChannel

else

throw new Error(rdquo 參數不足需要 Key Secret Token Token Secretrdquo)

接著來處理 request這個 method

request (method path body callback) -gt

記錄一下這次的 Request

consolelog(rdquohttpdomainpathrdquo)

Callback 這邊先不丟進去要用另一種方式處理

request = consumerget(rdquohttpdomainpathrdquo token token secret null)

requeston rdquoresponserdquo (res) -gt

reson rdquodatardquo (chunk) -gt

parseResponse(chunk+rsquorsquo callback)

reson rdquoendrdquo (data) -gt

consolelog rdquoEnd Request pathrdquo

reson rdquoerrorrdquo (data) -gt

consolelog rdquoError rdquo + data

requestend()

處理資料

parseResponse = (data callback) -gt

if datalength gt 0

用 TryCatch 避免處理 JSON 出錯導致整個中斷

try

callback null JSONparse(data)

catch err

74 8 製作一個 Hubot的 Plurk Adapter

Nodejs中文電子書

consolelog(rdquoError Parse JSONrdquo + data err)

繼續執行

callback null data ||

大致上就是這樣上面程式的架構已經將整個 Hubot Plurk Adapter完成因為在測試時竟然因為噗浪 Lag而沒讀到完整的 Comet資料然後造成程式異常為了避免這個問題發生因此需要加上為了完美呈現需要再

加上 Comet的處理所以要使用到 EventEmitter的功能

comet (server callback) -gt

在 Callback 裡面會找不到自身所以設定區域變數

self =

記錄一下這次的 Request

consolelog(rdquo[Comet] serverrdquo)

Callback 這邊先不丟進去要用另一種方式處理

request = consumerget(rdquohttpdomainpathrdquo token token secret null)

requeston rdquoresponserdquo (res) -gt

reson rdquodatardquo (chunk) -gt

parseResponse(chunk+rsquorsquo callback)

reson rdquoendrdquo (data) -gt

consolelog rdquoEnd Request pathrdquo

請求結束發出事件通知可以進行下一次請求

selfemit rdquonextPlurkrdquo

reson rdquoerrorrdquo (data) -gt

consolelog rdquoError rdquo + data

requestend()

處理資料

parseResponse = (data callback) -gt

if datalength gt 0

用 trycatch 避免失敗中斷

try

去掉 JavaScript 的 Callback

data = datamatch(CometChannelscriptCallback((+))s)

jsonData = rdquordquo

if data

83 建立 Robot跟 API 75

Nodejs中文電子書

jsonData = JSONparse(data[1])

else

如果沒有任何 Match 嘗試直接 parse

jsonData = JSONparse(data)

catch err

consolelog(rdquo[Comet] Errorrdquo data err)

用 TryCatch 避免處理 JSON 出錯導致整個中斷

try

只傳入 json 的 data 部分

callback null jsonDatadata

catch err

consolelog(rdquo[Comet]Error Parse JSONrdquo + data err)

繼續執行

callback null data ||

後面的 get跟 post就簡單多了

get (path callback) -gt

request(rdquoGETrdquo path null callback)

post (path body callback) -gt

request(rdquoPOSTrdquo path body callback)

接著處理取的 Comet網址的 getChannel

getChannel -gt

self =

get rdquoAPPRealtimegetUserChannelrdquo (error data) -gt

if error

檢查是否有 comet server

if datacomet server

selfchannel = datacomet server

如果沒有 Channel Ready 就嘗試連接會失敗

selfemit(rsquochannel readyrsquo)

那麼先來處理 Plurk Adaper好處理的部份

send (plruk id strings⋯)-gt

跟 Reply 一樣直接交給 reply 做

reply plurk id strings⋯

76 8 製作一個 Hubot的 Plurk Adapter

Nodejs中文電子書

reply (plurk id strings⋯) -gt

stringsforEach (message) =gt

botreply(plruk id message)

接著把 run處理好就可以上線運作摟

run -gt

self =

options =

key processenvHUBOT PLURK KEY

secret processenvHUBOT PLURK SECRET

token processenvHUBOT PLURK TOKEN

token secret processenvHUBOT PLURK TOKEN SECRET

創建剛剛的 API

bot = new PlurkStreaming(options)

依照 Twitter 的 new RobotTextMessage 會沒有反應所以參考 hubot-minecraft 的方式

r = robotconstructor

處理噗浪河道訊息

doPlurk = (data)-gt

檢查是否為回噗

if dataresponse

datacontent raw = dataresponsecontent raw

datauser id = dataresponseuser id

確定有噗浪 ID 跟訊息

if dataplurk id and datacontent raw

selfreceive new rTextMessage(dataplurk id datacontent raw)

取得 Comet Server 完成開始第一次 Comet 連接

boton rdquochannel readyrdquo () -gt

botplurk selfdoPlurk

上一次 Comet 完成繼續 Polling

boton rdquonextPlurkrdquo ()-gt

botplurk selfdoPlurk

定時接受好友邀請

do botacceptFriends

bot = bot

83 建立 Robot跟 API 77

Nodejs中文電子書

終於完成 AdapterHubot專案裡面的 scripts資料夾內是互動部分不需要像 Adapter如此大費周章處理只新增檔案並且設計好對白之後就會回噗了我開發用的機器人在此大家可以去跟他玩玩[httpplurkcomelct9620 bot](httpplurkcomelct9620 bot)

84 原始資料提供

bull [製 作 一 個 Hubot 的 噗 浪 Adapter](http revo-skillfrosttw blog20120318create-a-hubot-plurk-adapter)

78 8 製作一個 Hubot的 Plurk Adapter

9

Nodejs好用工具介紹

91 logio

Real-time log monitoring in your browser

httplogioorg

nodejs (gt=0412 lt=0611)

npm config set unsafe-perm true

npm install -g --prefix=usrlocal logio

logio server start

httplocalhost8998

79

Nodejs中文電子書

80 9 Nodejs好用工具介紹

10

精選文章

101 Express

bull 使用 NodeJS + Express從 GETPOST Request取值 [邀稿中]

bull link2 [邀稿中]

bull link3 [邀稿中]

81

Nodejs中文電子書

82 10 精選文章

11

參考資源

111 Nodejs書籍

bull Programming Nodejs April 2012 (est) Travis Swicegood Pragmatic Bookshelf

bull e Little Book on CoffeeScript January 2012 Alex MacCaw OrsquoReilly Media

bull Node for Front-End Developers January 2012 Garann Means OrsquoReilly Media

bull Building Hypermedia APIs with HTML5 and Node November 2011 MikeAmundsen OrsquoReilly Media

bull Node Web Development August 2011 David Herron PacktPub

bull CoffeeScript Accelerated JavaScript Development July 2011 Trevor BurnhamPragmatic Bookshelf

bull Getting Started with GEO CouchDB and Nodejs July 2011 Mick omp-son OrsquoReilly Media

bull What Is Node July 2011 Brett McLaughlin OrsquoReilly Media

bull Node Up and Running May 2011 Tom Hughes-Croucher Mike WilsonOrsquoReilly Media

83

Nodejs中文電子書

bull Hands-on Nodejs e Nodejs introduction and API reference May 2011 Pe-dro Teixeira LeanPub

bull e Node Beginner Book Manuel Kiessling

bull Mastering Nodejs visionmedia

112 Nodejs影音教學

bull e Node Sessions e Best of OSCON 2011 August 2011 OrsquoReilly Media(Video)

bull Tom Hughes-Croucher on Node March 2011 OrsquoReilly Media (Video)

113 Nodejs教學網站

bull Node Tuts

114 Nodejs課程

bull Introduction To Nodejs Van Nguyen CodeLesson

84 11 參考資源

Page 50: Nodejs Wiki

Nodejs中文電子書

安裝MacPorts的提示

在MacPorts網站可以取得 OS X系統版本對應的安裝程式(例如 106或 107)httpwwwmacportsorg安裝過程會詢問系統管理者密碼使用預設的選項完成安裝即可安

裝MacPorts之後在終端機執行 port -v將會看到MacPorts的版本訊息

安裝 npm之前先更新MacPorts的套件清單以確保安裝的 npm是最新版本

sudo port -d selfupdate

接著安裝 npm

sudo port install npm

若讀者的 Nodejs並非使用MacPorts安裝則不建議使用MacPorts安裝npm因為 MacPorts會自動檢查並安裝相依套件而 npm相依 nodejs所以MacPorts也會一併將 nodejs套件安裝造成先前讀者使用其它方式安裝的 nodejs被覆蓋

讀者可以先使用 MacPorts安裝 curl(sudo port install curl)

再參考 Linux的 installsh安裝方式即可使用 npm官方提供的安裝程式

NPM安裝後測試

npm是指令列工具(command-line tool)使用時請先打開系統的文字終端機工具

測試 npm安裝與設定是否正確請輸入指令如下

npm -v

或是

npm --version

如果 npm已經正確安裝設定就會顯示版本訊息

44 5 NPM套件管理工具

Nodejs中文電子書

執行結果(範例)

110-2

52 使用 NPM安裝套件

npm目前擁有超過 6000種套件(packages)可以在 npm registry使用關鍵字搜尋套件

httpsearchnpmjsorg

舉例來說在關鍵字欄位輸入「coffee-script」下方的清單就會自動列出包含 coffee-script關鍵字的套件

接著我們回到終端機模式的操作npm的指令工具本身就可以完成套

件搜尋的任務

例如以下的指令同樣可以找出 coffee-script相關套件

npm search coffee-script

以下是搜尋結果的參考畫面

52 使用 NPM安裝套件 45

Nodejs中文電子書

找到需要的套件後(例如 express)即可使用以下指令安裝

npm install coffee-script

值得注意的一點是使用 npm install會將指定的套件安裝在工

作目錄(Working Directory)的 node modules資料夾下

以Windows為例如果執行 npm install的目錄位於

Cproject1

那麼 npm將會自動建立一個 node modules的子目錄(如果不存在)

Cproject1node modules

並且將下載的套件放置於這個子目錄例如

Cproject1node modulescoffee-script

這個設計讓專案可以個別管理相依的套件並且可以在專案佈署或發

行時將這些套件(位於 node modules)一併打包方便其它專案的使用者不必再重新下載套件

這個 npm install的預設安裝模式為 local(本地)只會變更當前專案的資料夾不會影響系統

另一種安裝模式稱為 global(全域)這種模式會將套件安裝到系統資

料夾也就是 npm安裝路徑的 node modules資料夾例如

CProgram Filesnodejsnode modules

46 5 NPM套件管理工具

Nodejs中文電子書

是否要使用全域安裝可以依照套件是否提供新指令來判斷舉例來說express 套件提供 express 這個指令而 coffee-script 則提供 coffee

指令

在 local 安 裝 模 式 中這 些 指 令 的 程 式 檔 案會 被 安 裝 到node modules的 bin這個隱藏資料夾下除非將bin的路徑加入 PATH環境變數否則要執行這些指令將會相當不便

為了方便指令的執行我們可以在 npm install 加上 -g 或 --

global參數啟用 global安裝模式例如

npm install -g coffee-script

npm install -g express

使用 global安裝模式需要注意執行權限的問題若權限不足可能會出現類似以下的錯誤訊息

npm ERR Error EACCES permission denied rsquorsquo

npm ERR

npm ERR Please try running this command again as rootAdministrator

要獲得足夠得執行權限請參考以下說明

bull Windows 7 或 2008 以上在「命令提示字元」的捷徑按右鍵選擇「以系統管理員身分執行」執行 npm指令時就會具有 Administrator身分

bull Mac OS X或 Linux系統可以使用 sudo指令例如

sudo npm install -g express

bull Linux系統可以使用 root權限登入或是以「sudo su -」切換成 root身分(使用 root權限操作系統相當危險因此並不建議使用這種方式)

若加上 -g參數使用 npm install -g coffee-script完成安裝

後就可以在終端機執行 coffee指令例如

coffee -v

52 使用 NPM安裝套件 47

Nodejs中文電子書

執行結果(範例)

CoffeeScript version 120

53 套件的更新及維護

除了前一節說明的 search 及 install 用法npm 還提供其他許多指令(commands)

使用 npm help可以查詢可用的指令

npm help

執行結果(部分)

where ltcommandgt is one of

adduser apihelp author bin bugs c cache completion

config deprecate docs edit explore faq find get

help help-search home i info init install la link

list ll ln login ls outdated owner pack prefix

prune publish r rb rebuild remove restart rm root

run-script s se search set show star start stop

submodule tag test un uninstall unlink unpublish

unstar up update version view whoami

使用 npm help command可以查詢指令的詳細用法例如

npm help list

接下來本節要介紹開發過程常用的 npm指令

使用 list可以列出已安裝套件

npm list

48 5 NPM套件管理工具

Nodejs中文電子書

執行結果(範例)

-- coffee-script120

- express256

- connect185

| -- formidable108

-- mime124

-- mkdirp007

-- qs041

檢視某個套件的詳細資訊例如

npm show express

升級所有套件(如果該套件已發佈更新版本)

npm update

升級指定的套件

npm update express

移除指定的套件

npm uninstall express

54 使用 packagejson

對於正式的 Nodejs專案可以建立一個命名為 packagejson的設

定檔(純文字格式)檔案內容參考範例如下

54 使用 packagejson 49

Nodejs中文電子書

packagejson(範例)

rdquonamerdquo rdquoapplication-namerdquo

rdquoversionrdquo rdquo001rdquo

rdquoprivaterdquo true

rdquodependenciesrdquo

rdquoexpressrdquo rdquo255rdquo

rdquocoffee-scriptrdquo rdquolatestrdquo

rdquomongooserdquo rdquogt= 253rdquo

其中 name與 version依照專案的需求設置

需要注意的是 dependencies的設定它用於指定專案相依的套件名

稱及版本

bull rdquoexpressrdquo rdquo255rdquo

代表此專案相依版本 255的 express套件

bull rdquocoffee-scriptrdquo rdquolatestrdquo

使用最新版的 coffee-script套件(每次更新都會檢查新版)

bull rdquomongooserdquo rdquogt= 253rdquo

使用版本大於 253的 mongoose套件

假設某個套件的新版可能造成專案無法正常運作就必須指定套件的

版本避免專案的程式碼來不及更新以相容新版套件通常在開發初期的

專案需要盡可能維持新套件的相容性(以取得套件的更新或修正)可以

用「gt=」設定最低相容的版本或是使用「latest」設定永遠保持最新

套件

50 5 NPM套件管理工具

6

Express介紹

在前面的 nodejs基礎當中介紹許多許多開設 http的使用方法及介紹以及許多基本的 nodejs基本應用

接下來要介紹一個套件稱為 express [Express](httpexpressjscom)這個套件主要幫忙解決許多 nodejs http server所需要的基本服務讓開發 httpservice變得更為容易不需要像之前需要透過層層模組(module)才有辦法開始編寫自己的程式

這個套件是由 TJ Holowaychuk 製作而成的套件裡面包含基本的路由處理 (route)http資料處理(GETPOSTPUT)另外還與樣板套件(jshtml template engine)搭配同時也可以處理許多複雜化的問題

61 Express安裝

安裝方式十分簡單只要透過之前介紹的 NPM就可以使用簡單的指令安裝指令如下

這邊建議需要將此套件安裝成為全域模組方便日後使用

51

Nodejs中文電子書

62 Express基本操作

express的使用也十分簡單先來建立一個基本的 hello world

var app = require(rsquoexpressrsquo)createServer()

port = 1337

applisten(port)

appget(rsquorsquo function(req res)

ressend(rsquohello worldrsquo)

)

consolelog(rsquostart express servernrsquo)

可以從上面的程式碼發現基本操作與 nodejs http的建立方式沒有太大差異主要差在當我們設定路由時可以直接透過 appget方式設定回應與接受方式

63 Express路由處理

Express對於 http服務上有許多包裝讓開發者使用及設定上更為方便例如有幾個路由設定那我們就統一藉由 appget來處理

Create http server

appget(rsquorsquo function(req res)

ressend(rsquohello worldrsquo)

)

appget(rsquotestrsquo function(req res)

ressend(rsquotest renderrsquo)

)

appget(rsquouserrsquo function(req res)

ressend(rsquouser pagersquo)

)

52 6 Express介紹

Nodejs中文電子書

如上面的程式碼所表示appget可以帶入兩個參數第一個是路徑名稱設定第二個為回應函式 (call back function)回應函式裡面就如同之前的 createServer方法裡面包含 requestresponse兩個物件可供使用使用者就可以透過瀏覽器輸入不同的 url切換到不同的頁面顯示不同的結果

路由設定上也有基本的配對方式讓使用者從瀏覽器輸入的網址可以

是一個變數只要符合型態就可以有對應的頁面產出例如

Create http server

appget(rsquouseridrsquo function(req res)

ressend(rsquouser rsquo + reqparamsid)

)

appget(rsquonumberrsquo function(req res)

ressend(rsquonumber rsquo + reqparamsnumber)

)

裡 面 使 用 到number 從 網 址 輸 入 之 後 就 可 以 直 接 使 用reqparamsnumber 取得所輸入的資料變成 url 參數使用當然前面也是可以加上路徑的設定userid在瀏覽器上路徑必須符合userxxx透

過 reqparamsid就可以取到 xxx這個字串值

另外express參數處理也提供了路由參數配對處理也可以透過正規表示法作為參數設定

var app = require(rsquoexpressrsquo)createServer()

port = 1337

applisten(port)

appget(^ip((d23)((d23))((d23))((d23))) function(req res)

ressend(reqparams)

)

上面程式碼可以發現後面路由設定的型態是正規表示法裡面設定

格式為ip之後必須要加上 ip型態才會符合資料格式同時取得 ip資料已經由正規表示法將資料做分群因此可以取得 ip的四個數字

此程式執行之後可以透過瀏覽器測試輸入網址為 localhost3000ip25525510010可以從頁面獲得資料

63 Express路由處理 53

Nodejs中文電子書

此章節全部範例程式碼如下

overview

author Caesar Chi

blog clonnblogspotcom

version 20120226

create server

var app = require(rsquoexpressrsquo)createServer()

port = 1337

applisten(port)

normal style

appget(rsquorsquo function(req res)

ressend(rsquohello worldrsquo)

)

appget(rsquotestrsquo function(req res)

ressend(rsquotest renderrsquo)

)

parameter style

appget(rsquouseridrsquo function(req res)

ressend(rsquouser rsquo + reqparamsid)

)

appget(rsquonumberrsquo function(req res)

ressend(rsquonumber rsquo + reqparamsnumber)

)

REGX style

appget(^ip((d23)((d23))((d23))) function(req res)

ressend(reqparams)

)

appget(rsquorsquo function(req res)

ressend(rsquoPage not foundrsquo 404)

54 6 Express介紹

Nodejs中文電子書

)

consolelog(rsquostart express servernrsquo)

64 Express middleware

Express 裡面有一個十分好用的應用概念稱為 middleware可以透過middleware做出複雜的效果同時上面也有介紹 next方法參數傳遞就是靠 middleware的概念來傳遞參數讓開發者可以明確的控制程式邏輯

create http server

appuse(expressbodyParser())

appuse(expressmethodOverride())

appuse(expresssession())

上面都是一種 middleware的使用方式透過 appuse方式裡面載入函式執行方法回應函式會包含三個基本參數responserequestnext其中next表示下一個 middleware執行函式同時會自動將預設三個參數繼續帶往下個函式執行底下有個實驗

上面的片段程式執行後開啟瀏覽器連結上 localhost1337會發現伺服器回應結果順序如下

first middle ware

second middle ware

execute middle ware

end middleware function

從上面的結果可以得知剛才設定的 middleware都生效了在 appuse設定的 middleware是所有 url皆會執行方法如果有指定特定方法就可以使用 appget的 middleware設定在 appget函式的第二個參數就可以帶入函式或者是匿名函式只要函式裡面最後會接受 request response next這三個參數同時也有正確指定 next函式的執行時機最後都會執行到最後一個方法當然開發者也可以評估程式邏輯要執行到哪一個階段讓邏輯

可以更為分明

64 Express middleware 55

Nodejs中文電子書

65 Express路由應用

在實際開發上可能會遇到需要使用參數等方式混和變數一起使用

express裡面提供了一個很棒的處理方法 appall這個方式可以先採用基本路由配對再將設定為每個不同的處理方式開發者可以透過這個方式簡

化自己的程式邏輯

overview

author

version 20120226

create server

var app = require(rsquoexpressrsquo)createServer()

port = 1337

users = [

name rsquoClonnrsquo

name rsquoChirsquo

]

applisten(port)

appall(rsquouseridoprsquo function(req res next)

requser = users[reqparamsid]

if (requser)

next()

else

next(new Error(rsquocannot find user rsquo + reqparamsid))

)

appget(rsquouseridrsquo function(req res)

ressend(rsquoviewing rsquo + requsername)

)

appget(rsquouserideditrsquo function(req res)

ressend(rsquoediting rsquo + requsername)

)

56 6 Express介紹

Nodejs中文電子書

appget(rsquouseriddeletersquo function(req res)

ressend(rsquodeleting rsquo + requsername)

)

appget(rsquorsquo function(req res)

ressend(rsquoPage not foundrsquo 404)

)

consolelog(rsquostart express servernrsquo)

內部宣告一組預設的使用者分別給予名稱設定藉由 appall這個方法可以先將路由雛形建立再接下來設定 appget的路徑格式只要符合格式就會分配進入對應的方法中像上面的程式當中如果使用者輸入路徑為user0除了執行 appall程式之後執行 next方法就會對應到路徑設定為userid的這個方法當中如果使用者輸入路徑為user0edit就會執行到useridedit的對應方法

66 Express GET應用範例

我們準備一個使用 GET方法傳送資料的表單

ltDOCTYPE htmlgt

lthtmlgt

ltheadgt

ltmeta http-equiv=rdquoContent-Typerdquo content=rdquotexthtml charset=utf8rdquogt

lttitlegtNodejs 菜鳥筆記 (1)lttitlegt

ltlink rel=rdquostylesheetrdquo href=rdquocssstylecssrdquo type=rdquotextcssrdquo media=rdquoallrdquo gt

ltheadgt

ltbodygt

lth1gtNodejs 菜鳥筆記-註冊lth1gt

ltform id=rdquosignuprdquo method=rdquoGETrdquo action=rdquohttplocalhost3000Signuprdquogt

ltlabelgt 使用者名稱ltlabelgtltinput type=rdquotextrdquo id=rdquousernamerdquo name=rdquousernamerdquo gtltbrgt

ltlabelgt 電子郵件ltlabelgtltinput type=rdquotextrdquo id=rdquoemailrdquo name=rdquoemailrdquo gtltbrgt

ltinput type=rdquosubmitrdquo value=rdquo 註冊我的帳號rdquo gtltbrgt

ltformgt

ltbodygt

lthtmlgt

66 Express GET應用範例 57

Nodejs中文電子書

這個表單沒有什麼特別的地方我們只需要看第 9 行form 使用的method是 GET然後 action是rdquohttplocalhost3000Signupldquo等一下我們要來撰寫Signup這個 URL Path的處理程式

處理 Signup行為

我們知道所謂的 GET方法會透過 URL來把表單的值給帶過去以上面的表單來說到時候 URL會以這樣的形式傳遞

httplocalhost3000Signupusername=xxxampemail=xxx

所以要能處理這樣的資料必須有以下功能

bull 解析 URL

bull 辨別動作是 Signup

bull 解析出 username和 email

一旦能取得 username和 email的值程式就能加以應用了

處理 Signup的程式碼雛形

load module

var url = require(rsquourlrsquo)

urlData = urlparse(requrltrue)

action = urlDatapathname

reswriteHead(200 rdquoContent-Typerdquordquotexthtml charset=utf-8rdquo)

if (action === rdquoSignuprdquo)

user = urlDataquery

resend(rdquolth1gtrdquo + userusername + rdquo 歡迎您的加入 lth1gtltpgt 我們已經將會員啟用信寄至rdquo + useremail + rdquoltpgtrdquo)

首先需要加載 url module它是用來協助我們解析 URL的模組接著使用 urlparse方法第一個傳入 url字串變數也就是 requrl另外第二個參數的用意是設為 ture則引進 querystring模組來協助處理預設是 false它影響到的是 urlDataquery設為 true會傳回物件不然就只是一般的字串urlparse會將字串內容整理成一個物件我們把它指定給 urlData

action變數作為記錄 pathname這是我們稍後要來判斷目前網頁的動作是什麼接著先將 html表頭資訊 (Header)準備好再來判斷路徑邏輯如

58 6 Express介紹

Nodejs中文電子書

果是 Signup這個動作就把 urlDataquery裡的資料指定給 user然後輸出userusername和 useremail把使用者從表單註冊的資料顯示於頁面中

最後進行程式測試啟動 nodejs主程式之後開啟瀏覽器就會看到表單填寫完畢按下送出就可以看到結果了

完整 nodejs程式碼如下

var http = require(rsquohttprsquo)

url = require(rsquourlrsquo)

fs = require(rdquofsrdquo)

server

server = httpcreateServer(function (reqres)

var urlData

encode = rdquoutf8rdquo

filePath = rdquoviewexpress_get_example_formhtmlrdquo

action

urlData = urlparse(requrltrue)

action = urlDatapathname

reswriteHead(200 rdquoContent-Typerdquordquotexthtml charset=utf-8rdquo)

if (action === rdquoSignuprdquo)

user = urlDataquery

resend(rdquolth1gtrdquo + userusername + rdquo 歡迎您的加入 lth1gtltpgt 我們已經將會員啟用信寄至rdquo + useremail + rdquoltpgtrdquo)

else

fsreadFile(filePath encode function(err file)

reswrite(file)

resend()

)

)

serverlisten(3000)

consolelog(rsquoServer 跑起來了現在時間是rsquo + new Date())

66 Express GET應用範例 59

Nodejs中文電子書

67 Express POST應用範例

一開始準備基本的 html表單傳送內容以 POST方式form的 action屬性設定為 POST其餘 html內容與前一個範例應用相同

ltDOCTYPE htmlgt

lthtmlgt

ltheadgt

ltmeta http-equiv=rdquoContent-Typerdquo content=rdquotexthtml charset=utf8rdquogt

lttitlegtNodejs 菜鳥筆記 (1)lttitlegt

ltlink rel=rdquostylesheetrdquo href=rdquocssstylecssrdquo type=rdquotextcssrdquo media=rdquoallrdquo gt

ltheadgt

ltbodygt

lth1gtNodejs 菜鳥筆記-註冊lth1gt

ltform id=rdquosignuprdquo method=rdquoPOSTrdquo action=rdquohttplocalhost3000Signuprdquogt

ltlabelgt 使用者名稱ltlabelgtltinput type=rdquotextrdquo id=rdquousernamerdquo name=rdquousernamerdquo gtltbrgt

ltlabelgt 電子郵件ltlabelgtltinput type=rdquotextrdquo id=rdquoemailrdquo name=rdquoemailrdquo gtltbrgt

ltinput type=rdquosubmitrdquo value=rdquo 註冊我的帳號rdquo gtltbrgt

ltformgt

ltbodygt

lthtmlgt

nodejs的程式處理邏輯與前面 GET範例類似部分程式碼如下

qs = require(rsquoquerystringrsquo)

if (action === rdquoSignuprdquo)

formData = rsquorsquo

reqon(rdquodatardquo function (data)

formData += data

)

reqon(rdquoendrdquo function ()

user = qsparse(formData)

resend(rdquolth1gtrdquo + userusername + rdquo 歡迎您的加入 lth1gtltpgt 我們已經將會員啟用信寄至rdquo + useremail + rdquoltpgtrdquo)

)

60 6 Express介紹

Nodejs中文電子書

主要加入了rsquoquerystringrsquo這個moduel方便我們等一下解析由表單 POST回來的資料另外加入一個 formData的變數用來搜集待等一下表單回傳的資料前面的 GET範例我們只從 req拿出 url的資料這次要在利用req身上的事件處理

JavaScript 在訂閱事件時使用 addEventListener而 nodejs 使用的則是on這邊加上了監聽 data的事件會在瀏覽器傳送資料到Web Server時被執行參數是它所接收到的資料型態是字串

接著再增加 end的事件當瀏覽器的請求事件結束時它就會動作

由於瀏覽器使用 POST在上傳資料時會將資料一塊塊地上傳因為我們在監聽 data事件時透過 formData變數將它累加起來lt不過由於我們

上傳的資料很少一次就結束不過如果日後需要傳的是資料比較大的檔

案這個累加動作就很重要

當資料傳完就進到 end 事件中會用到 qsparse 來解析 formDataformData的內容是字串內容是

username=wordsmithampemail=wordsmith40somewhere

而 qsparse可以幫我們把這個 querystring轉成物件的格式也就是

username=wordsmithampemail=wordsmith40somewhere

一旦轉成物件並指定給 user之後其他的事情就和 GET方法時操作的一樣寫 response的表頭將內容回傳並將 userusername和 useremail代入到內容中

修改完成後接著執行 nodejs程式啟動 web server開啟瀏覽器進入表單測試看看POST的方式能否順利運作

完整程式碼如下

var http = require(rsquohttprsquo)

url = require(rsquourlrsquo)

fs = require(rdquofsrdquo)

qs = require(rsquoquerystringrsquo)

server

server = httpcreateServer(function (reqres)

var urlData

67 Express POST應用範例 61

Nodejs中文電子書

encode = rdquoutf8rdquo

filePath = rdquoviewexpress_post_example_formhtmlrdquo

formData

action

urlData = urlparse(requrltrue)

action = urlDatapathname

reswriteHead(200 rdquoContent-Typerdquordquotexthtml charset=utf-8rdquo)

if (action === rdquoSignuprdquo)

formData = rsquorsquo

reqon(rdquodatardquo function (data)

formData += data

)

reqon(rdquoendrdquo function ()

user = qsparse(formData)

resend(rdquolth1gtrdquo + userusername + rdquo 歡迎您的加入 lth1gtltpgt 我們已經將會員啟用信寄至rdquo + useremail + rdquoltpgtrdquo)

)

else

fsreadFile(filePath encode function(err file)

reswrite(file)

resend()

)

)

serverlisten(3000)

consolelog(rsquoServer 跑起來了現在時間是rsquo + new Date())

62 6 Express介紹

Nodejs中文電子書

68 Express AJAX應用範例

在 Nodejs要使用 Ajax傳送資料並且與之互動在接受資料的部份沒有太大的差別client端不是用 GET就是用 POST來傳資料重點在處理完後用 JSON格式回傳當然 Ajax不見得只傳 JSON格式有時是回傳一段 HTML碼不過後者對伺服器來說基本上就和前兩篇沒有差別了所以我們還是以回傳 JSON做為這一回的主題

這一回其實大多數的工作都會落在前端 Ajax上面前端要負責發送與接收資料並在接收資料後撤掉原先發送資料的表單並將取得的資料

改成 HTML格式之後放上頁面

首先先準備 HTML靜態頁面資料

ltDOCTYPE htmlgt

lthtmlgt

ltheadgt

lttitlegtNodejs 菜鳥筆記 (3)lttitlegt

ltlink rel=rdquostylesheetrdquo href=rdquocssstylecssrdquo type=rdquotextcssrdquo media=rdquoallrdquo gt

ltscript src=rdquohttpsajaxgoogleapiscomajaxlibsjquery171jqueryminjsrdquogtltscriptgt

ltheadgt

ltbodygt

lth1gtNodejs 菜鳥筆記-註冊 (用 Ajax)lth1gt

ltform id=rdquosignuprdquo action=rdquoSignuprdquo method=rdquoPOSTrdquogt

ltlabelgt 使用者名稱ltlabelgtltinput type=rdquotextrdquo id=rdquousernamerdquo name=rdquousernamerdquo gtltbrgt

ltlabelgt 電子郵件ltlabelgtltinput type=rdquotextrdquo id=rdquoemailrdquo name=rdquoemailrdquo gtltbrgt

ltinput type=rdquosubmitrdquo value=rdquo 註冊我的帳號rdquo gtltbrgt

ltformgt

ltdiv id=rdquoresultrdquogt

ltdivgt

ltscript type=rdquotextjavascriptrdquogt

$(function()$(rsquosignup input[type=rdquosubmitrdquo]rsquo)click(function(e)

var user =

userusername = $(rdquousernamerdquo)val()useremail = $(rdquoemailrdquo)val()

$post(rdquoSignuprdquouserfunction(data)greet(data)

)

68 Express AJAX應用範例 63

Nodejs中文電子書

epreventDefault()

)

var greet = function(msg)

var resultNode = $(rsquoresultrsquo)greeting = $(rsquolth2gtHirsquo+msgusername+rsquo 你的會員 id 是rsquo+ msgid +rsquo 我們會將會員啟動信寄至rsquo+msgemail+rsquolth2gtrsquo)

$(rsquosignuprsquo)hide()resultNodehtml(rdquordquo)

resultNodeappend(greeting)

)

ltscriptgt

ltbodygt

lthtmlgt

HTML頁面上準備了一個表單用來傳送註冊資料接著直接引用了Google CDN來載入 jQuery用來幫我們處理 Ajax的工作這次要傳送和接收的工作很大的變動都在 HTML頁面上的 JavaScript當中我們要做的事有 (相關 jQuery處理這邊不多做贅述指提起主要功能解說)

bull 用 jQUery取得 submit按鈕綁定它的 click動作

bull 取得表單 username和 email的值存放在 user這個物件中

bull 用 jQuery的 $post方法將 user的資料傳到 Server

bull 一旦成功取得資料後透過 greet這個 function組成回報給 user的訊息

bull 清空原本給使用者填資料的表單

bull 將 Server回傳的 usernameemail和 id這 3個資料組成回應的訊息

bull 將訊息放到原本表單的位置

經過以上的處理後一個 Ajax的表單的基本功能已經完成

接著進行 nodejs主要程式的編輯部分程式碼如下

var fs = require(rdquofsrdquo)

64 6 Express介紹

Nodejs中文電子書

qs = require(rsquoquerystringrsquo)

if (action === rdquoSignuprdquo)

formData = rsquorsquo

reqon(rdquodatardquo function (data)

formData += data

)

reqon(rdquoendrdquo function ()

var msg

user = qsparse(formData)

userid = rdquo123456rdquo

msg = JSONstringify(user)

reswriteHead(200 rdquoContent-Typerdquordquoapplicationjson charset=utf-8rdquordquoContent-Lengthrdquomsglength)

resend(msg)

)

這裡的程式和前面 POST範例基本上大同小異差別在

bull 幫 user的資料加上 id隨意存放一些文字進去讓 Server回傳的資料多於 Client端傳上來的不然會覺得 Server都沒做事

bull 增加了 msg 這個變數存放將 user 物件 JSON 文字化的結果JSONstringify這個轉換函式是 V8引擎所提供的如果你好奇的話

bull 大重點來了我們要告訴 Client端這次回傳的資料格式是 JSON所在 Content-type和 Content-Length要提供給 Client

Server很輕鬆就完成任務了最後進行程式測試啟動 nodejs主程式之後開啟瀏覽器就會看到表單填寫完畢按下送出就可以看到結果了

最後 nodejs本篇範例程式碼如下

var http = require(rsquohttprsquo)

url = require(rsquourlrsquo)

fs = require(rdquofsrdquo)

qs = require(rsquoquerystringrsquo)

server

68 Express AJAX應用範例 65

Nodejs中文電子書

server = httpcreateServer(function (reqres)

var urlData

encode = rdquoutf8rdquo

filePath = rdquoviewexpress_ajax_example_formhtmlrdquo

formData

action

urlData = urlparse(requrltrue)

action = urlDatapathname

if (action === rdquoSignuprdquo)

formData = rsquorsquo

reqon(rdquodatardquo function (data)

formData += data

)

reqon(rdquoendrdquo function ()

var msg

user = qsparse(formData)

userid = rdquo123456rdquo

msg = JSONstringify(user)

reswriteHead(200 rdquoContent-TyperdquordquoapplicationjsonrdquordquoContent-Lengthrdquomsglength)

resend(msg)

)

else

fsreadFile(filePath encode function(err file)

reswriteHead(200 rdquoContent-Typerdquordquotexthtml charset=utf-8rdquo)

reswrite(file)

resend()

)

)

serverlisten(3000)

66 6 Express介紹

Nodejs中文電子書

consolelog(rsquoServer 跑起來了現在時間是rsquo + new Date())

69 原始資料提供

bull [NodeJS初學者筆記 (1)-用 GET傳送資料] (httpithelpithomecomtwquestion10087402)

bull [NodeJS初學者筆記 (2)-用 POST傳送資料] (httpithelpithomecomtwquestion10087489)

bull [NodeJS 初學者筆記 (3)-用 Ajax 傳送資料] (httpithelpithomecomtwquestion10087627)

69 原始資料提供 67

Nodejs中文電子書

68 6 Express介紹

7

CoffeeScript

bull Spine

bull Hem

bull Spineapp

Letrsquos Have a Cup of CoffeeScript

bull es5-shim ECMAScript 5 compatibility shims for legacy JavaScript engines

ESnext

node-iform - A middleware for node-validator httpsgithubcomguileennode-iform

69

Nodejs中文電子書

70 7 CoffeeScript

8

製作一個 Hubot的 Plurk Adapter

81 應用事項提醒

此應用範例以CoffeeScript編寫主要程式架構採用Github釋放的Hubot專案以下有幾個主要注意事項請讀者注意

bull Hubot 一開始的架構除了內建的兩個 Adapter 之外其餘都要以Nodejs的Module方式才能運作

bull Hubot的 binhubot裡面寫著 npm install所以不管你怎麼改原始碼也不能改變第一點的狀況

其實上述都在討論同一件事情NodeJS的模組而且模組不能設定相對路徑之類的來安裝一定要包裝成 npm相符和格式才有辦法正確執行

82 建立 Adapter

首先需要準備兩個檔案

bull packagejson

bull plurkcoffee

71

Nodejs中文電子書

有這兩個就足以變成 NodeJS的模組了在開始 Coding之前先來設定一下 packagejson相依性

rdquonamerdquo rdquohubot-plurkrdquo

rdquoversionrdquo rdquo011rdquo

rdquomainrdquo rdquoplurkrdquo

rdquodependenciesrdquo

rdquohubotrdquo rdquogt=205rdquo

rdquooauthrdquo rdquordquo

rdquocronrdquordquordquo

這邊會使用到NodeJS的 cron模組(Module)而登入 Plurk需要OAuth標準授權才行所以也需要加載 OAuth模組

注意Hubot在讀取非內建模組時會自動在前面加上 hubot-的前置

83 建立 Robot跟 API

本篇應用基本上程式碼大部分參考 Hubot - Twitter Adapter來製作主要差異只有在使用 OAuth這個模組Twitter有 Streaming可用而 Plurk則得用 Comet方式來達到即時讀取

plurkcoffee程式碼

Robot = require(rdquohubotrdquo)robot()

Adapter = require(rdquohubtordquo)adapter()

EventEmitter = require(rdquoeventsrdquo)EventEmitter

oauth = require(rdquooauthrdquo)

cronJob = require(rdquocronrdquo)CronJob

class Plurk exntends Adapter

class PlurkStreaming exnteds EventEmitter

72 8 製作一個 Hubot的 Plurk Adapter

Nodejs中文電子書

先弄個基本架構主要 Class皆參考 Twitter Adapter命名方式接著再將 Plurk這個 Class增加幾個Method基本上只要有 run send reply就夠了而 run用來做初始化的部分

class Plurk entends Adapter

send (plurk id strings⋯) -gt

reply (plurk id strings⋯) -gt

run -gt

看起來有點東西接著來處理主要的 Plurk API結合部分

class PlurkStreaming extends EventEmitter

consuctor (options) -gt

plurk (callback) -gt

觀察河道

getChannel -gt

取得 Comet 網址

reply (plurk id message) -gt

回噗

acceptFriends -gt

接受好友

get (path callback) -gt

GET 請求

post (path body callback)-gt

POST 請求(其實是裝飾)

request (method path body callback)-gt

主要的 OAuth 請求

comet (server callback)-gt

噗浪的 Comet 傳回是 JavaScript Callback 要另外處理後才會變成 JSON

然後把注意力集中到 constructor上先把建構子弄好

constructor (options) -gt

super()

if optionskey and optionssecret and optionstoken and optionstoken secret

key = optionskey

secret = optionssecret

token = optionstoken

token secret = optionstoken secret

83 建立 Robot跟 API 73

Nodejs中文電子書

建立 OAuth 連接

consumer = new oauthOAuth(

rdquohttpwwwplurkcomOAuthrequest tokenrdquo

rdquohttpwwwplurkcomOAuthaccess tokenrdquo

key

secret

rdquo10rdquo

rdquohttpwwwplurkcomOAuthauthorizerdquo

rdquoHMAC-SHA1rdquo

)

domain = rdquowwwplurkcomrdquo

初始化取得 Comet 網址

do getChannel

else

throw new Error(rdquo 參數不足需要 Key Secret Token Token Secretrdquo)

接著來處理 request這個 method

request (method path body callback) -gt

記錄一下這次的 Request

consolelog(rdquohttpdomainpathrdquo)

Callback 這邊先不丟進去要用另一種方式處理

request = consumerget(rdquohttpdomainpathrdquo token token secret null)

requeston rdquoresponserdquo (res) -gt

reson rdquodatardquo (chunk) -gt

parseResponse(chunk+rsquorsquo callback)

reson rdquoendrdquo (data) -gt

consolelog rdquoEnd Request pathrdquo

reson rdquoerrorrdquo (data) -gt

consolelog rdquoError rdquo + data

requestend()

處理資料

parseResponse = (data callback) -gt

if datalength gt 0

用 TryCatch 避免處理 JSON 出錯導致整個中斷

try

callback null JSONparse(data)

catch err

74 8 製作一個 Hubot的 Plurk Adapter

Nodejs中文電子書

consolelog(rdquoError Parse JSONrdquo + data err)

繼續執行

callback null data ||

大致上就是這樣上面程式的架構已經將整個 Hubot Plurk Adapter完成因為在測試時竟然因為噗浪 Lag而沒讀到完整的 Comet資料然後造成程式異常為了避免這個問題發生因此需要加上為了完美呈現需要再

加上 Comet的處理所以要使用到 EventEmitter的功能

comet (server callback) -gt

在 Callback 裡面會找不到自身所以設定區域變數

self =

記錄一下這次的 Request

consolelog(rdquo[Comet] serverrdquo)

Callback 這邊先不丟進去要用另一種方式處理

request = consumerget(rdquohttpdomainpathrdquo token token secret null)

requeston rdquoresponserdquo (res) -gt

reson rdquodatardquo (chunk) -gt

parseResponse(chunk+rsquorsquo callback)

reson rdquoendrdquo (data) -gt

consolelog rdquoEnd Request pathrdquo

請求結束發出事件通知可以進行下一次請求

selfemit rdquonextPlurkrdquo

reson rdquoerrorrdquo (data) -gt

consolelog rdquoError rdquo + data

requestend()

處理資料

parseResponse = (data callback) -gt

if datalength gt 0

用 trycatch 避免失敗中斷

try

去掉 JavaScript 的 Callback

data = datamatch(CometChannelscriptCallback((+))s)

jsonData = rdquordquo

if data

83 建立 Robot跟 API 75

Nodejs中文電子書

jsonData = JSONparse(data[1])

else

如果沒有任何 Match 嘗試直接 parse

jsonData = JSONparse(data)

catch err

consolelog(rdquo[Comet] Errorrdquo data err)

用 TryCatch 避免處理 JSON 出錯導致整個中斷

try

只傳入 json 的 data 部分

callback null jsonDatadata

catch err

consolelog(rdquo[Comet]Error Parse JSONrdquo + data err)

繼續執行

callback null data ||

後面的 get跟 post就簡單多了

get (path callback) -gt

request(rdquoGETrdquo path null callback)

post (path body callback) -gt

request(rdquoPOSTrdquo path body callback)

接著處理取的 Comet網址的 getChannel

getChannel -gt

self =

get rdquoAPPRealtimegetUserChannelrdquo (error data) -gt

if error

檢查是否有 comet server

if datacomet server

selfchannel = datacomet server

如果沒有 Channel Ready 就嘗試連接會失敗

selfemit(rsquochannel readyrsquo)

那麼先來處理 Plurk Adaper好處理的部份

send (plruk id strings⋯)-gt

跟 Reply 一樣直接交給 reply 做

reply plurk id strings⋯

76 8 製作一個 Hubot的 Plurk Adapter

Nodejs中文電子書

reply (plurk id strings⋯) -gt

stringsforEach (message) =gt

botreply(plruk id message)

接著把 run處理好就可以上線運作摟

run -gt

self =

options =

key processenvHUBOT PLURK KEY

secret processenvHUBOT PLURK SECRET

token processenvHUBOT PLURK TOKEN

token secret processenvHUBOT PLURK TOKEN SECRET

創建剛剛的 API

bot = new PlurkStreaming(options)

依照 Twitter 的 new RobotTextMessage 會沒有反應所以參考 hubot-minecraft 的方式

r = robotconstructor

處理噗浪河道訊息

doPlurk = (data)-gt

檢查是否為回噗

if dataresponse

datacontent raw = dataresponsecontent raw

datauser id = dataresponseuser id

確定有噗浪 ID 跟訊息

if dataplurk id and datacontent raw

selfreceive new rTextMessage(dataplurk id datacontent raw)

取得 Comet Server 完成開始第一次 Comet 連接

boton rdquochannel readyrdquo () -gt

botplurk selfdoPlurk

上一次 Comet 完成繼續 Polling

boton rdquonextPlurkrdquo ()-gt

botplurk selfdoPlurk

定時接受好友邀請

do botacceptFriends

bot = bot

83 建立 Robot跟 API 77

Nodejs中文電子書

終於完成 AdapterHubot專案裡面的 scripts資料夾內是互動部分不需要像 Adapter如此大費周章處理只新增檔案並且設計好對白之後就會回噗了我開發用的機器人在此大家可以去跟他玩玩[httpplurkcomelct9620 bot](httpplurkcomelct9620 bot)

84 原始資料提供

bull [製 作 一 個 Hubot 的 噗 浪 Adapter](http revo-skillfrosttw blog20120318create-a-hubot-plurk-adapter)

78 8 製作一個 Hubot的 Plurk Adapter

9

Nodejs好用工具介紹

91 logio

Real-time log monitoring in your browser

httplogioorg

nodejs (gt=0412 lt=0611)

npm config set unsafe-perm true

npm install -g --prefix=usrlocal logio

logio server start

httplocalhost8998

79

Nodejs中文電子書

80 9 Nodejs好用工具介紹

10

精選文章

101 Express

bull 使用 NodeJS + Express從 GETPOST Request取值 [邀稿中]

bull link2 [邀稿中]

bull link3 [邀稿中]

81

Nodejs中文電子書

82 10 精選文章

11

參考資源

111 Nodejs書籍

bull Programming Nodejs April 2012 (est) Travis Swicegood Pragmatic Bookshelf

bull e Little Book on CoffeeScript January 2012 Alex MacCaw OrsquoReilly Media

bull Node for Front-End Developers January 2012 Garann Means OrsquoReilly Media

bull Building Hypermedia APIs with HTML5 and Node November 2011 MikeAmundsen OrsquoReilly Media

bull Node Web Development August 2011 David Herron PacktPub

bull CoffeeScript Accelerated JavaScript Development July 2011 Trevor BurnhamPragmatic Bookshelf

bull Getting Started with GEO CouchDB and Nodejs July 2011 Mick omp-son OrsquoReilly Media

bull What Is Node July 2011 Brett McLaughlin OrsquoReilly Media

bull Node Up and Running May 2011 Tom Hughes-Croucher Mike WilsonOrsquoReilly Media

83

Nodejs中文電子書

bull Hands-on Nodejs e Nodejs introduction and API reference May 2011 Pe-dro Teixeira LeanPub

bull e Node Beginner Book Manuel Kiessling

bull Mastering Nodejs visionmedia

112 Nodejs影音教學

bull e Node Sessions e Best of OSCON 2011 August 2011 OrsquoReilly Media(Video)

bull Tom Hughes-Croucher on Node March 2011 OrsquoReilly Media (Video)

113 Nodejs教學網站

bull Node Tuts

114 Nodejs課程

bull Introduction To Nodejs Van Nguyen CodeLesson

84 11 參考資源

Page 51: Nodejs Wiki

Nodejs中文電子書

執行結果(範例)

110-2

52 使用 NPM安裝套件

npm目前擁有超過 6000種套件(packages)可以在 npm registry使用關鍵字搜尋套件

httpsearchnpmjsorg

舉例來說在關鍵字欄位輸入「coffee-script」下方的清單就會自動列出包含 coffee-script關鍵字的套件

接著我們回到終端機模式的操作npm的指令工具本身就可以完成套

件搜尋的任務

例如以下的指令同樣可以找出 coffee-script相關套件

npm search coffee-script

以下是搜尋結果的參考畫面

52 使用 NPM安裝套件 45

Nodejs中文電子書

找到需要的套件後(例如 express)即可使用以下指令安裝

npm install coffee-script

值得注意的一點是使用 npm install會將指定的套件安裝在工

作目錄(Working Directory)的 node modules資料夾下

以Windows為例如果執行 npm install的目錄位於

Cproject1

那麼 npm將會自動建立一個 node modules的子目錄(如果不存在)

Cproject1node modules

並且將下載的套件放置於這個子目錄例如

Cproject1node modulescoffee-script

這個設計讓專案可以個別管理相依的套件並且可以在專案佈署或發

行時將這些套件(位於 node modules)一併打包方便其它專案的使用者不必再重新下載套件

這個 npm install的預設安裝模式為 local(本地)只會變更當前專案的資料夾不會影響系統

另一種安裝模式稱為 global(全域)這種模式會將套件安裝到系統資

料夾也就是 npm安裝路徑的 node modules資料夾例如

CProgram Filesnodejsnode modules

46 5 NPM套件管理工具

Nodejs中文電子書

是否要使用全域安裝可以依照套件是否提供新指令來判斷舉例來說express 套件提供 express 這個指令而 coffee-script 則提供 coffee

指令

在 local 安 裝 模 式 中這 些 指 令 的 程 式 檔 案會 被 安 裝 到node modules的 bin這個隱藏資料夾下除非將bin的路徑加入 PATH環境變數否則要執行這些指令將會相當不便

為了方便指令的執行我們可以在 npm install 加上 -g 或 --

global參數啟用 global安裝模式例如

npm install -g coffee-script

npm install -g express

使用 global安裝模式需要注意執行權限的問題若權限不足可能會出現類似以下的錯誤訊息

npm ERR Error EACCES permission denied rsquorsquo

npm ERR

npm ERR Please try running this command again as rootAdministrator

要獲得足夠得執行權限請參考以下說明

bull Windows 7 或 2008 以上在「命令提示字元」的捷徑按右鍵選擇「以系統管理員身分執行」執行 npm指令時就會具有 Administrator身分

bull Mac OS X或 Linux系統可以使用 sudo指令例如

sudo npm install -g express

bull Linux系統可以使用 root權限登入或是以「sudo su -」切換成 root身分(使用 root權限操作系統相當危險因此並不建議使用這種方式)

若加上 -g參數使用 npm install -g coffee-script完成安裝

後就可以在終端機執行 coffee指令例如

coffee -v

52 使用 NPM安裝套件 47

Nodejs中文電子書

執行結果(範例)

CoffeeScript version 120

53 套件的更新及維護

除了前一節說明的 search 及 install 用法npm 還提供其他許多指令(commands)

使用 npm help可以查詢可用的指令

npm help

執行結果(部分)

where ltcommandgt is one of

adduser apihelp author bin bugs c cache completion

config deprecate docs edit explore faq find get

help help-search home i info init install la link

list ll ln login ls outdated owner pack prefix

prune publish r rb rebuild remove restart rm root

run-script s se search set show star start stop

submodule tag test un uninstall unlink unpublish

unstar up update version view whoami

使用 npm help command可以查詢指令的詳細用法例如

npm help list

接下來本節要介紹開發過程常用的 npm指令

使用 list可以列出已安裝套件

npm list

48 5 NPM套件管理工具

Nodejs中文電子書

執行結果(範例)

-- coffee-script120

- express256

- connect185

| -- formidable108

-- mime124

-- mkdirp007

-- qs041

檢視某個套件的詳細資訊例如

npm show express

升級所有套件(如果該套件已發佈更新版本)

npm update

升級指定的套件

npm update express

移除指定的套件

npm uninstall express

54 使用 packagejson

對於正式的 Nodejs專案可以建立一個命名為 packagejson的設

定檔(純文字格式)檔案內容參考範例如下

54 使用 packagejson 49

Nodejs中文電子書

packagejson(範例)

rdquonamerdquo rdquoapplication-namerdquo

rdquoversionrdquo rdquo001rdquo

rdquoprivaterdquo true

rdquodependenciesrdquo

rdquoexpressrdquo rdquo255rdquo

rdquocoffee-scriptrdquo rdquolatestrdquo

rdquomongooserdquo rdquogt= 253rdquo

其中 name與 version依照專案的需求設置

需要注意的是 dependencies的設定它用於指定專案相依的套件名

稱及版本

bull rdquoexpressrdquo rdquo255rdquo

代表此專案相依版本 255的 express套件

bull rdquocoffee-scriptrdquo rdquolatestrdquo

使用最新版的 coffee-script套件(每次更新都會檢查新版)

bull rdquomongooserdquo rdquogt= 253rdquo

使用版本大於 253的 mongoose套件

假設某個套件的新版可能造成專案無法正常運作就必須指定套件的

版本避免專案的程式碼來不及更新以相容新版套件通常在開發初期的

專案需要盡可能維持新套件的相容性(以取得套件的更新或修正)可以

用「gt=」設定最低相容的版本或是使用「latest」設定永遠保持最新

套件

50 5 NPM套件管理工具

6

Express介紹

在前面的 nodejs基礎當中介紹許多許多開設 http的使用方法及介紹以及許多基本的 nodejs基本應用

接下來要介紹一個套件稱為 express [Express](httpexpressjscom)這個套件主要幫忙解決許多 nodejs http server所需要的基本服務讓開發 httpservice變得更為容易不需要像之前需要透過層層模組(module)才有辦法開始編寫自己的程式

這個套件是由 TJ Holowaychuk 製作而成的套件裡面包含基本的路由處理 (route)http資料處理(GETPOSTPUT)另外還與樣板套件(jshtml template engine)搭配同時也可以處理許多複雜化的問題

61 Express安裝

安裝方式十分簡單只要透過之前介紹的 NPM就可以使用簡單的指令安裝指令如下

這邊建議需要將此套件安裝成為全域模組方便日後使用

51

Nodejs中文電子書

62 Express基本操作

express的使用也十分簡單先來建立一個基本的 hello world

var app = require(rsquoexpressrsquo)createServer()

port = 1337

applisten(port)

appget(rsquorsquo function(req res)

ressend(rsquohello worldrsquo)

)

consolelog(rsquostart express servernrsquo)

可以從上面的程式碼發現基本操作與 nodejs http的建立方式沒有太大差異主要差在當我們設定路由時可以直接透過 appget方式設定回應與接受方式

63 Express路由處理

Express對於 http服務上有許多包裝讓開發者使用及設定上更為方便例如有幾個路由設定那我們就統一藉由 appget來處理

Create http server

appget(rsquorsquo function(req res)

ressend(rsquohello worldrsquo)

)

appget(rsquotestrsquo function(req res)

ressend(rsquotest renderrsquo)

)

appget(rsquouserrsquo function(req res)

ressend(rsquouser pagersquo)

)

52 6 Express介紹

Nodejs中文電子書

如上面的程式碼所表示appget可以帶入兩個參數第一個是路徑名稱設定第二個為回應函式 (call back function)回應函式裡面就如同之前的 createServer方法裡面包含 requestresponse兩個物件可供使用使用者就可以透過瀏覽器輸入不同的 url切換到不同的頁面顯示不同的結果

路由設定上也有基本的配對方式讓使用者從瀏覽器輸入的網址可以

是一個變數只要符合型態就可以有對應的頁面產出例如

Create http server

appget(rsquouseridrsquo function(req res)

ressend(rsquouser rsquo + reqparamsid)

)

appget(rsquonumberrsquo function(req res)

ressend(rsquonumber rsquo + reqparamsnumber)

)

裡 面 使 用 到number 從 網 址 輸 入 之 後 就 可 以 直 接 使 用reqparamsnumber 取得所輸入的資料變成 url 參數使用當然前面也是可以加上路徑的設定userid在瀏覽器上路徑必須符合userxxx透

過 reqparamsid就可以取到 xxx這個字串值

另外express參數處理也提供了路由參數配對處理也可以透過正規表示法作為參數設定

var app = require(rsquoexpressrsquo)createServer()

port = 1337

applisten(port)

appget(^ip((d23)((d23))((d23))((d23))) function(req res)

ressend(reqparams)

)

上面程式碼可以發現後面路由設定的型態是正規表示法裡面設定

格式為ip之後必須要加上 ip型態才會符合資料格式同時取得 ip資料已經由正規表示法將資料做分群因此可以取得 ip的四個數字

此程式執行之後可以透過瀏覽器測試輸入網址為 localhost3000ip25525510010可以從頁面獲得資料

63 Express路由處理 53

Nodejs中文電子書

此章節全部範例程式碼如下

overview

author Caesar Chi

blog clonnblogspotcom

version 20120226

create server

var app = require(rsquoexpressrsquo)createServer()

port = 1337

applisten(port)

normal style

appget(rsquorsquo function(req res)

ressend(rsquohello worldrsquo)

)

appget(rsquotestrsquo function(req res)

ressend(rsquotest renderrsquo)

)

parameter style

appget(rsquouseridrsquo function(req res)

ressend(rsquouser rsquo + reqparamsid)

)

appget(rsquonumberrsquo function(req res)

ressend(rsquonumber rsquo + reqparamsnumber)

)

REGX style

appget(^ip((d23)((d23))((d23))) function(req res)

ressend(reqparams)

)

appget(rsquorsquo function(req res)

ressend(rsquoPage not foundrsquo 404)

54 6 Express介紹

Nodejs中文電子書

)

consolelog(rsquostart express servernrsquo)

64 Express middleware

Express 裡面有一個十分好用的應用概念稱為 middleware可以透過middleware做出複雜的效果同時上面也有介紹 next方法參數傳遞就是靠 middleware的概念來傳遞參數讓開發者可以明確的控制程式邏輯

create http server

appuse(expressbodyParser())

appuse(expressmethodOverride())

appuse(expresssession())

上面都是一種 middleware的使用方式透過 appuse方式裡面載入函式執行方法回應函式會包含三個基本參數responserequestnext其中next表示下一個 middleware執行函式同時會自動將預設三個參數繼續帶往下個函式執行底下有個實驗

上面的片段程式執行後開啟瀏覽器連結上 localhost1337會發現伺服器回應結果順序如下

first middle ware

second middle ware

execute middle ware

end middleware function

從上面的結果可以得知剛才設定的 middleware都生效了在 appuse設定的 middleware是所有 url皆會執行方法如果有指定特定方法就可以使用 appget的 middleware設定在 appget函式的第二個參數就可以帶入函式或者是匿名函式只要函式裡面最後會接受 request response next這三個參數同時也有正確指定 next函式的執行時機最後都會執行到最後一個方法當然開發者也可以評估程式邏輯要執行到哪一個階段讓邏輯

可以更為分明

64 Express middleware 55

Nodejs中文電子書

65 Express路由應用

在實際開發上可能會遇到需要使用參數等方式混和變數一起使用

express裡面提供了一個很棒的處理方法 appall這個方式可以先採用基本路由配對再將設定為每個不同的處理方式開發者可以透過這個方式簡

化自己的程式邏輯

overview

author

version 20120226

create server

var app = require(rsquoexpressrsquo)createServer()

port = 1337

users = [

name rsquoClonnrsquo

name rsquoChirsquo

]

applisten(port)

appall(rsquouseridoprsquo function(req res next)

requser = users[reqparamsid]

if (requser)

next()

else

next(new Error(rsquocannot find user rsquo + reqparamsid))

)

appget(rsquouseridrsquo function(req res)

ressend(rsquoviewing rsquo + requsername)

)

appget(rsquouserideditrsquo function(req res)

ressend(rsquoediting rsquo + requsername)

)

56 6 Express介紹

Nodejs中文電子書

appget(rsquouseriddeletersquo function(req res)

ressend(rsquodeleting rsquo + requsername)

)

appget(rsquorsquo function(req res)

ressend(rsquoPage not foundrsquo 404)

)

consolelog(rsquostart express servernrsquo)

內部宣告一組預設的使用者分別給予名稱設定藉由 appall這個方法可以先將路由雛形建立再接下來設定 appget的路徑格式只要符合格式就會分配進入對應的方法中像上面的程式當中如果使用者輸入路徑為user0除了執行 appall程式之後執行 next方法就會對應到路徑設定為userid的這個方法當中如果使用者輸入路徑為user0edit就會執行到useridedit的對應方法

66 Express GET應用範例

我們準備一個使用 GET方法傳送資料的表單

ltDOCTYPE htmlgt

lthtmlgt

ltheadgt

ltmeta http-equiv=rdquoContent-Typerdquo content=rdquotexthtml charset=utf8rdquogt

lttitlegtNodejs 菜鳥筆記 (1)lttitlegt

ltlink rel=rdquostylesheetrdquo href=rdquocssstylecssrdquo type=rdquotextcssrdquo media=rdquoallrdquo gt

ltheadgt

ltbodygt

lth1gtNodejs 菜鳥筆記-註冊lth1gt

ltform id=rdquosignuprdquo method=rdquoGETrdquo action=rdquohttplocalhost3000Signuprdquogt

ltlabelgt 使用者名稱ltlabelgtltinput type=rdquotextrdquo id=rdquousernamerdquo name=rdquousernamerdquo gtltbrgt

ltlabelgt 電子郵件ltlabelgtltinput type=rdquotextrdquo id=rdquoemailrdquo name=rdquoemailrdquo gtltbrgt

ltinput type=rdquosubmitrdquo value=rdquo 註冊我的帳號rdquo gtltbrgt

ltformgt

ltbodygt

lthtmlgt

66 Express GET應用範例 57

Nodejs中文電子書

這個表單沒有什麼特別的地方我們只需要看第 9 行form 使用的method是 GET然後 action是rdquohttplocalhost3000Signupldquo等一下我們要來撰寫Signup這個 URL Path的處理程式

處理 Signup行為

我們知道所謂的 GET方法會透過 URL來把表單的值給帶過去以上面的表單來說到時候 URL會以這樣的形式傳遞

httplocalhost3000Signupusername=xxxampemail=xxx

所以要能處理這樣的資料必須有以下功能

bull 解析 URL

bull 辨別動作是 Signup

bull 解析出 username和 email

一旦能取得 username和 email的值程式就能加以應用了

處理 Signup的程式碼雛形

load module

var url = require(rsquourlrsquo)

urlData = urlparse(requrltrue)

action = urlDatapathname

reswriteHead(200 rdquoContent-Typerdquordquotexthtml charset=utf-8rdquo)

if (action === rdquoSignuprdquo)

user = urlDataquery

resend(rdquolth1gtrdquo + userusername + rdquo 歡迎您的加入 lth1gtltpgt 我們已經將會員啟用信寄至rdquo + useremail + rdquoltpgtrdquo)

首先需要加載 url module它是用來協助我們解析 URL的模組接著使用 urlparse方法第一個傳入 url字串變數也就是 requrl另外第二個參數的用意是設為 ture則引進 querystring模組來協助處理預設是 false它影響到的是 urlDataquery設為 true會傳回物件不然就只是一般的字串urlparse會將字串內容整理成一個物件我們把它指定給 urlData

action變數作為記錄 pathname這是我們稍後要來判斷目前網頁的動作是什麼接著先將 html表頭資訊 (Header)準備好再來判斷路徑邏輯如

58 6 Express介紹

Nodejs中文電子書

果是 Signup這個動作就把 urlDataquery裡的資料指定給 user然後輸出userusername和 useremail把使用者從表單註冊的資料顯示於頁面中

最後進行程式測試啟動 nodejs主程式之後開啟瀏覽器就會看到表單填寫完畢按下送出就可以看到結果了

完整 nodejs程式碼如下

var http = require(rsquohttprsquo)

url = require(rsquourlrsquo)

fs = require(rdquofsrdquo)

server

server = httpcreateServer(function (reqres)

var urlData

encode = rdquoutf8rdquo

filePath = rdquoviewexpress_get_example_formhtmlrdquo

action

urlData = urlparse(requrltrue)

action = urlDatapathname

reswriteHead(200 rdquoContent-Typerdquordquotexthtml charset=utf-8rdquo)

if (action === rdquoSignuprdquo)

user = urlDataquery

resend(rdquolth1gtrdquo + userusername + rdquo 歡迎您的加入 lth1gtltpgt 我們已經將會員啟用信寄至rdquo + useremail + rdquoltpgtrdquo)

else

fsreadFile(filePath encode function(err file)

reswrite(file)

resend()

)

)

serverlisten(3000)

consolelog(rsquoServer 跑起來了現在時間是rsquo + new Date())

66 Express GET應用範例 59

Nodejs中文電子書

67 Express POST應用範例

一開始準備基本的 html表單傳送內容以 POST方式form的 action屬性設定為 POST其餘 html內容與前一個範例應用相同

ltDOCTYPE htmlgt

lthtmlgt

ltheadgt

ltmeta http-equiv=rdquoContent-Typerdquo content=rdquotexthtml charset=utf8rdquogt

lttitlegtNodejs 菜鳥筆記 (1)lttitlegt

ltlink rel=rdquostylesheetrdquo href=rdquocssstylecssrdquo type=rdquotextcssrdquo media=rdquoallrdquo gt

ltheadgt

ltbodygt

lth1gtNodejs 菜鳥筆記-註冊lth1gt

ltform id=rdquosignuprdquo method=rdquoPOSTrdquo action=rdquohttplocalhost3000Signuprdquogt

ltlabelgt 使用者名稱ltlabelgtltinput type=rdquotextrdquo id=rdquousernamerdquo name=rdquousernamerdquo gtltbrgt

ltlabelgt 電子郵件ltlabelgtltinput type=rdquotextrdquo id=rdquoemailrdquo name=rdquoemailrdquo gtltbrgt

ltinput type=rdquosubmitrdquo value=rdquo 註冊我的帳號rdquo gtltbrgt

ltformgt

ltbodygt

lthtmlgt

nodejs的程式處理邏輯與前面 GET範例類似部分程式碼如下

qs = require(rsquoquerystringrsquo)

if (action === rdquoSignuprdquo)

formData = rsquorsquo

reqon(rdquodatardquo function (data)

formData += data

)

reqon(rdquoendrdquo function ()

user = qsparse(formData)

resend(rdquolth1gtrdquo + userusername + rdquo 歡迎您的加入 lth1gtltpgt 我們已經將會員啟用信寄至rdquo + useremail + rdquoltpgtrdquo)

)

60 6 Express介紹

Nodejs中文電子書

主要加入了rsquoquerystringrsquo這個moduel方便我們等一下解析由表單 POST回來的資料另外加入一個 formData的變數用來搜集待等一下表單回傳的資料前面的 GET範例我們只從 req拿出 url的資料這次要在利用req身上的事件處理

JavaScript 在訂閱事件時使用 addEventListener而 nodejs 使用的則是on這邊加上了監聽 data的事件會在瀏覽器傳送資料到Web Server時被執行參數是它所接收到的資料型態是字串

接著再增加 end的事件當瀏覽器的請求事件結束時它就會動作

由於瀏覽器使用 POST在上傳資料時會將資料一塊塊地上傳因為我們在監聽 data事件時透過 formData變數將它累加起來lt不過由於我們

上傳的資料很少一次就結束不過如果日後需要傳的是資料比較大的檔

案這個累加動作就很重要

當資料傳完就進到 end 事件中會用到 qsparse 來解析 formDataformData的內容是字串內容是

username=wordsmithampemail=wordsmith40somewhere

而 qsparse可以幫我們把這個 querystring轉成物件的格式也就是

username=wordsmithampemail=wordsmith40somewhere

一旦轉成物件並指定給 user之後其他的事情就和 GET方法時操作的一樣寫 response的表頭將內容回傳並將 userusername和 useremail代入到內容中

修改完成後接著執行 nodejs程式啟動 web server開啟瀏覽器進入表單測試看看POST的方式能否順利運作

完整程式碼如下

var http = require(rsquohttprsquo)

url = require(rsquourlrsquo)

fs = require(rdquofsrdquo)

qs = require(rsquoquerystringrsquo)

server

server = httpcreateServer(function (reqres)

var urlData

67 Express POST應用範例 61

Nodejs中文電子書

encode = rdquoutf8rdquo

filePath = rdquoviewexpress_post_example_formhtmlrdquo

formData

action

urlData = urlparse(requrltrue)

action = urlDatapathname

reswriteHead(200 rdquoContent-Typerdquordquotexthtml charset=utf-8rdquo)

if (action === rdquoSignuprdquo)

formData = rsquorsquo

reqon(rdquodatardquo function (data)

formData += data

)

reqon(rdquoendrdquo function ()

user = qsparse(formData)

resend(rdquolth1gtrdquo + userusername + rdquo 歡迎您的加入 lth1gtltpgt 我們已經將會員啟用信寄至rdquo + useremail + rdquoltpgtrdquo)

)

else

fsreadFile(filePath encode function(err file)

reswrite(file)

resend()

)

)

serverlisten(3000)

consolelog(rsquoServer 跑起來了現在時間是rsquo + new Date())

62 6 Express介紹

Nodejs中文電子書

68 Express AJAX應用範例

在 Nodejs要使用 Ajax傳送資料並且與之互動在接受資料的部份沒有太大的差別client端不是用 GET就是用 POST來傳資料重點在處理完後用 JSON格式回傳當然 Ajax不見得只傳 JSON格式有時是回傳一段 HTML碼不過後者對伺服器來說基本上就和前兩篇沒有差別了所以我們還是以回傳 JSON做為這一回的主題

這一回其實大多數的工作都會落在前端 Ajax上面前端要負責發送與接收資料並在接收資料後撤掉原先發送資料的表單並將取得的資料

改成 HTML格式之後放上頁面

首先先準備 HTML靜態頁面資料

ltDOCTYPE htmlgt

lthtmlgt

ltheadgt

lttitlegtNodejs 菜鳥筆記 (3)lttitlegt

ltlink rel=rdquostylesheetrdquo href=rdquocssstylecssrdquo type=rdquotextcssrdquo media=rdquoallrdquo gt

ltscript src=rdquohttpsajaxgoogleapiscomajaxlibsjquery171jqueryminjsrdquogtltscriptgt

ltheadgt

ltbodygt

lth1gtNodejs 菜鳥筆記-註冊 (用 Ajax)lth1gt

ltform id=rdquosignuprdquo action=rdquoSignuprdquo method=rdquoPOSTrdquogt

ltlabelgt 使用者名稱ltlabelgtltinput type=rdquotextrdquo id=rdquousernamerdquo name=rdquousernamerdquo gtltbrgt

ltlabelgt 電子郵件ltlabelgtltinput type=rdquotextrdquo id=rdquoemailrdquo name=rdquoemailrdquo gtltbrgt

ltinput type=rdquosubmitrdquo value=rdquo 註冊我的帳號rdquo gtltbrgt

ltformgt

ltdiv id=rdquoresultrdquogt

ltdivgt

ltscript type=rdquotextjavascriptrdquogt

$(function()$(rsquosignup input[type=rdquosubmitrdquo]rsquo)click(function(e)

var user =

userusername = $(rdquousernamerdquo)val()useremail = $(rdquoemailrdquo)val()

$post(rdquoSignuprdquouserfunction(data)greet(data)

)

68 Express AJAX應用範例 63

Nodejs中文電子書

epreventDefault()

)

var greet = function(msg)

var resultNode = $(rsquoresultrsquo)greeting = $(rsquolth2gtHirsquo+msgusername+rsquo 你的會員 id 是rsquo+ msgid +rsquo 我們會將會員啟動信寄至rsquo+msgemail+rsquolth2gtrsquo)

$(rsquosignuprsquo)hide()resultNodehtml(rdquordquo)

resultNodeappend(greeting)

)

ltscriptgt

ltbodygt

lthtmlgt

HTML頁面上準備了一個表單用來傳送註冊資料接著直接引用了Google CDN來載入 jQuery用來幫我們處理 Ajax的工作這次要傳送和接收的工作很大的變動都在 HTML頁面上的 JavaScript當中我們要做的事有 (相關 jQuery處理這邊不多做贅述指提起主要功能解說)

bull 用 jQUery取得 submit按鈕綁定它的 click動作

bull 取得表單 username和 email的值存放在 user這個物件中

bull 用 jQuery的 $post方法將 user的資料傳到 Server

bull 一旦成功取得資料後透過 greet這個 function組成回報給 user的訊息

bull 清空原本給使用者填資料的表單

bull 將 Server回傳的 usernameemail和 id這 3個資料組成回應的訊息

bull 將訊息放到原本表單的位置

經過以上的處理後一個 Ajax的表單的基本功能已經完成

接著進行 nodejs主要程式的編輯部分程式碼如下

var fs = require(rdquofsrdquo)

64 6 Express介紹

Nodejs中文電子書

qs = require(rsquoquerystringrsquo)

if (action === rdquoSignuprdquo)

formData = rsquorsquo

reqon(rdquodatardquo function (data)

formData += data

)

reqon(rdquoendrdquo function ()

var msg

user = qsparse(formData)

userid = rdquo123456rdquo

msg = JSONstringify(user)

reswriteHead(200 rdquoContent-Typerdquordquoapplicationjson charset=utf-8rdquordquoContent-Lengthrdquomsglength)

resend(msg)

)

這裡的程式和前面 POST範例基本上大同小異差別在

bull 幫 user的資料加上 id隨意存放一些文字進去讓 Server回傳的資料多於 Client端傳上來的不然會覺得 Server都沒做事

bull 增加了 msg 這個變數存放將 user 物件 JSON 文字化的結果JSONstringify這個轉換函式是 V8引擎所提供的如果你好奇的話

bull 大重點來了我們要告訴 Client端這次回傳的資料格式是 JSON所在 Content-type和 Content-Length要提供給 Client

Server很輕鬆就完成任務了最後進行程式測試啟動 nodejs主程式之後開啟瀏覽器就會看到表單填寫完畢按下送出就可以看到結果了

最後 nodejs本篇範例程式碼如下

var http = require(rsquohttprsquo)

url = require(rsquourlrsquo)

fs = require(rdquofsrdquo)

qs = require(rsquoquerystringrsquo)

server

68 Express AJAX應用範例 65

Nodejs中文電子書

server = httpcreateServer(function (reqres)

var urlData

encode = rdquoutf8rdquo

filePath = rdquoviewexpress_ajax_example_formhtmlrdquo

formData

action

urlData = urlparse(requrltrue)

action = urlDatapathname

if (action === rdquoSignuprdquo)

formData = rsquorsquo

reqon(rdquodatardquo function (data)

formData += data

)

reqon(rdquoendrdquo function ()

var msg

user = qsparse(formData)

userid = rdquo123456rdquo

msg = JSONstringify(user)

reswriteHead(200 rdquoContent-TyperdquordquoapplicationjsonrdquordquoContent-Lengthrdquomsglength)

resend(msg)

)

else

fsreadFile(filePath encode function(err file)

reswriteHead(200 rdquoContent-Typerdquordquotexthtml charset=utf-8rdquo)

reswrite(file)

resend()

)

)

serverlisten(3000)

66 6 Express介紹

Nodejs中文電子書

consolelog(rsquoServer 跑起來了現在時間是rsquo + new Date())

69 原始資料提供

bull [NodeJS初學者筆記 (1)-用 GET傳送資料] (httpithelpithomecomtwquestion10087402)

bull [NodeJS初學者筆記 (2)-用 POST傳送資料] (httpithelpithomecomtwquestion10087489)

bull [NodeJS 初學者筆記 (3)-用 Ajax 傳送資料] (httpithelpithomecomtwquestion10087627)

69 原始資料提供 67

Nodejs中文電子書

68 6 Express介紹

7

CoffeeScript

bull Spine

bull Hem

bull Spineapp

Letrsquos Have a Cup of CoffeeScript

bull es5-shim ECMAScript 5 compatibility shims for legacy JavaScript engines

ESnext

node-iform - A middleware for node-validator httpsgithubcomguileennode-iform

69

Nodejs中文電子書

70 7 CoffeeScript

8

製作一個 Hubot的 Plurk Adapter

81 應用事項提醒

此應用範例以CoffeeScript編寫主要程式架構採用Github釋放的Hubot專案以下有幾個主要注意事項請讀者注意

bull Hubot 一開始的架構除了內建的兩個 Adapter 之外其餘都要以Nodejs的Module方式才能運作

bull Hubot的 binhubot裡面寫著 npm install所以不管你怎麼改原始碼也不能改變第一點的狀況

其實上述都在討論同一件事情NodeJS的模組而且模組不能設定相對路徑之類的來安裝一定要包裝成 npm相符和格式才有辦法正確執行

82 建立 Adapter

首先需要準備兩個檔案

bull packagejson

bull plurkcoffee

71

Nodejs中文電子書

有這兩個就足以變成 NodeJS的模組了在開始 Coding之前先來設定一下 packagejson相依性

rdquonamerdquo rdquohubot-plurkrdquo

rdquoversionrdquo rdquo011rdquo

rdquomainrdquo rdquoplurkrdquo

rdquodependenciesrdquo

rdquohubotrdquo rdquogt=205rdquo

rdquooauthrdquo rdquordquo

rdquocronrdquordquordquo

這邊會使用到NodeJS的 cron模組(Module)而登入 Plurk需要OAuth標準授權才行所以也需要加載 OAuth模組

注意Hubot在讀取非內建模組時會自動在前面加上 hubot-的前置

83 建立 Robot跟 API

本篇應用基本上程式碼大部分參考 Hubot - Twitter Adapter來製作主要差異只有在使用 OAuth這個模組Twitter有 Streaming可用而 Plurk則得用 Comet方式來達到即時讀取

plurkcoffee程式碼

Robot = require(rdquohubotrdquo)robot()

Adapter = require(rdquohubtordquo)adapter()

EventEmitter = require(rdquoeventsrdquo)EventEmitter

oauth = require(rdquooauthrdquo)

cronJob = require(rdquocronrdquo)CronJob

class Plurk exntends Adapter

class PlurkStreaming exnteds EventEmitter

72 8 製作一個 Hubot的 Plurk Adapter

Nodejs中文電子書

先弄個基本架構主要 Class皆參考 Twitter Adapter命名方式接著再將 Plurk這個 Class增加幾個Method基本上只要有 run send reply就夠了而 run用來做初始化的部分

class Plurk entends Adapter

send (plurk id strings⋯) -gt

reply (plurk id strings⋯) -gt

run -gt

看起來有點東西接著來處理主要的 Plurk API結合部分

class PlurkStreaming extends EventEmitter

consuctor (options) -gt

plurk (callback) -gt

觀察河道

getChannel -gt

取得 Comet 網址

reply (plurk id message) -gt

回噗

acceptFriends -gt

接受好友

get (path callback) -gt

GET 請求

post (path body callback)-gt

POST 請求(其實是裝飾)

request (method path body callback)-gt

主要的 OAuth 請求

comet (server callback)-gt

噗浪的 Comet 傳回是 JavaScript Callback 要另外處理後才會變成 JSON

然後把注意力集中到 constructor上先把建構子弄好

constructor (options) -gt

super()

if optionskey and optionssecret and optionstoken and optionstoken secret

key = optionskey

secret = optionssecret

token = optionstoken

token secret = optionstoken secret

83 建立 Robot跟 API 73

Nodejs中文電子書

建立 OAuth 連接

consumer = new oauthOAuth(

rdquohttpwwwplurkcomOAuthrequest tokenrdquo

rdquohttpwwwplurkcomOAuthaccess tokenrdquo

key

secret

rdquo10rdquo

rdquohttpwwwplurkcomOAuthauthorizerdquo

rdquoHMAC-SHA1rdquo

)

domain = rdquowwwplurkcomrdquo

初始化取得 Comet 網址

do getChannel

else

throw new Error(rdquo 參數不足需要 Key Secret Token Token Secretrdquo)

接著來處理 request這個 method

request (method path body callback) -gt

記錄一下這次的 Request

consolelog(rdquohttpdomainpathrdquo)

Callback 這邊先不丟進去要用另一種方式處理

request = consumerget(rdquohttpdomainpathrdquo token token secret null)

requeston rdquoresponserdquo (res) -gt

reson rdquodatardquo (chunk) -gt

parseResponse(chunk+rsquorsquo callback)

reson rdquoendrdquo (data) -gt

consolelog rdquoEnd Request pathrdquo

reson rdquoerrorrdquo (data) -gt

consolelog rdquoError rdquo + data

requestend()

處理資料

parseResponse = (data callback) -gt

if datalength gt 0

用 TryCatch 避免處理 JSON 出錯導致整個中斷

try

callback null JSONparse(data)

catch err

74 8 製作一個 Hubot的 Plurk Adapter

Nodejs中文電子書

consolelog(rdquoError Parse JSONrdquo + data err)

繼續執行

callback null data ||

大致上就是這樣上面程式的架構已經將整個 Hubot Plurk Adapter完成因為在測試時竟然因為噗浪 Lag而沒讀到完整的 Comet資料然後造成程式異常為了避免這個問題發生因此需要加上為了完美呈現需要再

加上 Comet的處理所以要使用到 EventEmitter的功能

comet (server callback) -gt

在 Callback 裡面會找不到自身所以設定區域變數

self =

記錄一下這次的 Request

consolelog(rdquo[Comet] serverrdquo)

Callback 這邊先不丟進去要用另一種方式處理

request = consumerget(rdquohttpdomainpathrdquo token token secret null)

requeston rdquoresponserdquo (res) -gt

reson rdquodatardquo (chunk) -gt

parseResponse(chunk+rsquorsquo callback)

reson rdquoendrdquo (data) -gt

consolelog rdquoEnd Request pathrdquo

請求結束發出事件通知可以進行下一次請求

selfemit rdquonextPlurkrdquo

reson rdquoerrorrdquo (data) -gt

consolelog rdquoError rdquo + data

requestend()

處理資料

parseResponse = (data callback) -gt

if datalength gt 0

用 trycatch 避免失敗中斷

try

去掉 JavaScript 的 Callback

data = datamatch(CometChannelscriptCallback((+))s)

jsonData = rdquordquo

if data

83 建立 Robot跟 API 75

Nodejs中文電子書

jsonData = JSONparse(data[1])

else

如果沒有任何 Match 嘗試直接 parse

jsonData = JSONparse(data)

catch err

consolelog(rdquo[Comet] Errorrdquo data err)

用 TryCatch 避免處理 JSON 出錯導致整個中斷

try

只傳入 json 的 data 部分

callback null jsonDatadata

catch err

consolelog(rdquo[Comet]Error Parse JSONrdquo + data err)

繼續執行

callback null data ||

後面的 get跟 post就簡單多了

get (path callback) -gt

request(rdquoGETrdquo path null callback)

post (path body callback) -gt

request(rdquoPOSTrdquo path body callback)

接著處理取的 Comet網址的 getChannel

getChannel -gt

self =

get rdquoAPPRealtimegetUserChannelrdquo (error data) -gt

if error

檢查是否有 comet server

if datacomet server

selfchannel = datacomet server

如果沒有 Channel Ready 就嘗試連接會失敗

selfemit(rsquochannel readyrsquo)

那麼先來處理 Plurk Adaper好處理的部份

send (plruk id strings⋯)-gt

跟 Reply 一樣直接交給 reply 做

reply plurk id strings⋯

76 8 製作一個 Hubot的 Plurk Adapter

Nodejs中文電子書

reply (plurk id strings⋯) -gt

stringsforEach (message) =gt

botreply(plruk id message)

接著把 run處理好就可以上線運作摟

run -gt

self =

options =

key processenvHUBOT PLURK KEY

secret processenvHUBOT PLURK SECRET

token processenvHUBOT PLURK TOKEN

token secret processenvHUBOT PLURK TOKEN SECRET

創建剛剛的 API

bot = new PlurkStreaming(options)

依照 Twitter 的 new RobotTextMessage 會沒有反應所以參考 hubot-minecraft 的方式

r = robotconstructor

處理噗浪河道訊息

doPlurk = (data)-gt

檢查是否為回噗

if dataresponse

datacontent raw = dataresponsecontent raw

datauser id = dataresponseuser id

確定有噗浪 ID 跟訊息

if dataplurk id and datacontent raw

selfreceive new rTextMessage(dataplurk id datacontent raw)

取得 Comet Server 完成開始第一次 Comet 連接

boton rdquochannel readyrdquo () -gt

botplurk selfdoPlurk

上一次 Comet 完成繼續 Polling

boton rdquonextPlurkrdquo ()-gt

botplurk selfdoPlurk

定時接受好友邀請

do botacceptFriends

bot = bot

83 建立 Robot跟 API 77

Nodejs中文電子書

終於完成 AdapterHubot專案裡面的 scripts資料夾內是互動部分不需要像 Adapter如此大費周章處理只新增檔案並且設計好對白之後就會回噗了我開發用的機器人在此大家可以去跟他玩玩[httpplurkcomelct9620 bot](httpplurkcomelct9620 bot)

84 原始資料提供

bull [製 作 一 個 Hubot 的 噗 浪 Adapter](http revo-skillfrosttw blog20120318create-a-hubot-plurk-adapter)

78 8 製作一個 Hubot的 Plurk Adapter

9

Nodejs好用工具介紹

91 logio

Real-time log monitoring in your browser

httplogioorg

nodejs (gt=0412 lt=0611)

npm config set unsafe-perm true

npm install -g --prefix=usrlocal logio

logio server start

httplocalhost8998

79

Nodejs中文電子書

80 9 Nodejs好用工具介紹

10

精選文章

101 Express

bull 使用 NodeJS + Express從 GETPOST Request取值 [邀稿中]

bull link2 [邀稿中]

bull link3 [邀稿中]

81

Nodejs中文電子書

82 10 精選文章

11

參考資源

111 Nodejs書籍

bull Programming Nodejs April 2012 (est) Travis Swicegood Pragmatic Bookshelf

bull e Little Book on CoffeeScript January 2012 Alex MacCaw OrsquoReilly Media

bull Node for Front-End Developers January 2012 Garann Means OrsquoReilly Media

bull Building Hypermedia APIs with HTML5 and Node November 2011 MikeAmundsen OrsquoReilly Media

bull Node Web Development August 2011 David Herron PacktPub

bull CoffeeScript Accelerated JavaScript Development July 2011 Trevor BurnhamPragmatic Bookshelf

bull Getting Started with GEO CouchDB and Nodejs July 2011 Mick omp-son OrsquoReilly Media

bull What Is Node July 2011 Brett McLaughlin OrsquoReilly Media

bull Node Up and Running May 2011 Tom Hughes-Croucher Mike WilsonOrsquoReilly Media

83

Nodejs中文電子書

bull Hands-on Nodejs e Nodejs introduction and API reference May 2011 Pe-dro Teixeira LeanPub

bull e Node Beginner Book Manuel Kiessling

bull Mastering Nodejs visionmedia

112 Nodejs影音教學

bull e Node Sessions e Best of OSCON 2011 August 2011 OrsquoReilly Media(Video)

bull Tom Hughes-Croucher on Node March 2011 OrsquoReilly Media (Video)

113 Nodejs教學網站

bull Node Tuts

114 Nodejs課程

bull Introduction To Nodejs Van Nguyen CodeLesson

84 11 參考資源

Page 52: Nodejs Wiki

Nodejs中文電子書

找到需要的套件後(例如 express)即可使用以下指令安裝

npm install coffee-script

值得注意的一點是使用 npm install會將指定的套件安裝在工

作目錄(Working Directory)的 node modules資料夾下

以Windows為例如果執行 npm install的目錄位於

Cproject1

那麼 npm將會自動建立一個 node modules的子目錄(如果不存在)

Cproject1node modules

並且將下載的套件放置於這個子目錄例如

Cproject1node modulescoffee-script

這個設計讓專案可以個別管理相依的套件並且可以在專案佈署或發

行時將這些套件(位於 node modules)一併打包方便其它專案的使用者不必再重新下載套件

這個 npm install的預設安裝模式為 local(本地)只會變更當前專案的資料夾不會影響系統

另一種安裝模式稱為 global(全域)這種模式會將套件安裝到系統資

料夾也就是 npm安裝路徑的 node modules資料夾例如

CProgram Filesnodejsnode modules

46 5 NPM套件管理工具

Nodejs中文電子書

是否要使用全域安裝可以依照套件是否提供新指令來判斷舉例來說express 套件提供 express 這個指令而 coffee-script 則提供 coffee

指令

在 local 安 裝 模 式 中這 些 指 令 的 程 式 檔 案會 被 安 裝 到node modules的 bin這個隱藏資料夾下除非將bin的路徑加入 PATH環境變數否則要執行這些指令將會相當不便

為了方便指令的執行我們可以在 npm install 加上 -g 或 --

global參數啟用 global安裝模式例如

npm install -g coffee-script

npm install -g express

使用 global安裝模式需要注意執行權限的問題若權限不足可能會出現類似以下的錯誤訊息

npm ERR Error EACCES permission denied rsquorsquo

npm ERR

npm ERR Please try running this command again as rootAdministrator

要獲得足夠得執行權限請參考以下說明

bull Windows 7 或 2008 以上在「命令提示字元」的捷徑按右鍵選擇「以系統管理員身分執行」執行 npm指令時就會具有 Administrator身分

bull Mac OS X或 Linux系統可以使用 sudo指令例如

sudo npm install -g express

bull Linux系統可以使用 root權限登入或是以「sudo su -」切換成 root身分(使用 root權限操作系統相當危險因此並不建議使用這種方式)

若加上 -g參數使用 npm install -g coffee-script完成安裝

後就可以在終端機執行 coffee指令例如

coffee -v

52 使用 NPM安裝套件 47

Nodejs中文電子書

執行結果(範例)

CoffeeScript version 120

53 套件的更新及維護

除了前一節說明的 search 及 install 用法npm 還提供其他許多指令(commands)

使用 npm help可以查詢可用的指令

npm help

執行結果(部分)

where ltcommandgt is one of

adduser apihelp author bin bugs c cache completion

config deprecate docs edit explore faq find get

help help-search home i info init install la link

list ll ln login ls outdated owner pack prefix

prune publish r rb rebuild remove restart rm root

run-script s se search set show star start stop

submodule tag test un uninstall unlink unpublish

unstar up update version view whoami

使用 npm help command可以查詢指令的詳細用法例如

npm help list

接下來本節要介紹開發過程常用的 npm指令

使用 list可以列出已安裝套件

npm list

48 5 NPM套件管理工具

Nodejs中文電子書

執行結果(範例)

-- coffee-script120

- express256

- connect185

| -- formidable108

-- mime124

-- mkdirp007

-- qs041

檢視某個套件的詳細資訊例如

npm show express

升級所有套件(如果該套件已發佈更新版本)

npm update

升級指定的套件

npm update express

移除指定的套件

npm uninstall express

54 使用 packagejson

對於正式的 Nodejs專案可以建立一個命名為 packagejson的設

定檔(純文字格式)檔案內容參考範例如下

54 使用 packagejson 49

Nodejs中文電子書

packagejson(範例)

rdquonamerdquo rdquoapplication-namerdquo

rdquoversionrdquo rdquo001rdquo

rdquoprivaterdquo true

rdquodependenciesrdquo

rdquoexpressrdquo rdquo255rdquo

rdquocoffee-scriptrdquo rdquolatestrdquo

rdquomongooserdquo rdquogt= 253rdquo

其中 name與 version依照專案的需求設置

需要注意的是 dependencies的設定它用於指定專案相依的套件名

稱及版本

bull rdquoexpressrdquo rdquo255rdquo

代表此專案相依版本 255的 express套件

bull rdquocoffee-scriptrdquo rdquolatestrdquo

使用最新版的 coffee-script套件(每次更新都會檢查新版)

bull rdquomongooserdquo rdquogt= 253rdquo

使用版本大於 253的 mongoose套件

假設某個套件的新版可能造成專案無法正常運作就必須指定套件的

版本避免專案的程式碼來不及更新以相容新版套件通常在開發初期的

專案需要盡可能維持新套件的相容性(以取得套件的更新或修正)可以

用「gt=」設定最低相容的版本或是使用「latest」設定永遠保持最新

套件

50 5 NPM套件管理工具

6

Express介紹

在前面的 nodejs基礎當中介紹許多許多開設 http的使用方法及介紹以及許多基本的 nodejs基本應用

接下來要介紹一個套件稱為 express [Express](httpexpressjscom)這個套件主要幫忙解決許多 nodejs http server所需要的基本服務讓開發 httpservice變得更為容易不需要像之前需要透過層層模組(module)才有辦法開始編寫自己的程式

這個套件是由 TJ Holowaychuk 製作而成的套件裡面包含基本的路由處理 (route)http資料處理(GETPOSTPUT)另外還與樣板套件(jshtml template engine)搭配同時也可以處理許多複雜化的問題

61 Express安裝

安裝方式十分簡單只要透過之前介紹的 NPM就可以使用簡單的指令安裝指令如下

這邊建議需要將此套件安裝成為全域模組方便日後使用

51

Nodejs中文電子書

62 Express基本操作

express的使用也十分簡單先來建立一個基本的 hello world

var app = require(rsquoexpressrsquo)createServer()

port = 1337

applisten(port)

appget(rsquorsquo function(req res)

ressend(rsquohello worldrsquo)

)

consolelog(rsquostart express servernrsquo)

可以從上面的程式碼發現基本操作與 nodejs http的建立方式沒有太大差異主要差在當我們設定路由時可以直接透過 appget方式設定回應與接受方式

63 Express路由處理

Express對於 http服務上有許多包裝讓開發者使用及設定上更為方便例如有幾個路由設定那我們就統一藉由 appget來處理

Create http server

appget(rsquorsquo function(req res)

ressend(rsquohello worldrsquo)

)

appget(rsquotestrsquo function(req res)

ressend(rsquotest renderrsquo)

)

appget(rsquouserrsquo function(req res)

ressend(rsquouser pagersquo)

)

52 6 Express介紹

Nodejs中文電子書

如上面的程式碼所表示appget可以帶入兩個參數第一個是路徑名稱設定第二個為回應函式 (call back function)回應函式裡面就如同之前的 createServer方法裡面包含 requestresponse兩個物件可供使用使用者就可以透過瀏覽器輸入不同的 url切換到不同的頁面顯示不同的結果

路由設定上也有基本的配對方式讓使用者從瀏覽器輸入的網址可以

是一個變數只要符合型態就可以有對應的頁面產出例如

Create http server

appget(rsquouseridrsquo function(req res)

ressend(rsquouser rsquo + reqparamsid)

)

appget(rsquonumberrsquo function(req res)

ressend(rsquonumber rsquo + reqparamsnumber)

)

裡 面 使 用 到number 從 網 址 輸 入 之 後 就 可 以 直 接 使 用reqparamsnumber 取得所輸入的資料變成 url 參數使用當然前面也是可以加上路徑的設定userid在瀏覽器上路徑必須符合userxxx透

過 reqparamsid就可以取到 xxx這個字串值

另外express參數處理也提供了路由參數配對處理也可以透過正規表示法作為參數設定

var app = require(rsquoexpressrsquo)createServer()

port = 1337

applisten(port)

appget(^ip((d23)((d23))((d23))((d23))) function(req res)

ressend(reqparams)

)

上面程式碼可以發現後面路由設定的型態是正規表示法裡面設定

格式為ip之後必須要加上 ip型態才會符合資料格式同時取得 ip資料已經由正規表示法將資料做分群因此可以取得 ip的四個數字

此程式執行之後可以透過瀏覽器測試輸入網址為 localhost3000ip25525510010可以從頁面獲得資料

63 Express路由處理 53

Nodejs中文電子書

此章節全部範例程式碼如下

overview

author Caesar Chi

blog clonnblogspotcom

version 20120226

create server

var app = require(rsquoexpressrsquo)createServer()

port = 1337

applisten(port)

normal style

appget(rsquorsquo function(req res)

ressend(rsquohello worldrsquo)

)

appget(rsquotestrsquo function(req res)

ressend(rsquotest renderrsquo)

)

parameter style

appget(rsquouseridrsquo function(req res)

ressend(rsquouser rsquo + reqparamsid)

)

appget(rsquonumberrsquo function(req res)

ressend(rsquonumber rsquo + reqparamsnumber)

)

REGX style

appget(^ip((d23)((d23))((d23))) function(req res)

ressend(reqparams)

)

appget(rsquorsquo function(req res)

ressend(rsquoPage not foundrsquo 404)

54 6 Express介紹

Nodejs中文電子書

)

consolelog(rsquostart express servernrsquo)

64 Express middleware

Express 裡面有一個十分好用的應用概念稱為 middleware可以透過middleware做出複雜的效果同時上面也有介紹 next方法參數傳遞就是靠 middleware的概念來傳遞參數讓開發者可以明確的控制程式邏輯

create http server

appuse(expressbodyParser())

appuse(expressmethodOverride())

appuse(expresssession())

上面都是一種 middleware的使用方式透過 appuse方式裡面載入函式執行方法回應函式會包含三個基本參數responserequestnext其中next表示下一個 middleware執行函式同時會自動將預設三個參數繼續帶往下個函式執行底下有個實驗

上面的片段程式執行後開啟瀏覽器連結上 localhost1337會發現伺服器回應結果順序如下

first middle ware

second middle ware

execute middle ware

end middleware function

從上面的結果可以得知剛才設定的 middleware都生效了在 appuse設定的 middleware是所有 url皆會執行方法如果有指定特定方法就可以使用 appget的 middleware設定在 appget函式的第二個參數就可以帶入函式或者是匿名函式只要函式裡面最後會接受 request response next這三個參數同時也有正確指定 next函式的執行時機最後都會執行到最後一個方法當然開發者也可以評估程式邏輯要執行到哪一個階段讓邏輯

可以更為分明

64 Express middleware 55

Nodejs中文電子書

65 Express路由應用

在實際開發上可能會遇到需要使用參數等方式混和變數一起使用

express裡面提供了一個很棒的處理方法 appall這個方式可以先採用基本路由配對再將設定為每個不同的處理方式開發者可以透過這個方式簡

化自己的程式邏輯

overview

author

version 20120226

create server

var app = require(rsquoexpressrsquo)createServer()

port = 1337

users = [

name rsquoClonnrsquo

name rsquoChirsquo

]

applisten(port)

appall(rsquouseridoprsquo function(req res next)

requser = users[reqparamsid]

if (requser)

next()

else

next(new Error(rsquocannot find user rsquo + reqparamsid))

)

appget(rsquouseridrsquo function(req res)

ressend(rsquoviewing rsquo + requsername)

)

appget(rsquouserideditrsquo function(req res)

ressend(rsquoediting rsquo + requsername)

)

56 6 Express介紹

Nodejs中文電子書

appget(rsquouseriddeletersquo function(req res)

ressend(rsquodeleting rsquo + requsername)

)

appget(rsquorsquo function(req res)

ressend(rsquoPage not foundrsquo 404)

)

consolelog(rsquostart express servernrsquo)

內部宣告一組預設的使用者分別給予名稱設定藉由 appall這個方法可以先將路由雛形建立再接下來設定 appget的路徑格式只要符合格式就會分配進入對應的方法中像上面的程式當中如果使用者輸入路徑為user0除了執行 appall程式之後執行 next方法就會對應到路徑設定為userid的這個方法當中如果使用者輸入路徑為user0edit就會執行到useridedit的對應方法

66 Express GET應用範例

我們準備一個使用 GET方法傳送資料的表單

ltDOCTYPE htmlgt

lthtmlgt

ltheadgt

ltmeta http-equiv=rdquoContent-Typerdquo content=rdquotexthtml charset=utf8rdquogt

lttitlegtNodejs 菜鳥筆記 (1)lttitlegt

ltlink rel=rdquostylesheetrdquo href=rdquocssstylecssrdquo type=rdquotextcssrdquo media=rdquoallrdquo gt

ltheadgt

ltbodygt

lth1gtNodejs 菜鳥筆記-註冊lth1gt

ltform id=rdquosignuprdquo method=rdquoGETrdquo action=rdquohttplocalhost3000Signuprdquogt

ltlabelgt 使用者名稱ltlabelgtltinput type=rdquotextrdquo id=rdquousernamerdquo name=rdquousernamerdquo gtltbrgt

ltlabelgt 電子郵件ltlabelgtltinput type=rdquotextrdquo id=rdquoemailrdquo name=rdquoemailrdquo gtltbrgt

ltinput type=rdquosubmitrdquo value=rdquo 註冊我的帳號rdquo gtltbrgt

ltformgt

ltbodygt

lthtmlgt

66 Express GET應用範例 57

Nodejs中文電子書

這個表單沒有什麼特別的地方我們只需要看第 9 行form 使用的method是 GET然後 action是rdquohttplocalhost3000Signupldquo等一下我們要來撰寫Signup這個 URL Path的處理程式

處理 Signup行為

我們知道所謂的 GET方法會透過 URL來把表單的值給帶過去以上面的表單來說到時候 URL會以這樣的形式傳遞

httplocalhost3000Signupusername=xxxampemail=xxx

所以要能處理這樣的資料必須有以下功能

bull 解析 URL

bull 辨別動作是 Signup

bull 解析出 username和 email

一旦能取得 username和 email的值程式就能加以應用了

處理 Signup的程式碼雛形

load module

var url = require(rsquourlrsquo)

urlData = urlparse(requrltrue)

action = urlDatapathname

reswriteHead(200 rdquoContent-Typerdquordquotexthtml charset=utf-8rdquo)

if (action === rdquoSignuprdquo)

user = urlDataquery

resend(rdquolth1gtrdquo + userusername + rdquo 歡迎您的加入 lth1gtltpgt 我們已經將會員啟用信寄至rdquo + useremail + rdquoltpgtrdquo)

首先需要加載 url module它是用來協助我們解析 URL的模組接著使用 urlparse方法第一個傳入 url字串變數也就是 requrl另外第二個參數的用意是設為 ture則引進 querystring模組來協助處理預設是 false它影響到的是 urlDataquery設為 true會傳回物件不然就只是一般的字串urlparse會將字串內容整理成一個物件我們把它指定給 urlData

action變數作為記錄 pathname這是我們稍後要來判斷目前網頁的動作是什麼接著先將 html表頭資訊 (Header)準備好再來判斷路徑邏輯如

58 6 Express介紹

Nodejs中文電子書

果是 Signup這個動作就把 urlDataquery裡的資料指定給 user然後輸出userusername和 useremail把使用者從表單註冊的資料顯示於頁面中

最後進行程式測試啟動 nodejs主程式之後開啟瀏覽器就會看到表單填寫完畢按下送出就可以看到結果了

完整 nodejs程式碼如下

var http = require(rsquohttprsquo)

url = require(rsquourlrsquo)

fs = require(rdquofsrdquo)

server

server = httpcreateServer(function (reqres)

var urlData

encode = rdquoutf8rdquo

filePath = rdquoviewexpress_get_example_formhtmlrdquo

action

urlData = urlparse(requrltrue)

action = urlDatapathname

reswriteHead(200 rdquoContent-Typerdquordquotexthtml charset=utf-8rdquo)

if (action === rdquoSignuprdquo)

user = urlDataquery

resend(rdquolth1gtrdquo + userusername + rdquo 歡迎您的加入 lth1gtltpgt 我們已經將會員啟用信寄至rdquo + useremail + rdquoltpgtrdquo)

else

fsreadFile(filePath encode function(err file)

reswrite(file)

resend()

)

)

serverlisten(3000)

consolelog(rsquoServer 跑起來了現在時間是rsquo + new Date())

66 Express GET應用範例 59

Nodejs中文電子書

67 Express POST應用範例

一開始準備基本的 html表單傳送內容以 POST方式form的 action屬性設定為 POST其餘 html內容與前一個範例應用相同

ltDOCTYPE htmlgt

lthtmlgt

ltheadgt

ltmeta http-equiv=rdquoContent-Typerdquo content=rdquotexthtml charset=utf8rdquogt

lttitlegtNodejs 菜鳥筆記 (1)lttitlegt

ltlink rel=rdquostylesheetrdquo href=rdquocssstylecssrdquo type=rdquotextcssrdquo media=rdquoallrdquo gt

ltheadgt

ltbodygt

lth1gtNodejs 菜鳥筆記-註冊lth1gt

ltform id=rdquosignuprdquo method=rdquoPOSTrdquo action=rdquohttplocalhost3000Signuprdquogt

ltlabelgt 使用者名稱ltlabelgtltinput type=rdquotextrdquo id=rdquousernamerdquo name=rdquousernamerdquo gtltbrgt

ltlabelgt 電子郵件ltlabelgtltinput type=rdquotextrdquo id=rdquoemailrdquo name=rdquoemailrdquo gtltbrgt

ltinput type=rdquosubmitrdquo value=rdquo 註冊我的帳號rdquo gtltbrgt

ltformgt

ltbodygt

lthtmlgt

nodejs的程式處理邏輯與前面 GET範例類似部分程式碼如下

qs = require(rsquoquerystringrsquo)

if (action === rdquoSignuprdquo)

formData = rsquorsquo

reqon(rdquodatardquo function (data)

formData += data

)

reqon(rdquoendrdquo function ()

user = qsparse(formData)

resend(rdquolth1gtrdquo + userusername + rdquo 歡迎您的加入 lth1gtltpgt 我們已經將會員啟用信寄至rdquo + useremail + rdquoltpgtrdquo)

)

60 6 Express介紹

Nodejs中文電子書

主要加入了rsquoquerystringrsquo這個moduel方便我們等一下解析由表單 POST回來的資料另外加入一個 formData的變數用來搜集待等一下表單回傳的資料前面的 GET範例我們只從 req拿出 url的資料這次要在利用req身上的事件處理

JavaScript 在訂閱事件時使用 addEventListener而 nodejs 使用的則是on這邊加上了監聽 data的事件會在瀏覽器傳送資料到Web Server時被執行參數是它所接收到的資料型態是字串

接著再增加 end的事件當瀏覽器的請求事件結束時它就會動作

由於瀏覽器使用 POST在上傳資料時會將資料一塊塊地上傳因為我們在監聽 data事件時透過 formData變數將它累加起來lt不過由於我們

上傳的資料很少一次就結束不過如果日後需要傳的是資料比較大的檔

案這個累加動作就很重要

當資料傳完就進到 end 事件中會用到 qsparse 來解析 formDataformData的內容是字串內容是

username=wordsmithampemail=wordsmith40somewhere

而 qsparse可以幫我們把這個 querystring轉成物件的格式也就是

username=wordsmithampemail=wordsmith40somewhere

一旦轉成物件並指定給 user之後其他的事情就和 GET方法時操作的一樣寫 response的表頭將內容回傳並將 userusername和 useremail代入到內容中

修改完成後接著執行 nodejs程式啟動 web server開啟瀏覽器進入表單測試看看POST的方式能否順利運作

完整程式碼如下

var http = require(rsquohttprsquo)

url = require(rsquourlrsquo)

fs = require(rdquofsrdquo)

qs = require(rsquoquerystringrsquo)

server

server = httpcreateServer(function (reqres)

var urlData

67 Express POST應用範例 61

Nodejs中文電子書

encode = rdquoutf8rdquo

filePath = rdquoviewexpress_post_example_formhtmlrdquo

formData

action

urlData = urlparse(requrltrue)

action = urlDatapathname

reswriteHead(200 rdquoContent-Typerdquordquotexthtml charset=utf-8rdquo)

if (action === rdquoSignuprdquo)

formData = rsquorsquo

reqon(rdquodatardquo function (data)

formData += data

)

reqon(rdquoendrdquo function ()

user = qsparse(formData)

resend(rdquolth1gtrdquo + userusername + rdquo 歡迎您的加入 lth1gtltpgt 我們已經將會員啟用信寄至rdquo + useremail + rdquoltpgtrdquo)

)

else

fsreadFile(filePath encode function(err file)

reswrite(file)

resend()

)

)

serverlisten(3000)

consolelog(rsquoServer 跑起來了現在時間是rsquo + new Date())

62 6 Express介紹

Nodejs中文電子書

68 Express AJAX應用範例

在 Nodejs要使用 Ajax傳送資料並且與之互動在接受資料的部份沒有太大的差別client端不是用 GET就是用 POST來傳資料重點在處理完後用 JSON格式回傳當然 Ajax不見得只傳 JSON格式有時是回傳一段 HTML碼不過後者對伺服器來說基本上就和前兩篇沒有差別了所以我們還是以回傳 JSON做為這一回的主題

這一回其實大多數的工作都會落在前端 Ajax上面前端要負責發送與接收資料並在接收資料後撤掉原先發送資料的表單並將取得的資料

改成 HTML格式之後放上頁面

首先先準備 HTML靜態頁面資料

ltDOCTYPE htmlgt

lthtmlgt

ltheadgt

lttitlegtNodejs 菜鳥筆記 (3)lttitlegt

ltlink rel=rdquostylesheetrdquo href=rdquocssstylecssrdquo type=rdquotextcssrdquo media=rdquoallrdquo gt

ltscript src=rdquohttpsajaxgoogleapiscomajaxlibsjquery171jqueryminjsrdquogtltscriptgt

ltheadgt

ltbodygt

lth1gtNodejs 菜鳥筆記-註冊 (用 Ajax)lth1gt

ltform id=rdquosignuprdquo action=rdquoSignuprdquo method=rdquoPOSTrdquogt

ltlabelgt 使用者名稱ltlabelgtltinput type=rdquotextrdquo id=rdquousernamerdquo name=rdquousernamerdquo gtltbrgt

ltlabelgt 電子郵件ltlabelgtltinput type=rdquotextrdquo id=rdquoemailrdquo name=rdquoemailrdquo gtltbrgt

ltinput type=rdquosubmitrdquo value=rdquo 註冊我的帳號rdquo gtltbrgt

ltformgt

ltdiv id=rdquoresultrdquogt

ltdivgt

ltscript type=rdquotextjavascriptrdquogt

$(function()$(rsquosignup input[type=rdquosubmitrdquo]rsquo)click(function(e)

var user =

userusername = $(rdquousernamerdquo)val()useremail = $(rdquoemailrdquo)val()

$post(rdquoSignuprdquouserfunction(data)greet(data)

)

68 Express AJAX應用範例 63

Nodejs中文電子書

epreventDefault()

)

var greet = function(msg)

var resultNode = $(rsquoresultrsquo)greeting = $(rsquolth2gtHirsquo+msgusername+rsquo 你的會員 id 是rsquo+ msgid +rsquo 我們會將會員啟動信寄至rsquo+msgemail+rsquolth2gtrsquo)

$(rsquosignuprsquo)hide()resultNodehtml(rdquordquo)

resultNodeappend(greeting)

)

ltscriptgt

ltbodygt

lthtmlgt

HTML頁面上準備了一個表單用來傳送註冊資料接著直接引用了Google CDN來載入 jQuery用來幫我們處理 Ajax的工作這次要傳送和接收的工作很大的變動都在 HTML頁面上的 JavaScript當中我們要做的事有 (相關 jQuery處理這邊不多做贅述指提起主要功能解說)

bull 用 jQUery取得 submit按鈕綁定它的 click動作

bull 取得表單 username和 email的值存放在 user這個物件中

bull 用 jQuery的 $post方法將 user的資料傳到 Server

bull 一旦成功取得資料後透過 greet這個 function組成回報給 user的訊息

bull 清空原本給使用者填資料的表單

bull 將 Server回傳的 usernameemail和 id這 3個資料組成回應的訊息

bull 將訊息放到原本表單的位置

經過以上的處理後一個 Ajax的表單的基本功能已經完成

接著進行 nodejs主要程式的編輯部分程式碼如下

var fs = require(rdquofsrdquo)

64 6 Express介紹

Nodejs中文電子書

qs = require(rsquoquerystringrsquo)

if (action === rdquoSignuprdquo)

formData = rsquorsquo

reqon(rdquodatardquo function (data)

formData += data

)

reqon(rdquoendrdquo function ()

var msg

user = qsparse(formData)

userid = rdquo123456rdquo

msg = JSONstringify(user)

reswriteHead(200 rdquoContent-Typerdquordquoapplicationjson charset=utf-8rdquordquoContent-Lengthrdquomsglength)

resend(msg)

)

這裡的程式和前面 POST範例基本上大同小異差別在

bull 幫 user的資料加上 id隨意存放一些文字進去讓 Server回傳的資料多於 Client端傳上來的不然會覺得 Server都沒做事

bull 增加了 msg 這個變數存放將 user 物件 JSON 文字化的結果JSONstringify這個轉換函式是 V8引擎所提供的如果你好奇的話

bull 大重點來了我們要告訴 Client端這次回傳的資料格式是 JSON所在 Content-type和 Content-Length要提供給 Client

Server很輕鬆就完成任務了最後進行程式測試啟動 nodejs主程式之後開啟瀏覽器就會看到表單填寫完畢按下送出就可以看到結果了

最後 nodejs本篇範例程式碼如下

var http = require(rsquohttprsquo)

url = require(rsquourlrsquo)

fs = require(rdquofsrdquo)

qs = require(rsquoquerystringrsquo)

server

68 Express AJAX應用範例 65

Nodejs中文電子書

server = httpcreateServer(function (reqres)

var urlData

encode = rdquoutf8rdquo

filePath = rdquoviewexpress_ajax_example_formhtmlrdquo

formData

action

urlData = urlparse(requrltrue)

action = urlDatapathname

if (action === rdquoSignuprdquo)

formData = rsquorsquo

reqon(rdquodatardquo function (data)

formData += data

)

reqon(rdquoendrdquo function ()

var msg

user = qsparse(formData)

userid = rdquo123456rdquo

msg = JSONstringify(user)

reswriteHead(200 rdquoContent-TyperdquordquoapplicationjsonrdquordquoContent-Lengthrdquomsglength)

resend(msg)

)

else

fsreadFile(filePath encode function(err file)

reswriteHead(200 rdquoContent-Typerdquordquotexthtml charset=utf-8rdquo)

reswrite(file)

resend()

)

)

serverlisten(3000)

66 6 Express介紹

Nodejs中文電子書

consolelog(rsquoServer 跑起來了現在時間是rsquo + new Date())

69 原始資料提供

bull [NodeJS初學者筆記 (1)-用 GET傳送資料] (httpithelpithomecomtwquestion10087402)

bull [NodeJS初學者筆記 (2)-用 POST傳送資料] (httpithelpithomecomtwquestion10087489)

bull [NodeJS 初學者筆記 (3)-用 Ajax 傳送資料] (httpithelpithomecomtwquestion10087627)

69 原始資料提供 67

Nodejs中文電子書

68 6 Express介紹

7

CoffeeScript

bull Spine

bull Hem

bull Spineapp

Letrsquos Have a Cup of CoffeeScript

bull es5-shim ECMAScript 5 compatibility shims for legacy JavaScript engines

ESnext

node-iform - A middleware for node-validator httpsgithubcomguileennode-iform

69

Nodejs中文電子書

70 7 CoffeeScript

8

製作一個 Hubot的 Plurk Adapter

81 應用事項提醒

此應用範例以CoffeeScript編寫主要程式架構採用Github釋放的Hubot專案以下有幾個主要注意事項請讀者注意

bull Hubot 一開始的架構除了內建的兩個 Adapter 之外其餘都要以Nodejs的Module方式才能運作

bull Hubot的 binhubot裡面寫著 npm install所以不管你怎麼改原始碼也不能改變第一點的狀況

其實上述都在討論同一件事情NodeJS的模組而且模組不能設定相對路徑之類的來安裝一定要包裝成 npm相符和格式才有辦法正確執行

82 建立 Adapter

首先需要準備兩個檔案

bull packagejson

bull plurkcoffee

71

Nodejs中文電子書

有這兩個就足以變成 NodeJS的模組了在開始 Coding之前先來設定一下 packagejson相依性

rdquonamerdquo rdquohubot-plurkrdquo

rdquoversionrdquo rdquo011rdquo

rdquomainrdquo rdquoplurkrdquo

rdquodependenciesrdquo

rdquohubotrdquo rdquogt=205rdquo

rdquooauthrdquo rdquordquo

rdquocronrdquordquordquo

這邊會使用到NodeJS的 cron模組(Module)而登入 Plurk需要OAuth標準授權才行所以也需要加載 OAuth模組

注意Hubot在讀取非內建模組時會自動在前面加上 hubot-的前置

83 建立 Robot跟 API

本篇應用基本上程式碼大部分參考 Hubot - Twitter Adapter來製作主要差異只有在使用 OAuth這個模組Twitter有 Streaming可用而 Plurk則得用 Comet方式來達到即時讀取

plurkcoffee程式碼

Robot = require(rdquohubotrdquo)robot()

Adapter = require(rdquohubtordquo)adapter()

EventEmitter = require(rdquoeventsrdquo)EventEmitter

oauth = require(rdquooauthrdquo)

cronJob = require(rdquocronrdquo)CronJob

class Plurk exntends Adapter

class PlurkStreaming exnteds EventEmitter

72 8 製作一個 Hubot的 Plurk Adapter

Nodejs中文電子書

先弄個基本架構主要 Class皆參考 Twitter Adapter命名方式接著再將 Plurk這個 Class增加幾個Method基本上只要有 run send reply就夠了而 run用來做初始化的部分

class Plurk entends Adapter

send (plurk id strings⋯) -gt

reply (plurk id strings⋯) -gt

run -gt

看起來有點東西接著來處理主要的 Plurk API結合部分

class PlurkStreaming extends EventEmitter

consuctor (options) -gt

plurk (callback) -gt

觀察河道

getChannel -gt

取得 Comet 網址

reply (plurk id message) -gt

回噗

acceptFriends -gt

接受好友

get (path callback) -gt

GET 請求

post (path body callback)-gt

POST 請求(其實是裝飾)

request (method path body callback)-gt

主要的 OAuth 請求

comet (server callback)-gt

噗浪的 Comet 傳回是 JavaScript Callback 要另外處理後才會變成 JSON

然後把注意力集中到 constructor上先把建構子弄好

constructor (options) -gt

super()

if optionskey and optionssecret and optionstoken and optionstoken secret

key = optionskey

secret = optionssecret

token = optionstoken

token secret = optionstoken secret

83 建立 Robot跟 API 73

Nodejs中文電子書

建立 OAuth 連接

consumer = new oauthOAuth(

rdquohttpwwwplurkcomOAuthrequest tokenrdquo

rdquohttpwwwplurkcomOAuthaccess tokenrdquo

key

secret

rdquo10rdquo

rdquohttpwwwplurkcomOAuthauthorizerdquo

rdquoHMAC-SHA1rdquo

)

domain = rdquowwwplurkcomrdquo

初始化取得 Comet 網址

do getChannel

else

throw new Error(rdquo 參數不足需要 Key Secret Token Token Secretrdquo)

接著來處理 request這個 method

request (method path body callback) -gt

記錄一下這次的 Request

consolelog(rdquohttpdomainpathrdquo)

Callback 這邊先不丟進去要用另一種方式處理

request = consumerget(rdquohttpdomainpathrdquo token token secret null)

requeston rdquoresponserdquo (res) -gt

reson rdquodatardquo (chunk) -gt

parseResponse(chunk+rsquorsquo callback)

reson rdquoendrdquo (data) -gt

consolelog rdquoEnd Request pathrdquo

reson rdquoerrorrdquo (data) -gt

consolelog rdquoError rdquo + data

requestend()

處理資料

parseResponse = (data callback) -gt

if datalength gt 0

用 TryCatch 避免處理 JSON 出錯導致整個中斷

try

callback null JSONparse(data)

catch err

74 8 製作一個 Hubot的 Plurk Adapter

Nodejs中文電子書

consolelog(rdquoError Parse JSONrdquo + data err)

繼續執行

callback null data ||

大致上就是這樣上面程式的架構已經將整個 Hubot Plurk Adapter完成因為在測試時竟然因為噗浪 Lag而沒讀到完整的 Comet資料然後造成程式異常為了避免這個問題發生因此需要加上為了完美呈現需要再

加上 Comet的處理所以要使用到 EventEmitter的功能

comet (server callback) -gt

在 Callback 裡面會找不到自身所以設定區域變數

self =

記錄一下這次的 Request

consolelog(rdquo[Comet] serverrdquo)

Callback 這邊先不丟進去要用另一種方式處理

request = consumerget(rdquohttpdomainpathrdquo token token secret null)

requeston rdquoresponserdquo (res) -gt

reson rdquodatardquo (chunk) -gt

parseResponse(chunk+rsquorsquo callback)

reson rdquoendrdquo (data) -gt

consolelog rdquoEnd Request pathrdquo

請求結束發出事件通知可以進行下一次請求

selfemit rdquonextPlurkrdquo

reson rdquoerrorrdquo (data) -gt

consolelog rdquoError rdquo + data

requestend()

處理資料

parseResponse = (data callback) -gt

if datalength gt 0

用 trycatch 避免失敗中斷

try

去掉 JavaScript 的 Callback

data = datamatch(CometChannelscriptCallback((+))s)

jsonData = rdquordquo

if data

83 建立 Robot跟 API 75

Nodejs中文電子書

jsonData = JSONparse(data[1])

else

如果沒有任何 Match 嘗試直接 parse

jsonData = JSONparse(data)

catch err

consolelog(rdquo[Comet] Errorrdquo data err)

用 TryCatch 避免處理 JSON 出錯導致整個中斷

try

只傳入 json 的 data 部分

callback null jsonDatadata

catch err

consolelog(rdquo[Comet]Error Parse JSONrdquo + data err)

繼續執行

callback null data ||

後面的 get跟 post就簡單多了

get (path callback) -gt

request(rdquoGETrdquo path null callback)

post (path body callback) -gt

request(rdquoPOSTrdquo path body callback)

接著處理取的 Comet網址的 getChannel

getChannel -gt

self =

get rdquoAPPRealtimegetUserChannelrdquo (error data) -gt

if error

檢查是否有 comet server

if datacomet server

selfchannel = datacomet server

如果沒有 Channel Ready 就嘗試連接會失敗

selfemit(rsquochannel readyrsquo)

那麼先來處理 Plurk Adaper好處理的部份

send (plruk id strings⋯)-gt

跟 Reply 一樣直接交給 reply 做

reply plurk id strings⋯

76 8 製作一個 Hubot的 Plurk Adapter

Nodejs中文電子書

reply (plurk id strings⋯) -gt

stringsforEach (message) =gt

botreply(plruk id message)

接著把 run處理好就可以上線運作摟

run -gt

self =

options =

key processenvHUBOT PLURK KEY

secret processenvHUBOT PLURK SECRET

token processenvHUBOT PLURK TOKEN

token secret processenvHUBOT PLURK TOKEN SECRET

創建剛剛的 API

bot = new PlurkStreaming(options)

依照 Twitter 的 new RobotTextMessage 會沒有反應所以參考 hubot-minecraft 的方式

r = robotconstructor

處理噗浪河道訊息

doPlurk = (data)-gt

檢查是否為回噗

if dataresponse

datacontent raw = dataresponsecontent raw

datauser id = dataresponseuser id

確定有噗浪 ID 跟訊息

if dataplurk id and datacontent raw

selfreceive new rTextMessage(dataplurk id datacontent raw)

取得 Comet Server 完成開始第一次 Comet 連接

boton rdquochannel readyrdquo () -gt

botplurk selfdoPlurk

上一次 Comet 完成繼續 Polling

boton rdquonextPlurkrdquo ()-gt

botplurk selfdoPlurk

定時接受好友邀請

do botacceptFriends

bot = bot

83 建立 Robot跟 API 77

Nodejs中文電子書

終於完成 AdapterHubot專案裡面的 scripts資料夾內是互動部分不需要像 Adapter如此大費周章處理只新增檔案並且設計好對白之後就會回噗了我開發用的機器人在此大家可以去跟他玩玩[httpplurkcomelct9620 bot](httpplurkcomelct9620 bot)

84 原始資料提供

bull [製 作 一 個 Hubot 的 噗 浪 Adapter](http revo-skillfrosttw blog20120318create-a-hubot-plurk-adapter)

78 8 製作一個 Hubot的 Plurk Adapter

9

Nodejs好用工具介紹

91 logio

Real-time log monitoring in your browser

httplogioorg

nodejs (gt=0412 lt=0611)

npm config set unsafe-perm true

npm install -g --prefix=usrlocal logio

logio server start

httplocalhost8998

79

Nodejs中文電子書

80 9 Nodejs好用工具介紹

10

精選文章

101 Express

bull 使用 NodeJS + Express從 GETPOST Request取值 [邀稿中]

bull link2 [邀稿中]

bull link3 [邀稿中]

81

Nodejs中文電子書

82 10 精選文章

11

參考資源

111 Nodejs書籍

bull Programming Nodejs April 2012 (est) Travis Swicegood Pragmatic Bookshelf

bull e Little Book on CoffeeScript January 2012 Alex MacCaw OrsquoReilly Media

bull Node for Front-End Developers January 2012 Garann Means OrsquoReilly Media

bull Building Hypermedia APIs with HTML5 and Node November 2011 MikeAmundsen OrsquoReilly Media

bull Node Web Development August 2011 David Herron PacktPub

bull CoffeeScript Accelerated JavaScript Development July 2011 Trevor BurnhamPragmatic Bookshelf

bull Getting Started with GEO CouchDB and Nodejs July 2011 Mick omp-son OrsquoReilly Media

bull What Is Node July 2011 Brett McLaughlin OrsquoReilly Media

bull Node Up and Running May 2011 Tom Hughes-Croucher Mike WilsonOrsquoReilly Media

83

Nodejs中文電子書

bull Hands-on Nodejs e Nodejs introduction and API reference May 2011 Pe-dro Teixeira LeanPub

bull e Node Beginner Book Manuel Kiessling

bull Mastering Nodejs visionmedia

112 Nodejs影音教學

bull e Node Sessions e Best of OSCON 2011 August 2011 OrsquoReilly Media(Video)

bull Tom Hughes-Croucher on Node March 2011 OrsquoReilly Media (Video)

113 Nodejs教學網站

bull Node Tuts

114 Nodejs課程

bull Introduction To Nodejs Van Nguyen CodeLesson

84 11 參考資源

Page 53: Nodejs Wiki

Nodejs中文電子書

是否要使用全域安裝可以依照套件是否提供新指令來判斷舉例來說express 套件提供 express 這個指令而 coffee-script 則提供 coffee

指令

在 local 安 裝 模 式 中這 些 指 令 的 程 式 檔 案會 被 安 裝 到node modules的 bin這個隱藏資料夾下除非將bin的路徑加入 PATH環境變數否則要執行這些指令將會相當不便

為了方便指令的執行我們可以在 npm install 加上 -g 或 --

global參數啟用 global安裝模式例如

npm install -g coffee-script

npm install -g express

使用 global安裝模式需要注意執行權限的問題若權限不足可能會出現類似以下的錯誤訊息

npm ERR Error EACCES permission denied rsquorsquo

npm ERR

npm ERR Please try running this command again as rootAdministrator

要獲得足夠得執行權限請參考以下說明

bull Windows 7 或 2008 以上在「命令提示字元」的捷徑按右鍵選擇「以系統管理員身分執行」執行 npm指令時就會具有 Administrator身分

bull Mac OS X或 Linux系統可以使用 sudo指令例如

sudo npm install -g express

bull Linux系統可以使用 root權限登入或是以「sudo su -」切換成 root身分(使用 root權限操作系統相當危險因此並不建議使用這種方式)

若加上 -g參數使用 npm install -g coffee-script完成安裝

後就可以在終端機執行 coffee指令例如

coffee -v

52 使用 NPM安裝套件 47

Nodejs中文電子書

執行結果(範例)

CoffeeScript version 120

53 套件的更新及維護

除了前一節說明的 search 及 install 用法npm 還提供其他許多指令(commands)

使用 npm help可以查詢可用的指令

npm help

執行結果(部分)

where ltcommandgt is one of

adduser apihelp author bin bugs c cache completion

config deprecate docs edit explore faq find get

help help-search home i info init install la link

list ll ln login ls outdated owner pack prefix

prune publish r rb rebuild remove restart rm root

run-script s se search set show star start stop

submodule tag test un uninstall unlink unpublish

unstar up update version view whoami

使用 npm help command可以查詢指令的詳細用法例如

npm help list

接下來本節要介紹開發過程常用的 npm指令

使用 list可以列出已安裝套件

npm list

48 5 NPM套件管理工具

Nodejs中文電子書

執行結果(範例)

-- coffee-script120

- express256

- connect185

| -- formidable108

-- mime124

-- mkdirp007

-- qs041

檢視某個套件的詳細資訊例如

npm show express

升級所有套件(如果該套件已發佈更新版本)

npm update

升級指定的套件

npm update express

移除指定的套件

npm uninstall express

54 使用 packagejson

對於正式的 Nodejs專案可以建立一個命名為 packagejson的設

定檔(純文字格式)檔案內容參考範例如下

54 使用 packagejson 49

Nodejs中文電子書

packagejson(範例)

rdquonamerdquo rdquoapplication-namerdquo

rdquoversionrdquo rdquo001rdquo

rdquoprivaterdquo true

rdquodependenciesrdquo

rdquoexpressrdquo rdquo255rdquo

rdquocoffee-scriptrdquo rdquolatestrdquo

rdquomongooserdquo rdquogt= 253rdquo

其中 name與 version依照專案的需求設置

需要注意的是 dependencies的設定它用於指定專案相依的套件名

稱及版本

bull rdquoexpressrdquo rdquo255rdquo

代表此專案相依版本 255的 express套件

bull rdquocoffee-scriptrdquo rdquolatestrdquo

使用最新版的 coffee-script套件(每次更新都會檢查新版)

bull rdquomongooserdquo rdquogt= 253rdquo

使用版本大於 253的 mongoose套件

假設某個套件的新版可能造成專案無法正常運作就必須指定套件的

版本避免專案的程式碼來不及更新以相容新版套件通常在開發初期的

專案需要盡可能維持新套件的相容性(以取得套件的更新或修正)可以

用「gt=」設定最低相容的版本或是使用「latest」設定永遠保持最新

套件

50 5 NPM套件管理工具

6

Express介紹

在前面的 nodejs基礎當中介紹許多許多開設 http的使用方法及介紹以及許多基本的 nodejs基本應用

接下來要介紹一個套件稱為 express [Express](httpexpressjscom)這個套件主要幫忙解決許多 nodejs http server所需要的基本服務讓開發 httpservice變得更為容易不需要像之前需要透過層層模組(module)才有辦法開始編寫自己的程式

這個套件是由 TJ Holowaychuk 製作而成的套件裡面包含基本的路由處理 (route)http資料處理(GETPOSTPUT)另外還與樣板套件(jshtml template engine)搭配同時也可以處理許多複雜化的問題

61 Express安裝

安裝方式十分簡單只要透過之前介紹的 NPM就可以使用簡單的指令安裝指令如下

這邊建議需要將此套件安裝成為全域模組方便日後使用

51

Nodejs中文電子書

62 Express基本操作

express的使用也十分簡單先來建立一個基本的 hello world

var app = require(rsquoexpressrsquo)createServer()

port = 1337

applisten(port)

appget(rsquorsquo function(req res)

ressend(rsquohello worldrsquo)

)

consolelog(rsquostart express servernrsquo)

可以從上面的程式碼發現基本操作與 nodejs http的建立方式沒有太大差異主要差在當我們設定路由時可以直接透過 appget方式設定回應與接受方式

63 Express路由處理

Express對於 http服務上有許多包裝讓開發者使用及設定上更為方便例如有幾個路由設定那我們就統一藉由 appget來處理

Create http server

appget(rsquorsquo function(req res)

ressend(rsquohello worldrsquo)

)

appget(rsquotestrsquo function(req res)

ressend(rsquotest renderrsquo)

)

appget(rsquouserrsquo function(req res)

ressend(rsquouser pagersquo)

)

52 6 Express介紹

Nodejs中文電子書

如上面的程式碼所表示appget可以帶入兩個參數第一個是路徑名稱設定第二個為回應函式 (call back function)回應函式裡面就如同之前的 createServer方法裡面包含 requestresponse兩個物件可供使用使用者就可以透過瀏覽器輸入不同的 url切換到不同的頁面顯示不同的結果

路由設定上也有基本的配對方式讓使用者從瀏覽器輸入的網址可以

是一個變數只要符合型態就可以有對應的頁面產出例如

Create http server

appget(rsquouseridrsquo function(req res)

ressend(rsquouser rsquo + reqparamsid)

)

appget(rsquonumberrsquo function(req res)

ressend(rsquonumber rsquo + reqparamsnumber)

)

裡 面 使 用 到number 從 網 址 輸 入 之 後 就 可 以 直 接 使 用reqparamsnumber 取得所輸入的資料變成 url 參數使用當然前面也是可以加上路徑的設定userid在瀏覽器上路徑必須符合userxxx透

過 reqparamsid就可以取到 xxx這個字串值

另外express參數處理也提供了路由參數配對處理也可以透過正規表示法作為參數設定

var app = require(rsquoexpressrsquo)createServer()

port = 1337

applisten(port)

appget(^ip((d23)((d23))((d23))((d23))) function(req res)

ressend(reqparams)

)

上面程式碼可以發現後面路由設定的型態是正規表示法裡面設定

格式為ip之後必須要加上 ip型態才會符合資料格式同時取得 ip資料已經由正規表示法將資料做分群因此可以取得 ip的四個數字

此程式執行之後可以透過瀏覽器測試輸入網址為 localhost3000ip25525510010可以從頁面獲得資料

63 Express路由處理 53

Nodejs中文電子書

此章節全部範例程式碼如下

overview

author Caesar Chi

blog clonnblogspotcom

version 20120226

create server

var app = require(rsquoexpressrsquo)createServer()

port = 1337

applisten(port)

normal style

appget(rsquorsquo function(req res)

ressend(rsquohello worldrsquo)

)

appget(rsquotestrsquo function(req res)

ressend(rsquotest renderrsquo)

)

parameter style

appget(rsquouseridrsquo function(req res)

ressend(rsquouser rsquo + reqparamsid)

)

appget(rsquonumberrsquo function(req res)

ressend(rsquonumber rsquo + reqparamsnumber)

)

REGX style

appget(^ip((d23)((d23))((d23))) function(req res)

ressend(reqparams)

)

appget(rsquorsquo function(req res)

ressend(rsquoPage not foundrsquo 404)

54 6 Express介紹

Nodejs中文電子書

)

consolelog(rsquostart express servernrsquo)

64 Express middleware

Express 裡面有一個十分好用的應用概念稱為 middleware可以透過middleware做出複雜的效果同時上面也有介紹 next方法參數傳遞就是靠 middleware的概念來傳遞參數讓開發者可以明確的控制程式邏輯

create http server

appuse(expressbodyParser())

appuse(expressmethodOverride())

appuse(expresssession())

上面都是一種 middleware的使用方式透過 appuse方式裡面載入函式執行方法回應函式會包含三個基本參數responserequestnext其中next表示下一個 middleware執行函式同時會自動將預設三個參數繼續帶往下個函式執行底下有個實驗

上面的片段程式執行後開啟瀏覽器連結上 localhost1337會發現伺服器回應結果順序如下

first middle ware

second middle ware

execute middle ware

end middleware function

從上面的結果可以得知剛才設定的 middleware都生效了在 appuse設定的 middleware是所有 url皆會執行方法如果有指定特定方法就可以使用 appget的 middleware設定在 appget函式的第二個參數就可以帶入函式或者是匿名函式只要函式裡面最後會接受 request response next這三個參數同時也有正確指定 next函式的執行時機最後都會執行到最後一個方法當然開發者也可以評估程式邏輯要執行到哪一個階段讓邏輯

可以更為分明

64 Express middleware 55

Nodejs中文電子書

65 Express路由應用

在實際開發上可能會遇到需要使用參數等方式混和變數一起使用

express裡面提供了一個很棒的處理方法 appall這個方式可以先採用基本路由配對再將設定為每個不同的處理方式開發者可以透過這個方式簡

化自己的程式邏輯

overview

author

version 20120226

create server

var app = require(rsquoexpressrsquo)createServer()

port = 1337

users = [

name rsquoClonnrsquo

name rsquoChirsquo

]

applisten(port)

appall(rsquouseridoprsquo function(req res next)

requser = users[reqparamsid]

if (requser)

next()

else

next(new Error(rsquocannot find user rsquo + reqparamsid))

)

appget(rsquouseridrsquo function(req res)

ressend(rsquoviewing rsquo + requsername)

)

appget(rsquouserideditrsquo function(req res)

ressend(rsquoediting rsquo + requsername)

)

56 6 Express介紹

Nodejs中文電子書

appget(rsquouseriddeletersquo function(req res)

ressend(rsquodeleting rsquo + requsername)

)

appget(rsquorsquo function(req res)

ressend(rsquoPage not foundrsquo 404)

)

consolelog(rsquostart express servernrsquo)

內部宣告一組預設的使用者分別給予名稱設定藉由 appall這個方法可以先將路由雛形建立再接下來設定 appget的路徑格式只要符合格式就會分配進入對應的方法中像上面的程式當中如果使用者輸入路徑為user0除了執行 appall程式之後執行 next方法就會對應到路徑設定為userid的這個方法當中如果使用者輸入路徑為user0edit就會執行到useridedit的對應方法

66 Express GET應用範例

我們準備一個使用 GET方法傳送資料的表單

ltDOCTYPE htmlgt

lthtmlgt

ltheadgt

ltmeta http-equiv=rdquoContent-Typerdquo content=rdquotexthtml charset=utf8rdquogt

lttitlegtNodejs 菜鳥筆記 (1)lttitlegt

ltlink rel=rdquostylesheetrdquo href=rdquocssstylecssrdquo type=rdquotextcssrdquo media=rdquoallrdquo gt

ltheadgt

ltbodygt

lth1gtNodejs 菜鳥筆記-註冊lth1gt

ltform id=rdquosignuprdquo method=rdquoGETrdquo action=rdquohttplocalhost3000Signuprdquogt

ltlabelgt 使用者名稱ltlabelgtltinput type=rdquotextrdquo id=rdquousernamerdquo name=rdquousernamerdquo gtltbrgt

ltlabelgt 電子郵件ltlabelgtltinput type=rdquotextrdquo id=rdquoemailrdquo name=rdquoemailrdquo gtltbrgt

ltinput type=rdquosubmitrdquo value=rdquo 註冊我的帳號rdquo gtltbrgt

ltformgt

ltbodygt

lthtmlgt

66 Express GET應用範例 57

Nodejs中文電子書

這個表單沒有什麼特別的地方我們只需要看第 9 行form 使用的method是 GET然後 action是rdquohttplocalhost3000Signupldquo等一下我們要來撰寫Signup這個 URL Path的處理程式

處理 Signup行為

我們知道所謂的 GET方法會透過 URL來把表單的值給帶過去以上面的表單來說到時候 URL會以這樣的形式傳遞

httplocalhost3000Signupusername=xxxampemail=xxx

所以要能處理這樣的資料必須有以下功能

bull 解析 URL

bull 辨別動作是 Signup

bull 解析出 username和 email

一旦能取得 username和 email的值程式就能加以應用了

處理 Signup的程式碼雛形

load module

var url = require(rsquourlrsquo)

urlData = urlparse(requrltrue)

action = urlDatapathname

reswriteHead(200 rdquoContent-Typerdquordquotexthtml charset=utf-8rdquo)

if (action === rdquoSignuprdquo)

user = urlDataquery

resend(rdquolth1gtrdquo + userusername + rdquo 歡迎您的加入 lth1gtltpgt 我們已經將會員啟用信寄至rdquo + useremail + rdquoltpgtrdquo)

首先需要加載 url module它是用來協助我們解析 URL的模組接著使用 urlparse方法第一個傳入 url字串變數也就是 requrl另外第二個參數的用意是設為 ture則引進 querystring模組來協助處理預設是 false它影響到的是 urlDataquery設為 true會傳回物件不然就只是一般的字串urlparse會將字串內容整理成一個物件我們把它指定給 urlData

action變數作為記錄 pathname這是我們稍後要來判斷目前網頁的動作是什麼接著先將 html表頭資訊 (Header)準備好再來判斷路徑邏輯如

58 6 Express介紹

Nodejs中文電子書

果是 Signup這個動作就把 urlDataquery裡的資料指定給 user然後輸出userusername和 useremail把使用者從表單註冊的資料顯示於頁面中

最後進行程式測試啟動 nodejs主程式之後開啟瀏覽器就會看到表單填寫完畢按下送出就可以看到結果了

完整 nodejs程式碼如下

var http = require(rsquohttprsquo)

url = require(rsquourlrsquo)

fs = require(rdquofsrdquo)

server

server = httpcreateServer(function (reqres)

var urlData

encode = rdquoutf8rdquo

filePath = rdquoviewexpress_get_example_formhtmlrdquo

action

urlData = urlparse(requrltrue)

action = urlDatapathname

reswriteHead(200 rdquoContent-Typerdquordquotexthtml charset=utf-8rdquo)

if (action === rdquoSignuprdquo)

user = urlDataquery

resend(rdquolth1gtrdquo + userusername + rdquo 歡迎您的加入 lth1gtltpgt 我們已經將會員啟用信寄至rdquo + useremail + rdquoltpgtrdquo)

else

fsreadFile(filePath encode function(err file)

reswrite(file)

resend()

)

)

serverlisten(3000)

consolelog(rsquoServer 跑起來了現在時間是rsquo + new Date())

66 Express GET應用範例 59

Nodejs中文電子書

67 Express POST應用範例

一開始準備基本的 html表單傳送內容以 POST方式form的 action屬性設定為 POST其餘 html內容與前一個範例應用相同

ltDOCTYPE htmlgt

lthtmlgt

ltheadgt

ltmeta http-equiv=rdquoContent-Typerdquo content=rdquotexthtml charset=utf8rdquogt

lttitlegtNodejs 菜鳥筆記 (1)lttitlegt

ltlink rel=rdquostylesheetrdquo href=rdquocssstylecssrdquo type=rdquotextcssrdquo media=rdquoallrdquo gt

ltheadgt

ltbodygt

lth1gtNodejs 菜鳥筆記-註冊lth1gt

ltform id=rdquosignuprdquo method=rdquoPOSTrdquo action=rdquohttplocalhost3000Signuprdquogt

ltlabelgt 使用者名稱ltlabelgtltinput type=rdquotextrdquo id=rdquousernamerdquo name=rdquousernamerdquo gtltbrgt

ltlabelgt 電子郵件ltlabelgtltinput type=rdquotextrdquo id=rdquoemailrdquo name=rdquoemailrdquo gtltbrgt

ltinput type=rdquosubmitrdquo value=rdquo 註冊我的帳號rdquo gtltbrgt

ltformgt

ltbodygt

lthtmlgt

nodejs的程式處理邏輯與前面 GET範例類似部分程式碼如下

qs = require(rsquoquerystringrsquo)

if (action === rdquoSignuprdquo)

formData = rsquorsquo

reqon(rdquodatardquo function (data)

formData += data

)

reqon(rdquoendrdquo function ()

user = qsparse(formData)

resend(rdquolth1gtrdquo + userusername + rdquo 歡迎您的加入 lth1gtltpgt 我們已經將會員啟用信寄至rdquo + useremail + rdquoltpgtrdquo)

)

60 6 Express介紹

Nodejs中文電子書

主要加入了rsquoquerystringrsquo這個moduel方便我們等一下解析由表單 POST回來的資料另外加入一個 formData的變數用來搜集待等一下表單回傳的資料前面的 GET範例我們只從 req拿出 url的資料這次要在利用req身上的事件處理

JavaScript 在訂閱事件時使用 addEventListener而 nodejs 使用的則是on這邊加上了監聽 data的事件會在瀏覽器傳送資料到Web Server時被執行參數是它所接收到的資料型態是字串

接著再增加 end的事件當瀏覽器的請求事件結束時它就會動作

由於瀏覽器使用 POST在上傳資料時會將資料一塊塊地上傳因為我們在監聽 data事件時透過 formData變數將它累加起來lt不過由於我們

上傳的資料很少一次就結束不過如果日後需要傳的是資料比較大的檔

案這個累加動作就很重要

當資料傳完就進到 end 事件中會用到 qsparse 來解析 formDataformData的內容是字串內容是

username=wordsmithampemail=wordsmith40somewhere

而 qsparse可以幫我們把這個 querystring轉成物件的格式也就是

username=wordsmithampemail=wordsmith40somewhere

一旦轉成物件並指定給 user之後其他的事情就和 GET方法時操作的一樣寫 response的表頭將內容回傳並將 userusername和 useremail代入到內容中

修改完成後接著執行 nodejs程式啟動 web server開啟瀏覽器進入表單測試看看POST的方式能否順利運作

完整程式碼如下

var http = require(rsquohttprsquo)

url = require(rsquourlrsquo)

fs = require(rdquofsrdquo)

qs = require(rsquoquerystringrsquo)

server

server = httpcreateServer(function (reqres)

var urlData

67 Express POST應用範例 61

Nodejs中文電子書

encode = rdquoutf8rdquo

filePath = rdquoviewexpress_post_example_formhtmlrdquo

formData

action

urlData = urlparse(requrltrue)

action = urlDatapathname

reswriteHead(200 rdquoContent-Typerdquordquotexthtml charset=utf-8rdquo)

if (action === rdquoSignuprdquo)

formData = rsquorsquo

reqon(rdquodatardquo function (data)

formData += data

)

reqon(rdquoendrdquo function ()

user = qsparse(formData)

resend(rdquolth1gtrdquo + userusername + rdquo 歡迎您的加入 lth1gtltpgt 我們已經將會員啟用信寄至rdquo + useremail + rdquoltpgtrdquo)

)

else

fsreadFile(filePath encode function(err file)

reswrite(file)

resend()

)

)

serverlisten(3000)

consolelog(rsquoServer 跑起來了現在時間是rsquo + new Date())

62 6 Express介紹

Nodejs中文電子書

68 Express AJAX應用範例

在 Nodejs要使用 Ajax傳送資料並且與之互動在接受資料的部份沒有太大的差別client端不是用 GET就是用 POST來傳資料重點在處理完後用 JSON格式回傳當然 Ajax不見得只傳 JSON格式有時是回傳一段 HTML碼不過後者對伺服器來說基本上就和前兩篇沒有差別了所以我們還是以回傳 JSON做為這一回的主題

這一回其實大多數的工作都會落在前端 Ajax上面前端要負責發送與接收資料並在接收資料後撤掉原先發送資料的表單並將取得的資料

改成 HTML格式之後放上頁面

首先先準備 HTML靜態頁面資料

ltDOCTYPE htmlgt

lthtmlgt

ltheadgt

lttitlegtNodejs 菜鳥筆記 (3)lttitlegt

ltlink rel=rdquostylesheetrdquo href=rdquocssstylecssrdquo type=rdquotextcssrdquo media=rdquoallrdquo gt

ltscript src=rdquohttpsajaxgoogleapiscomajaxlibsjquery171jqueryminjsrdquogtltscriptgt

ltheadgt

ltbodygt

lth1gtNodejs 菜鳥筆記-註冊 (用 Ajax)lth1gt

ltform id=rdquosignuprdquo action=rdquoSignuprdquo method=rdquoPOSTrdquogt

ltlabelgt 使用者名稱ltlabelgtltinput type=rdquotextrdquo id=rdquousernamerdquo name=rdquousernamerdquo gtltbrgt

ltlabelgt 電子郵件ltlabelgtltinput type=rdquotextrdquo id=rdquoemailrdquo name=rdquoemailrdquo gtltbrgt

ltinput type=rdquosubmitrdquo value=rdquo 註冊我的帳號rdquo gtltbrgt

ltformgt

ltdiv id=rdquoresultrdquogt

ltdivgt

ltscript type=rdquotextjavascriptrdquogt

$(function()$(rsquosignup input[type=rdquosubmitrdquo]rsquo)click(function(e)

var user =

userusername = $(rdquousernamerdquo)val()useremail = $(rdquoemailrdquo)val()

$post(rdquoSignuprdquouserfunction(data)greet(data)

)

68 Express AJAX應用範例 63

Nodejs中文電子書

epreventDefault()

)

var greet = function(msg)

var resultNode = $(rsquoresultrsquo)greeting = $(rsquolth2gtHirsquo+msgusername+rsquo 你的會員 id 是rsquo+ msgid +rsquo 我們會將會員啟動信寄至rsquo+msgemail+rsquolth2gtrsquo)

$(rsquosignuprsquo)hide()resultNodehtml(rdquordquo)

resultNodeappend(greeting)

)

ltscriptgt

ltbodygt

lthtmlgt

HTML頁面上準備了一個表單用來傳送註冊資料接著直接引用了Google CDN來載入 jQuery用來幫我們處理 Ajax的工作這次要傳送和接收的工作很大的變動都在 HTML頁面上的 JavaScript當中我們要做的事有 (相關 jQuery處理這邊不多做贅述指提起主要功能解說)

bull 用 jQUery取得 submit按鈕綁定它的 click動作

bull 取得表單 username和 email的值存放在 user這個物件中

bull 用 jQuery的 $post方法將 user的資料傳到 Server

bull 一旦成功取得資料後透過 greet這個 function組成回報給 user的訊息

bull 清空原本給使用者填資料的表單

bull 將 Server回傳的 usernameemail和 id這 3個資料組成回應的訊息

bull 將訊息放到原本表單的位置

經過以上的處理後一個 Ajax的表單的基本功能已經完成

接著進行 nodejs主要程式的編輯部分程式碼如下

var fs = require(rdquofsrdquo)

64 6 Express介紹

Nodejs中文電子書

qs = require(rsquoquerystringrsquo)

if (action === rdquoSignuprdquo)

formData = rsquorsquo

reqon(rdquodatardquo function (data)

formData += data

)

reqon(rdquoendrdquo function ()

var msg

user = qsparse(formData)

userid = rdquo123456rdquo

msg = JSONstringify(user)

reswriteHead(200 rdquoContent-Typerdquordquoapplicationjson charset=utf-8rdquordquoContent-Lengthrdquomsglength)

resend(msg)

)

這裡的程式和前面 POST範例基本上大同小異差別在

bull 幫 user的資料加上 id隨意存放一些文字進去讓 Server回傳的資料多於 Client端傳上來的不然會覺得 Server都沒做事

bull 增加了 msg 這個變數存放將 user 物件 JSON 文字化的結果JSONstringify這個轉換函式是 V8引擎所提供的如果你好奇的話

bull 大重點來了我們要告訴 Client端這次回傳的資料格式是 JSON所在 Content-type和 Content-Length要提供給 Client

Server很輕鬆就完成任務了最後進行程式測試啟動 nodejs主程式之後開啟瀏覽器就會看到表單填寫完畢按下送出就可以看到結果了

最後 nodejs本篇範例程式碼如下

var http = require(rsquohttprsquo)

url = require(rsquourlrsquo)

fs = require(rdquofsrdquo)

qs = require(rsquoquerystringrsquo)

server

68 Express AJAX應用範例 65

Nodejs中文電子書

server = httpcreateServer(function (reqres)

var urlData

encode = rdquoutf8rdquo

filePath = rdquoviewexpress_ajax_example_formhtmlrdquo

formData

action

urlData = urlparse(requrltrue)

action = urlDatapathname

if (action === rdquoSignuprdquo)

formData = rsquorsquo

reqon(rdquodatardquo function (data)

formData += data

)

reqon(rdquoendrdquo function ()

var msg

user = qsparse(formData)

userid = rdquo123456rdquo

msg = JSONstringify(user)

reswriteHead(200 rdquoContent-TyperdquordquoapplicationjsonrdquordquoContent-Lengthrdquomsglength)

resend(msg)

)

else

fsreadFile(filePath encode function(err file)

reswriteHead(200 rdquoContent-Typerdquordquotexthtml charset=utf-8rdquo)

reswrite(file)

resend()

)

)

serverlisten(3000)

66 6 Express介紹

Nodejs中文電子書

consolelog(rsquoServer 跑起來了現在時間是rsquo + new Date())

69 原始資料提供

bull [NodeJS初學者筆記 (1)-用 GET傳送資料] (httpithelpithomecomtwquestion10087402)

bull [NodeJS初學者筆記 (2)-用 POST傳送資料] (httpithelpithomecomtwquestion10087489)

bull [NodeJS 初學者筆記 (3)-用 Ajax 傳送資料] (httpithelpithomecomtwquestion10087627)

69 原始資料提供 67

Nodejs中文電子書

68 6 Express介紹

7

CoffeeScript

bull Spine

bull Hem

bull Spineapp

Letrsquos Have a Cup of CoffeeScript

bull es5-shim ECMAScript 5 compatibility shims for legacy JavaScript engines

ESnext

node-iform - A middleware for node-validator httpsgithubcomguileennode-iform

69

Nodejs中文電子書

70 7 CoffeeScript

8

製作一個 Hubot的 Plurk Adapter

81 應用事項提醒

此應用範例以CoffeeScript編寫主要程式架構採用Github釋放的Hubot專案以下有幾個主要注意事項請讀者注意

bull Hubot 一開始的架構除了內建的兩個 Adapter 之外其餘都要以Nodejs的Module方式才能運作

bull Hubot的 binhubot裡面寫著 npm install所以不管你怎麼改原始碼也不能改變第一點的狀況

其實上述都在討論同一件事情NodeJS的模組而且模組不能設定相對路徑之類的來安裝一定要包裝成 npm相符和格式才有辦法正確執行

82 建立 Adapter

首先需要準備兩個檔案

bull packagejson

bull plurkcoffee

71

Nodejs中文電子書

有這兩個就足以變成 NodeJS的模組了在開始 Coding之前先來設定一下 packagejson相依性

rdquonamerdquo rdquohubot-plurkrdquo

rdquoversionrdquo rdquo011rdquo

rdquomainrdquo rdquoplurkrdquo

rdquodependenciesrdquo

rdquohubotrdquo rdquogt=205rdquo

rdquooauthrdquo rdquordquo

rdquocronrdquordquordquo

這邊會使用到NodeJS的 cron模組(Module)而登入 Plurk需要OAuth標準授權才行所以也需要加載 OAuth模組

注意Hubot在讀取非內建模組時會自動在前面加上 hubot-的前置

83 建立 Robot跟 API

本篇應用基本上程式碼大部分參考 Hubot - Twitter Adapter來製作主要差異只有在使用 OAuth這個模組Twitter有 Streaming可用而 Plurk則得用 Comet方式來達到即時讀取

plurkcoffee程式碼

Robot = require(rdquohubotrdquo)robot()

Adapter = require(rdquohubtordquo)adapter()

EventEmitter = require(rdquoeventsrdquo)EventEmitter

oauth = require(rdquooauthrdquo)

cronJob = require(rdquocronrdquo)CronJob

class Plurk exntends Adapter

class PlurkStreaming exnteds EventEmitter

72 8 製作一個 Hubot的 Plurk Adapter

Nodejs中文電子書

先弄個基本架構主要 Class皆參考 Twitter Adapter命名方式接著再將 Plurk這個 Class增加幾個Method基本上只要有 run send reply就夠了而 run用來做初始化的部分

class Plurk entends Adapter

send (plurk id strings⋯) -gt

reply (plurk id strings⋯) -gt

run -gt

看起來有點東西接著來處理主要的 Plurk API結合部分

class PlurkStreaming extends EventEmitter

consuctor (options) -gt

plurk (callback) -gt

觀察河道

getChannel -gt

取得 Comet 網址

reply (plurk id message) -gt

回噗

acceptFriends -gt

接受好友

get (path callback) -gt

GET 請求

post (path body callback)-gt

POST 請求(其實是裝飾)

request (method path body callback)-gt

主要的 OAuth 請求

comet (server callback)-gt

噗浪的 Comet 傳回是 JavaScript Callback 要另外處理後才會變成 JSON

然後把注意力集中到 constructor上先把建構子弄好

constructor (options) -gt

super()

if optionskey and optionssecret and optionstoken and optionstoken secret

key = optionskey

secret = optionssecret

token = optionstoken

token secret = optionstoken secret

83 建立 Robot跟 API 73

Nodejs中文電子書

建立 OAuth 連接

consumer = new oauthOAuth(

rdquohttpwwwplurkcomOAuthrequest tokenrdquo

rdquohttpwwwplurkcomOAuthaccess tokenrdquo

key

secret

rdquo10rdquo

rdquohttpwwwplurkcomOAuthauthorizerdquo

rdquoHMAC-SHA1rdquo

)

domain = rdquowwwplurkcomrdquo

初始化取得 Comet 網址

do getChannel

else

throw new Error(rdquo 參數不足需要 Key Secret Token Token Secretrdquo)

接著來處理 request這個 method

request (method path body callback) -gt

記錄一下這次的 Request

consolelog(rdquohttpdomainpathrdquo)

Callback 這邊先不丟進去要用另一種方式處理

request = consumerget(rdquohttpdomainpathrdquo token token secret null)

requeston rdquoresponserdquo (res) -gt

reson rdquodatardquo (chunk) -gt

parseResponse(chunk+rsquorsquo callback)

reson rdquoendrdquo (data) -gt

consolelog rdquoEnd Request pathrdquo

reson rdquoerrorrdquo (data) -gt

consolelog rdquoError rdquo + data

requestend()

處理資料

parseResponse = (data callback) -gt

if datalength gt 0

用 TryCatch 避免處理 JSON 出錯導致整個中斷

try

callback null JSONparse(data)

catch err

74 8 製作一個 Hubot的 Plurk Adapter

Nodejs中文電子書

consolelog(rdquoError Parse JSONrdquo + data err)

繼續執行

callback null data ||

大致上就是這樣上面程式的架構已經將整個 Hubot Plurk Adapter完成因為在測試時竟然因為噗浪 Lag而沒讀到完整的 Comet資料然後造成程式異常為了避免這個問題發生因此需要加上為了完美呈現需要再

加上 Comet的處理所以要使用到 EventEmitter的功能

comet (server callback) -gt

在 Callback 裡面會找不到自身所以設定區域變數

self =

記錄一下這次的 Request

consolelog(rdquo[Comet] serverrdquo)

Callback 這邊先不丟進去要用另一種方式處理

request = consumerget(rdquohttpdomainpathrdquo token token secret null)

requeston rdquoresponserdquo (res) -gt

reson rdquodatardquo (chunk) -gt

parseResponse(chunk+rsquorsquo callback)

reson rdquoendrdquo (data) -gt

consolelog rdquoEnd Request pathrdquo

請求結束發出事件通知可以進行下一次請求

selfemit rdquonextPlurkrdquo

reson rdquoerrorrdquo (data) -gt

consolelog rdquoError rdquo + data

requestend()

處理資料

parseResponse = (data callback) -gt

if datalength gt 0

用 trycatch 避免失敗中斷

try

去掉 JavaScript 的 Callback

data = datamatch(CometChannelscriptCallback((+))s)

jsonData = rdquordquo

if data

83 建立 Robot跟 API 75

Nodejs中文電子書

jsonData = JSONparse(data[1])

else

如果沒有任何 Match 嘗試直接 parse

jsonData = JSONparse(data)

catch err

consolelog(rdquo[Comet] Errorrdquo data err)

用 TryCatch 避免處理 JSON 出錯導致整個中斷

try

只傳入 json 的 data 部分

callback null jsonDatadata

catch err

consolelog(rdquo[Comet]Error Parse JSONrdquo + data err)

繼續執行

callback null data ||

後面的 get跟 post就簡單多了

get (path callback) -gt

request(rdquoGETrdquo path null callback)

post (path body callback) -gt

request(rdquoPOSTrdquo path body callback)

接著處理取的 Comet網址的 getChannel

getChannel -gt

self =

get rdquoAPPRealtimegetUserChannelrdquo (error data) -gt

if error

檢查是否有 comet server

if datacomet server

selfchannel = datacomet server

如果沒有 Channel Ready 就嘗試連接會失敗

selfemit(rsquochannel readyrsquo)

那麼先來處理 Plurk Adaper好處理的部份

send (plruk id strings⋯)-gt

跟 Reply 一樣直接交給 reply 做

reply plurk id strings⋯

76 8 製作一個 Hubot的 Plurk Adapter

Nodejs中文電子書

reply (plurk id strings⋯) -gt

stringsforEach (message) =gt

botreply(plruk id message)

接著把 run處理好就可以上線運作摟

run -gt

self =

options =

key processenvHUBOT PLURK KEY

secret processenvHUBOT PLURK SECRET

token processenvHUBOT PLURK TOKEN

token secret processenvHUBOT PLURK TOKEN SECRET

創建剛剛的 API

bot = new PlurkStreaming(options)

依照 Twitter 的 new RobotTextMessage 會沒有反應所以參考 hubot-minecraft 的方式

r = robotconstructor

處理噗浪河道訊息

doPlurk = (data)-gt

檢查是否為回噗

if dataresponse

datacontent raw = dataresponsecontent raw

datauser id = dataresponseuser id

確定有噗浪 ID 跟訊息

if dataplurk id and datacontent raw

selfreceive new rTextMessage(dataplurk id datacontent raw)

取得 Comet Server 完成開始第一次 Comet 連接

boton rdquochannel readyrdquo () -gt

botplurk selfdoPlurk

上一次 Comet 完成繼續 Polling

boton rdquonextPlurkrdquo ()-gt

botplurk selfdoPlurk

定時接受好友邀請

do botacceptFriends

bot = bot

83 建立 Robot跟 API 77

Nodejs中文電子書

終於完成 AdapterHubot專案裡面的 scripts資料夾內是互動部分不需要像 Adapter如此大費周章處理只新增檔案並且設計好對白之後就會回噗了我開發用的機器人在此大家可以去跟他玩玩[httpplurkcomelct9620 bot](httpplurkcomelct9620 bot)

84 原始資料提供

bull [製 作 一 個 Hubot 的 噗 浪 Adapter](http revo-skillfrosttw blog20120318create-a-hubot-plurk-adapter)

78 8 製作一個 Hubot的 Plurk Adapter

9

Nodejs好用工具介紹

91 logio

Real-time log monitoring in your browser

httplogioorg

nodejs (gt=0412 lt=0611)

npm config set unsafe-perm true

npm install -g --prefix=usrlocal logio

logio server start

httplocalhost8998

79

Nodejs中文電子書

80 9 Nodejs好用工具介紹

10

精選文章

101 Express

bull 使用 NodeJS + Express從 GETPOST Request取值 [邀稿中]

bull link2 [邀稿中]

bull link3 [邀稿中]

81

Nodejs中文電子書

82 10 精選文章

11

參考資源

111 Nodejs書籍

bull Programming Nodejs April 2012 (est) Travis Swicegood Pragmatic Bookshelf

bull e Little Book on CoffeeScript January 2012 Alex MacCaw OrsquoReilly Media

bull Node for Front-End Developers January 2012 Garann Means OrsquoReilly Media

bull Building Hypermedia APIs with HTML5 and Node November 2011 MikeAmundsen OrsquoReilly Media

bull Node Web Development August 2011 David Herron PacktPub

bull CoffeeScript Accelerated JavaScript Development July 2011 Trevor BurnhamPragmatic Bookshelf

bull Getting Started with GEO CouchDB and Nodejs July 2011 Mick omp-son OrsquoReilly Media

bull What Is Node July 2011 Brett McLaughlin OrsquoReilly Media

bull Node Up and Running May 2011 Tom Hughes-Croucher Mike WilsonOrsquoReilly Media

83

Nodejs中文電子書

bull Hands-on Nodejs e Nodejs introduction and API reference May 2011 Pe-dro Teixeira LeanPub

bull e Node Beginner Book Manuel Kiessling

bull Mastering Nodejs visionmedia

112 Nodejs影音教學

bull e Node Sessions e Best of OSCON 2011 August 2011 OrsquoReilly Media(Video)

bull Tom Hughes-Croucher on Node March 2011 OrsquoReilly Media (Video)

113 Nodejs教學網站

bull Node Tuts

114 Nodejs課程

bull Introduction To Nodejs Van Nguyen CodeLesson

84 11 參考資源

Page 54: Nodejs Wiki

Nodejs中文電子書

執行結果(範例)

CoffeeScript version 120

53 套件的更新及維護

除了前一節說明的 search 及 install 用法npm 還提供其他許多指令(commands)

使用 npm help可以查詢可用的指令

npm help

執行結果(部分)

where ltcommandgt is one of

adduser apihelp author bin bugs c cache completion

config deprecate docs edit explore faq find get

help help-search home i info init install la link

list ll ln login ls outdated owner pack prefix

prune publish r rb rebuild remove restart rm root

run-script s se search set show star start stop

submodule tag test un uninstall unlink unpublish

unstar up update version view whoami

使用 npm help command可以查詢指令的詳細用法例如

npm help list

接下來本節要介紹開發過程常用的 npm指令

使用 list可以列出已安裝套件

npm list

48 5 NPM套件管理工具

Nodejs中文電子書

執行結果(範例)

-- coffee-script120

- express256

- connect185

| -- formidable108

-- mime124

-- mkdirp007

-- qs041

檢視某個套件的詳細資訊例如

npm show express

升級所有套件(如果該套件已發佈更新版本)

npm update

升級指定的套件

npm update express

移除指定的套件

npm uninstall express

54 使用 packagejson

對於正式的 Nodejs專案可以建立一個命名為 packagejson的設

定檔(純文字格式)檔案內容參考範例如下

54 使用 packagejson 49

Nodejs中文電子書

packagejson(範例)

rdquonamerdquo rdquoapplication-namerdquo

rdquoversionrdquo rdquo001rdquo

rdquoprivaterdquo true

rdquodependenciesrdquo

rdquoexpressrdquo rdquo255rdquo

rdquocoffee-scriptrdquo rdquolatestrdquo

rdquomongooserdquo rdquogt= 253rdquo

其中 name與 version依照專案的需求設置

需要注意的是 dependencies的設定它用於指定專案相依的套件名

稱及版本

bull rdquoexpressrdquo rdquo255rdquo

代表此專案相依版本 255的 express套件

bull rdquocoffee-scriptrdquo rdquolatestrdquo

使用最新版的 coffee-script套件(每次更新都會檢查新版)

bull rdquomongooserdquo rdquogt= 253rdquo

使用版本大於 253的 mongoose套件

假設某個套件的新版可能造成專案無法正常運作就必須指定套件的

版本避免專案的程式碼來不及更新以相容新版套件通常在開發初期的

專案需要盡可能維持新套件的相容性(以取得套件的更新或修正)可以

用「gt=」設定最低相容的版本或是使用「latest」設定永遠保持最新

套件

50 5 NPM套件管理工具

6

Express介紹

在前面的 nodejs基礎當中介紹許多許多開設 http的使用方法及介紹以及許多基本的 nodejs基本應用

接下來要介紹一個套件稱為 express [Express](httpexpressjscom)這個套件主要幫忙解決許多 nodejs http server所需要的基本服務讓開發 httpservice變得更為容易不需要像之前需要透過層層模組(module)才有辦法開始編寫自己的程式

這個套件是由 TJ Holowaychuk 製作而成的套件裡面包含基本的路由處理 (route)http資料處理(GETPOSTPUT)另外還與樣板套件(jshtml template engine)搭配同時也可以處理許多複雜化的問題

61 Express安裝

安裝方式十分簡單只要透過之前介紹的 NPM就可以使用簡單的指令安裝指令如下

這邊建議需要將此套件安裝成為全域模組方便日後使用

51

Nodejs中文電子書

62 Express基本操作

express的使用也十分簡單先來建立一個基本的 hello world

var app = require(rsquoexpressrsquo)createServer()

port = 1337

applisten(port)

appget(rsquorsquo function(req res)

ressend(rsquohello worldrsquo)

)

consolelog(rsquostart express servernrsquo)

可以從上面的程式碼發現基本操作與 nodejs http的建立方式沒有太大差異主要差在當我們設定路由時可以直接透過 appget方式設定回應與接受方式

63 Express路由處理

Express對於 http服務上有許多包裝讓開發者使用及設定上更為方便例如有幾個路由設定那我們就統一藉由 appget來處理

Create http server

appget(rsquorsquo function(req res)

ressend(rsquohello worldrsquo)

)

appget(rsquotestrsquo function(req res)

ressend(rsquotest renderrsquo)

)

appget(rsquouserrsquo function(req res)

ressend(rsquouser pagersquo)

)

52 6 Express介紹

Nodejs中文電子書

如上面的程式碼所表示appget可以帶入兩個參數第一個是路徑名稱設定第二個為回應函式 (call back function)回應函式裡面就如同之前的 createServer方法裡面包含 requestresponse兩個物件可供使用使用者就可以透過瀏覽器輸入不同的 url切換到不同的頁面顯示不同的結果

路由設定上也有基本的配對方式讓使用者從瀏覽器輸入的網址可以

是一個變數只要符合型態就可以有對應的頁面產出例如

Create http server

appget(rsquouseridrsquo function(req res)

ressend(rsquouser rsquo + reqparamsid)

)

appget(rsquonumberrsquo function(req res)

ressend(rsquonumber rsquo + reqparamsnumber)

)

裡 面 使 用 到number 從 網 址 輸 入 之 後 就 可 以 直 接 使 用reqparamsnumber 取得所輸入的資料變成 url 參數使用當然前面也是可以加上路徑的設定userid在瀏覽器上路徑必須符合userxxx透

過 reqparamsid就可以取到 xxx這個字串值

另外express參數處理也提供了路由參數配對處理也可以透過正規表示法作為參數設定

var app = require(rsquoexpressrsquo)createServer()

port = 1337

applisten(port)

appget(^ip((d23)((d23))((d23))((d23))) function(req res)

ressend(reqparams)

)

上面程式碼可以發現後面路由設定的型態是正規表示法裡面設定

格式為ip之後必須要加上 ip型態才會符合資料格式同時取得 ip資料已經由正規表示法將資料做分群因此可以取得 ip的四個數字

此程式執行之後可以透過瀏覽器測試輸入網址為 localhost3000ip25525510010可以從頁面獲得資料

63 Express路由處理 53

Nodejs中文電子書

此章節全部範例程式碼如下

overview

author Caesar Chi

blog clonnblogspotcom

version 20120226

create server

var app = require(rsquoexpressrsquo)createServer()

port = 1337

applisten(port)

normal style

appget(rsquorsquo function(req res)

ressend(rsquohello worldrsquo)

)

appget(rsquotestrsquo function(req res)

ressend(rsquotest renderrsquo)

)

parameter style

appget(rsquouseridrsquo function(req res)

ressend(rsquouser rsquo + reqparamsid)

)

appget(rsquonumberrsquo function(req res)

ressend(rsquonumber rsquo + reqparamsnumber)

)

REGX style

appget(^ip((d23)((d23))((d23))) function(req res)

ressend(reqparams)

)

appget(rsquorsquo function(req res)

ressend(rsquoPage not foundrsquo 404)

54 6 Express介紹

Nodejs中文電子書

)

consolelog(rsquostart express servernrsquo)

64 Express middleware

Express 裡面有一個十分好用的應用概念稱為 middleware可以透過middleware做出複雜的效果同時上面也有介紹 next方法參數傳遞就是靠 middleware的概念來傳遞參數讓開發者可以明確的控制程式邏輯

create http server

appuse(expressbodyParser())

appuse(expressmethodOverride())

appuse(expresssession())

上面都是一種 middleware的使用方式透過 appuse方式裡面載入函式執行方法回應函式會包含三個基本參數responserequestnext其中next表示下一個 middleware執行函式同時會自動將預設三個參數繼續帶往下個函式執行底下有個實驗

上面的片段程式執行後開啟瀏覽器連結上 localhost1337會發現伺服器回應結果順序如下

first middle ware

second middle ware

execute middle ware

end middleware function

從上面的結果可以得知剛才設定的 middleware都生效了在 appuse設定的 middleware是所有 url皆會執行方法如果有指定特定方法就可以使用 appget的 middleware設定在 appget函式的第二個參數就可以帶入函式或者是匿名函式只要函式裡面最後會接受 request response next這三個參數同時也有正確指定 next函式的執行時機最後都會執行到最後一個方法當然開發者也可以評估程式邏輯要執行到哪一個階段讓邏輯

可以更為分明

64 Express middleware 55

Nodejs中文電子書

65 Express路由應用

在實際開發上可能會遇到需要使用參數等方式混和變數一起使用

express裡面提供了一個很棒的處理方法 appall這個方式可以先採用基本路由配對再將設定為每個不同的處理方式開發者可以透過這個方式簡

化自己的程式邏輯

overview

author

version 20120226

create server

var app = require(rsquoexpressrsquo)createServer()

port = 1337

users = [

name rsquoClonnrsquo

name rsquoChirsquo

]

applisten(port)

appall(rsquouseridoprsquo function(req res next)

requser = users[reqparamsid]

if (requser)

next()

else

next(new Error(rsquocannot find user rsquo + reqparamsid))

)

appget(rsquouseridrsquo function(req res)

ressend(rsquoviewing rsquo + requsername)

)

appget(rsquouserideditrsquo function(req res)

ressend(rsquoediting rsquo + requsername)

)

56 6 Express介紹

Nodejs中文電子書

appget(rsquouseriddeletersquo function(req res)

ressend(rsquodeleting rsquo + requsername)

)

appget(rsquorsquo function(req res)

ressend(rsquoPage not foundrsquo 404)

)

consolelog(rsquostart express servernrsquo)

內部宣告一組預設的使用者分別給予名稱設定藉由 appall這個方法可以先將路由雛形建立再接下來設定 appget的路徑格式只要符合格式就會分配進入對應的方法中像上面的程式當中如果使用者輸入路徑為user0除了執行 appall程式之後執行 next方法就會對應到路徑設定為userid的這個方法當中如果使用者輸入路徑為user0edit就會執行到useridedit的對應方法

66 Express GET應用範例

我們準備一個使用 GET方法傳送資料的表單

ltDOCTYPE htmlgt

lthtmlgt

ltheadgt

ltmeta http-equiv=rdquoContent-Typerdquo content=rdquotexthtml charset=utf8rdquogt

lttitlegtNodejs 菜鳥筆記 (1)lttitlegt

ltlink rel=rdquostylesheetrdquo href=rdquocssstylecssrdquo type=rdquotextcssrdquo media=rdquoallrdquo gt

ltheadgt

ltbodygt

lth1gtNodejs 菜鳥筆記-註冊lth1gt

ltform id=rdquosignuprdquo method=rdquoGETrdquo action=rdquohttplocalhost3000Signuprdquogt

ltlabelgt 使用者名稱ltlabelgtltinput type=rdquotextrdquo id=rdquousernamerdquo name=rdquousernamerdquo gtltbrgt

ltlabelgt 電子郵件ltlabelgtltinput type=rdquotextrdquo id=rdquoemailrdquo name=rdquoemailrdquo gtltbrgt

ltinput type=rdquosubmitrdquo value=rdquo 註冊我的帳號rdquo gtltbrgt

ltformgt

ltbodygt

lthtmlgt

66 Express GET應用範例 57

Nodejs中文電子書

這個表單沒有什麼特別的地方我們只需要看第 9 行form 使用的method是 GET然後 action是rdquohttplocalhost3000Signupldquo等一下我們要來撰寫Signup這個 URL Path的處理程式

處理 Signup行為

我們知道所謂的 GET方法會透過 URL來把表單的值給帶過去以上面的表單來說到時候 URL會以這樣的形式傳遞

httplocalhost3000Signupusername=xxxampemail=xxx

所以要能處理這樣的資料必須有以下功能

bull 解析 URL

bull 辨別動作是 Signup

bull 解析出 username和 email

一旦能取得 username和 email的值程式就能加以應用了

處理 Signup的程式碼雛形

load module

var url = require(rsquourlrsquo)

urlData = urlparse(requrltrue)

action = urlDatapathname

reswriteHead(200 rdquoContent-Typerdquordquotexthtml charset=utf-8rdquo)

if (action === rdquoSignuprdquo)

user = urlDataquery

resend(rdquolth1gtrdquo + userusername + rdquo 歡迎您的加入 lth1gtltpgt 我們已經將會員啟用信寄至rdquo + useremail + rdquoltpgtrdquo)

首先需要加載 url module它是用來協助我們解析 URL的模組接著使用 urlparse方法第一個傳入 url字串變數也就是 requrl另外第二個參數的用意是設為 ture則引進 querystring模組來協助處理預設是 false它影響到的是 urlDataquery設為 true會傳回物件不然就只是一般的字串urlparse會將字串內容整理成一個物件我們把它指定給 urlData

action變數作為記錄 pathname這是我們稍後要來判斷目前網頁的動作是什麼接著先將 html表頭資訊 (Header)準備好再來判斷路徑邏輯如

58 6 Express介紹

Nodejs中文電子書

果是 Signup這個動作就把 urlDataquery裡的資料指定給 user然後輸出userusername和 useremail把使用者從表單註冊的資料顯示於頁面中

最後進行程式測試啟動 nodejs主程式之後開啟瀏覽器就會看到表單填寫完畢按下送出就可以看到結果了

完整 nodejs程式碼如下

var http = require(rsquohttprsquo)

url = require(rsquourlrsquo)

fs = require(rdquofsrdquo)

server

server = httpcreateServer(function (reqres)

var urlData

encode = rdquoutf8rdquo

filePath = rdquoviewexpress_get_example_formhtmlrdquo

action

urlData = urlparse(requrltrue)

action = urlDatapathname

reswriteHead(200 rdquoContent-Typerdquordquotexthtml charset=utf-8rdquo)

if (action === rdquoSignuprdquo)

user = urlDataquery

resend(rdquolth1gtrdquo + userusername + rdquo 歡迎您的加入 lth1gtltpgt 我們已經將會員啟用信寄至rdquo + useremail + rdquoltpgtrdquo)

else

fsreadFile(filePath encode function(err file)

reswrite(file)

resend()

)

)

serverlisten(3000)

consolelog(rsquoServer 跑起來了現在時間是rsquo + new Date())

66 Express GET應用範例 59

Nodejs中文電子書

67 Express POST應用範例

一開始準備基本的 html表單傳送內容以 POST方式form的 action屬性設定為 POST其餘 html內容與前一個範例應用相同

ltDOCTYPE htmlgt

lthtmlgt

ltheadgt

ltmeta http-equiv=rdquoContent-Typerdquo content=rdquotexthtml charset=utf8rdquogt

lttitlegtNodejs 菜鳥筆記 (1)lttitlegt

ltlink rel=rdquostylesheetrdquo href=rdquocssstylecssrdquo type=rdquotextcssrdquo media=rdquoallrdquo gt

ltheadgt

ltbodygt

lth1gtNodejs 菜鳥筆記-註冊lth1gt

ltform id=rdquosignuprdquo method=rdquoPOSTrdquo action=rdquohttplocalhost3000Signuprdquogt

ltlabelgt 使用者名稱ltlabelgtltinput type=rdquotextrdquo id=rdquousernamerdquo name=rdquousernamerdquo gtltbrgt

ltlabelgt 電子郵件ltlabelgtltinput type=rdquotextrdquo id=rdquoemailrdquo name=rdquoemailrdquo gtltbrgt

ltinput type=rdquosubmitrdquo value=rdquo 註冊我的帳號rdquo gtltbrgt

ltformgt

ltbodygt

lthtmlgt

nodejs的程式處理邏輯與前面 GET範例類似部分程式碼如下

qs = require(rsquoquerystringrsquo)

if (action === rdquoSignuprdquo)

formData = rsquorsquo

reqon(rdquodatardquo function (data)

formData += data

)

reqon(rdquoendrdquo function ()

user = qsparse(formData)

resend(rdquolth1gtrdquo + userusername + rdquo 歡迎您的加入 lth1gtltpgt 我們已經將會員啟用信寄至rdquo + useremail + rdquoltpgtrdquo)

)

60 6 Express介紹

Nodejs中文電子書

主要加入了rsquoquerystringrsquo這個moduel方便我們等一下解析由表單 POST回來的資料另外加入一個 formData的變數用來搜集待等一下表單回傳的資料前面的 GET範例我們只從 req拿出 url的資料這次要在利用req身上的事件處理

JavaScript 在訂閱事件時使用 addEventListener而 nodejs 使用的則是on這邊加上了監聽 data的事件會在瀏覽器傳送資料到Web Server時被執行參數是它所接收到的資料型態是字串

接著再增加 end的事件當瀏覽器的請求事件結束時它就會動作

由於瀏覽器使用 POST在上傳資料時會將資料一塊塊地上傳因為我們在監聽 data事件時透過 formData變數將它累加起來lt不過由於我們

上傳的資料很少一次就結束不過如果日後需要傳的是資料比較大的檔

案這個累加動作就很重要

當資料傳完就進到 end 事件中會用到 qsparse 來解析 formDataformData的內容是字串內容是

username=wordsmithampemail=wordsmith40somewhere

而 qsparse可以幫我們把這個 querystring轉成物件的格式也就是

username=wordsmithampemail=wordsmith40somewhere

一旦轉成物件並指定給 user之後其他的事情就和 GET方法時操作的一樣寫 response的表頭將內容回傳並將 userusername和 useremail代入到內容中

修改完成後接著執行 nodejs程式啟動 web server開啟瀏覽器進入表單測試看看POST的方式能否順利運作

完整程式碼如下

var http = require(rsquohttprsquo)

url = require(rsquourlrsquo)

fs = require(rdquofsrdquo)

qs = require(rsquoquerystringrsquo)

server

server = httpcreateServer(function (reqres)

var urlData

67 Express POST應用範例 61

Nodejs中文電子書

encode = rdquoutf8rdquo

filePath = rdquoviewexpress_post_example_formhtmlrdquo

formData

action

urlData = urlparse(requrltrue)

action = urlDatapathname

reswriteHead(200 rdquoContent-Typerdquordquotexthtml charset=utf-8rdquo)

if (action === rdquoSignuprdquo)

formData = rsquorsquo

reqon(rdquodatardquo function (data)

formData += data

)

reqon(rdquoendrdquo function ()

user = qsparse(formData)

resend(rdquolth1gtrdquo + userusername + rdquo 歡迎您的加入 lth1gtltpgt 我們已經將會員啟用信寄至rdquo + useremail + rdquoltpgtrdquo)

)

else

fsreadFile(filePath encode function(err file)

reswrite(file)

resend()

)

)

serverlisten(3000)

consolelog(rsquoServer 跑起來了現在時間是rsquo + new Date())

62 6 Express介紹

Nodejs中文電子書

68 Express AJAX應用範例

在 Nodejs要使用 Ajax傳送資料並且與之互動在接受資料的部份沒有太大的差別client端不是用 GET就是用 POST來傳資料重點在處理完後用 JSON格式回傳當然 Ajax不見得只傳 JSON格式有時是回傳一段 HTML碼不過後者對伺服器來說基本上就和前兩篇沒有差別了所以我們還是以回傳 JSON做為這一回的主題

這一回其實大多數的工作都會落在前端 Ajax上面前端要負責發送與接收資料並在接收資料後撤掉原先發送資料的表單並將取得的資料

改成 HTML格式之後放上頁面

首先先準備 HTML靜態頁面資料

ltDOCTYPE htmlgt

lthtmlgt

ltheadgt

lttitlegtNodejs 菜鳥筆記 (3)lttitlegt

ltlink rel=rdquostylesheetrdquo href=rdquocssstylecssrdquo type=rdquotextcssrdquo media=rdquoallrdquo gt

ltscript src=rdquohttpsajaxgoogleapiscomajaxlibsjquery171jqueryminjsrdquogtltscriptgt

ltheadgt

ltbodygt

lth1gtNodejs 菜鳥筆記-註冊 (用 Ajax)lth1gt

ltform id=rdquosignuprdquo action=rdquoSignuprdquo method=rdquoPOSTrdquogt

ltlabelgt 使用者名稱ltlabelgtltinput type=rdquotextrdquo id=rdquousernamerdquo name=rdquousernamerdquo gtltbrgt

ltlabelgt 電子郵件ltlabelgtltinput type=rdquotextrdquo id=rdquoemailrdquo name=rdquoemailrdquo gtltbrgt

ltinput type=rdquosubmitrdquo value=rdquo 註冊我的帳號rdquo gtltbrgt

ltformgt

ltdiv id=rdquoresultrdquogt

ltdivgt

ltscript type=rdquotextjavascriptrdquogt

$(function()$(rsquosignup input[type=rdquosubmitrdquo]rsquo)click(function(e)

var user =

userusername = $(rdquousernamerdquo)val()useremail = $(rdquoemailrdquo)val()

$post(rdquoSignuprdquouserfunction(data)greet(data)

)

68 Express AJAX應用範例 63

Nodejs中文電子書

epreventDefault()

)

var greet = function(msg)

var resultNode = $(rsquoresultrsquo)greeting = $(rsquolth2gtHirsquo+msgusername+rsquo 你的會員 id 是rsquo+ msgid +rsquo 我們會將會員啟動信寄至rsquo+msgemail+rsquolth2gtrsquo)

$(rsquosignuprsquo)hide()resultNodehtml(rdquordquo)

resultNodeappend(greeting)

)

ltscriptgt

ltbodygt

lthtmlgt

HTML頁面上準備了一個表單用來傳送註冊資料接著直接引用了Google CDN來載入 jQuery用來幫我們處理 Ajax的工作這次要傳送和接收的工作很大的變動都在 HTML頁面上的 JavaScript當中我們要做的事有 (相關 jQuery處理這邊不多做贅述指提起主要功能解說)

bull 用 jQUery取得 submit按鈕綁定它的 click動作

bull 取得表單 username和 email的值存放在 user這個物件中

bull 用 jQuery的 $post方法將 user的資料傳到 Server

bull 一旦成功取得資料後透過 greet這個 function組成回報給 user的訊息

bull 清空原本給使用者填資料的表單

bull 將 Server回傳的 usernameemail和 id這 3個資料組成回應的訊息

bull 將訊息放到原本表單的位置

經過以上的處理後一個 Ajax的表單的基本功能已經完成

接著進行 nodejs主要程式的編輯部分程式碼如下

var fs = require(rdquofsrdquo)

64 6 Express介紹

Nodejs中文電子書

qs = require(rsquoquerystringrsquo)

if (action === rdquoSignuprdquo)

formData = rsquorsquo

reqon(rdquodatardquo function (data)

formData += data

)

reqon(rdquoendrdquo function ()

var msg

user = qsparse(formData)

userid = rdquo123456rdquo

msg = JSONstringify(user)

reswriteHead(200 rdquoContent-Typerdquordquoapplicationjson charset=utf-8rdquordquoContent-Lengthrdquomsglength)

resend(msg)

)

這裡的程式和前面 POST範例基本上大同小異差別在

bull 幫 user的資料加上 id隨意存放一些文字進去讓 Server回傳的資料多於 Client端傳上來的不然會覺得 Server都沒做事

bull 增加了 msg 這個變數存放將 user 物件 JSON 文字化的結果JSONstringify這個轉換函式是 V8引擎所提供的如果你好奇的話

bull 大重點來了我們要告訴 Client端這次回傳的資料格式是 JSON所在 Content-type和 Content-Length要提供給 Client

Server很輕鬆就完成任務了最後進行程式測試啟動 nodejs主程式之後開啟瀏覽器就會看到表單填寫完畢按下送出就可以看到結果了

最後 nodejs本篇範例程式碼如下

var http = require(rsquohttprsquo)

url = require(rsquourlrsquo)

fs = require(rdquofsrdquo)

qs = require(rsquoquerystringrsquo)

server

68 Express AJAX應用範例 65

Nodejs中文電子書

server = httpcreateServer(function (reqres)

var urlData

encode = rdquoutf8rdquo

filePath = rdquoviewexpress_ajax_example_formhtmlrdquo

formData

action

urlData = urlparse(requrltrue)

action = urlDatapathname

if (action === rdquoSignuprdquo)

formData = rsquorsquo

reqon(rdquodatardquo function (data)

formData += data

)

reqon(rdquoendrdquo function ()

var msg

user = qsparse(formData)

userid = rdquo123456rdquo

msg = JSONstringify(user)

reswriteHead(200 rdquoContent-TyperdquordquoapplicationjsonrdquordquoContent-Lengthrdquomsglength)

resend(msg)

)

else

fsreadFile(filePath encode function(err file)

reswriteHead(200 rdquoContent-Typerdquordquotexthtml charset=utf-8rdquo)

reswrite(file)

resend()

)

)

serverlisten(3000)

66 6 Express介紹

Nodejs中文電子書

consolelog(rsquoServer 跑起來了現在時間是rsquo + new Date())

69 原始資料提供

bull [NodeJS初學者筆記 (1)-用 GET傳送資料] (httpithelpithomecomtwquestion10087402)

bull [NodeJS初學者筆記 (2)-用 POST傳送資料] (httpithelpithomecomtwquestion10087489)

bull [NodeJS 初學者筆記 (3)-用 Ajax 傳送資料] (httpithelpithomecomtwquestion10087627)

69 原始資料提供 67

Nodejs中文電子書

68 6 Express介紹

7

CoffeeScript

bull Spine

bull Hem

bull Spineapp

Letrsquos Have a Cup of CoffeeScript

bull es5-shim ECMAScript 5 compatibility shims for legacy JavaScript engines

ESnext

node-iform - A middleware for node-validator httpsgithubcomguileennode-iform

69

Nodejs中文電子書

70 7 CoffeeScript

8

製作一個 Hubot的 Plurk Adapter

81 應用事項提醒

此應用範例以CoffeeScript編寫主要程式架構採用Github釋放的Hubot專案以下有幾個主要注意事項請讀者注意

bull Hubot 一開始的架構除了內建的兩個 Adapter 之外其餘都要以Nodejs的Module方式才能運作

bull Hubot的 binhubot裡面寫著 npm install所以不管你怎麼改原始碼也不能改變第一點的狀況

其實上述都在討論同一件事情NodeJS的模組而且模組不能設定相對路徑之類的來安裝一定要包裝成 npm相符和格式才有辦法正確執行

82 建立 Adapter

首先需要準備兩個檔案

bull packagejson

bull plurkcoffee

71

Nodejs中文電子書

有這兩個就足以變成 NodeJS的模組了在開始 Coding之前先來設定一下 packagejson相依性

rdquonamerdquo rdquohubot-plurkrdquo

rdquoversionrdquo rdquo011rdquo

rdquomainrdquo rdquoplurkrdquo

rdquodependenciesrdquo

rdquohubotrdquo rdquogt=205rdquo

rdquooauthrdquo rdquordquo

rdquocronrdquordquordquo

這邊會使用到NodeJS的 cron模組(Module)而登入 Plurk需要OAuth標準授權才行所以也需要加載 OAuth模組

注意Hubot在讀取非內建模組時會自動在前面加上 hubot-的前置

83 建立 Robot跟 API

本篇應用基本上程式碼大部分參考 Hubot - Twitter Adapter來製作主要差異只有在使用 OAuth這個模組Twitter有 Streaming可用而 Plurk則得用 Comet方式來達到即時讀取

plurkcoffee程式碼

Robot = require(rdquohubotrdquo)robot()

Adapter = require(rdquohubtordquo)adapter()

EventEmitter = require(rdquoeventsrdquo)EventEmitter

oauth = require(rdquooauthrdquo)

cronJob = require(rdquocronrdquo)CronJob

class Plurk exntends Adapter

class PlurkStreaming exnteds EventEmitter

72 8 製作一個 Hubot的 Plurk Adapter

Nodejs中文電子書

先弄個基本架構主要 Class皆參考 Twitter Adapter命名方式接著再將 Plurk這個 Class增加幾個Method基本上只要有 run send reply就夠了而 run用來做初始化的部分

class Plurk entends Adapter

send (plurk id strings⋯) -gt

reply (plurk id strings⋯) -gt

run -gt

看起來有點東西接著來處理主要的 Plurk API結合部分

class PlurkStreaming extends EventEmitter

consuctor (options) -gt

plurk (callback) -gt

觀察河道

getChannel -gt

取得 Comet 網址

reply (plurk id message) -gt

回噗

acceptFriends -gt

接受好友

get (path callback) -gt

GET 請求

post (path body callback)-gt

POST 請求(其實是裝飾)

request (method path body callback)-gt

主要的 OAuth 請求

comet (server callback)-gt

噗浪的 Comet 傳回是 JavaScript Callback 要另外處理後才會變成 JSON

然後把注意力集中到 constructor上先把建構子弄好

constructor (options) -gt

super()

if optionskey and optionssecret and optionstoken and optionstoken secret

key = optionskey

secret = optionssecret

token = optionstoken

token secret = optionstoken secret

83 建立 Robot跟 API 73

Nodejs中文電子書

建立 OAuth 連接

consumer = new oauthOAuth(

rdquohttpwwwplurkcomOAuthrequest tokenrdquo

rdquohttpwwwplurkcomOAuthaccess tokenrdquo

key

secret

rdquo10rdquo

rdquohttpwwwplurkcomOAuthauthorizerdquo

rdquoHMAC-SHA1rdquo

)

domain = rdquowwwplurkcomrdquo

初始化取得 Comet 網址

do getChannel

else

throw new Error(rdquo 參數不足需要 Key Secret Token Token Secretrdquo)

接著來處理 request這個 method

request (method path body callback) -gt

記錄一下這次的 Request

consolelog(rdquohttpdomainpathrdquo)

Callback 這邊先不丟進去要用另一種方式處理

request = consumerget(rdquohttpdomainpathrdquo token token secret null)

requeston rdquoresponserdquo (res) -gt

reson rdquodatardquo (chunk) -gt

parseResponse(chunk+rsquorsquo callback)

reson rdquoendrdquo (data) -gt

consolelog rdquoEnd Request pathrdquo

reson rdquoerrorrdquo (data) -gt

consolelog rdquoError rdquo + data

requestend()

處理資料

parseResponse = (data callback) -gt

if datalength gt 0

用 TryCatch 避免處理 JSON 出錯導致整個中斷

try

callback null JSONparse(data)

catch err

74 8 製作一個 Hubot的 Plurk Adapter

Nodejs中文電子書

consolelog(rdquoError Parse JSONrdquo + data err)

繼續執行

callback null data ||

大致上就是這樣上面程式的架構已經將整個 Hubot Plurk Adapter完成因為在測試時竟然因為噗浪 Lag而沒讀到完整的 Comet資料然後造成程式異常為了避免這個問題發生因此需要加上為了完美呈現需要再

加上 Comet的處理所以要使用到 EventEmitter的功能

comet (server callback) -gt

在 Callback 裡面會找不到自身所以設定區域變數

self =

記錄一下這次的 Request

consolelog(rdquo[Comet] serverrdquo)

Callback 這邊先不丟進去要用另一種方式處理

request = consumerget(rdquohttpdomainpathrdquo token token secret null)

requeston rdquoresponserdquo (res) -gt

reson rdquodatardquo (chunk) -gt

parseResponse(chunk+rsquorsquo callback)

reson rdquoendrdquo (data) -gt

consolelog rdquoEnd Request pathrdquo

請求結束發出事件通知可以進行下一次請求

selfemit rdquonextPlurkrdquo

reson rdquoerrorrdquo (data) -gt

consolelog rdquoError rdquo + data

requestend()

處理資料

parseResponse = (data callback) -gt

if datalength gt 0

用 trycatch 避免失敗中斷

try

去掉 JavaScript 的 Callback

data = datamatch(CometChannelscriptCallback((+))s)

jsonData = rdquordquo

if data

83 建立 Robot跟 API 75

Nodejs中文電子書

jsonData = JSONparse(data[1])

else

如果沒有任何 Match 嘗試直接 parse

jsonData = JSONparse(data)

catch err

consolelog(rdquo[Comet] Errorrdquo data err)

用 TryCatch 避免處理 JSON 出錯導致整個中斷

try

只傳入 json 的 data 部分

callback null jsonDatadata

catch err

consolelog(rdquo[Comet]Error Parse JSONrdquo + data err)

繼續執行

callback null data ||

後面的 get跟 post就簡單多了

get (path callback) -gt

request(rdquoGETrdquo path null callback)

post (path body callback) -gt

request(rdquoPOSTrdquo path body callback)

接著處理取的 Comet網址的 getChannel

getChannel -gt

self =

get rdquoAPPRealtimegetUserChannelrdquo (error data) -gt

if error

檢查是否有 comet server

if datacomet server

selfchannel = datacomet server

如果沒有 Channel Ready 就嘗試連接會失敗

selfemit(rsquochannel readyrsquo)

那麼先來處理 Plurk Adaper好處理的部份

send (plruk id strings⋯)-gt

跟 Reply 一樣直接交給 reply 做

reply plurk id strings⋯

76 8 製作一個 Hubot的 Plurk Adapter

Nodejs中文電子書

reply (plurk id strings⋯) -gt

stringsforEach (message) =gt

botreply(plruk id message)

接著把 run處理好就可以上線運作摟

run -gt

self =

options =

key processenvHUBOT PLURK KEY

secret processenvHUBOT PLURK SECRET

token processenvHUBOT PLURK TOKEN

token secret processenvHUBOT PLURK TOKEN SECRET

創建剛剛的 API

bot = new PlurkStreaming(options)

依照 Twitter 的 new RobotTextMessage 會沒有反應所以參考 hubot-minecraft 的方式

r = robotconstructor

處理噗浪河道訊息

doPlurk = (data)-gt

檢查是否為回噗

if dataresponse

datacontent raw = dataresponsecontent raw

datauser id = dataresponseuser id

確定有噗浪 ID 跟訊息

if dataplurk id and datacontent raw

selfreceive new rTextMessage(dataplurk id datacontent raw)

取得 Comet Server 完成開始第一次 Comet 連接

boton rdquochannel readyrdquo () -gt

botplurk selfdoPlurk

上一次 Comet 完成繼續 Polling

boton rdquonextPlurkrdquo ()-gt

botplurk selfdoPlurk

定時接受好友邀請

do botacceptFriends

bot = bot

83 建立 Robot跟 API 77

Nodejs中文電子書

終於完成 AdapterHubot專案裡面的 scripts資料夾內是互動部分不需要像 Adapter如此大費周章處理只新增檔案並且設計好對白之後就會回噗了我開發用的機器人在此大家可以去跟他玩玩[httpplurkcomelct9620 bot](httpplurkcomelct9620 bot)

84 原始資料提供

bull [製 作 一 個 Hubot 的 噗 浪 Adapter](http revo-skillfrosttw blog20120318create-a-hubot-plurk-adapter)

78 8 製作一個 Hubot的 Plurk Adapter

9

Nodejs好用工具介紹

91 logio

Real-time log monitoring in your browser

httplogioorg

nodejs (gt=0412 lt=0611)

npm config set unsafe-perm true

npm install -g --prefix=usrlocal logio

logio server start

httplocalhost8998

79

Nodejs中文電子書

80 9 Nodejs好用工具介紹

10

精選文章

101 Express

bull 使用 NodeJS + Express從 GETPOST Request取值 [邀稿中]

bull link2 [邀稿中]

bull link3 [邀稿中]

81

Nodejs中文電子書

82 10 精選文章

11

參考資源

111 Nodejs書籍

bull Programming Nodejs April 2012 (est) Travis Swicegood Pragmatic Bookshelf

bull e Little Book on CoffeeScript January 2012 Alex MacCaw OrsquoReilly Media

bull Node for Front-End Developers January 2012 Garann Means OrsquoReilly Media

bull Building Hypermedia APIs with HTML5 and Node November 2011 MikeAmundsen OrsquoReilly Media

bull Node Web Development August 2011 David Herron PacktPub

bull CoffeeScript Accelerated JavaScript Development July 2011 Trevor BurnhamPragmatic Bookshelf

bull Getting Started with GEO CouchDB and Nodejs July 2011 Mick omp-son OrsquoReilly Media

bull What Is Node July 2011 Brett McLaughlin OrsquoReilly Media

bull Node Up and Running May 2011 Tom Hughes-Croucher Mike WilsonOrsquoReilly Media

83

Nodejs中文電子書

bull Hands-on Nodejs e Nodejs introduction and API reference May 2011 Pe-dro Teixeira LeanPub

bull e Node Beginner Book Manuel Kiessling

bull Mastering Nodejs visionmedia

112 Nodejs影音教學

bull e Node Sessions e Best of OSCON 2011 August 2011 OrsquoReilly Media(Video)

bull Tom Hughes-Croucher on Node March 2011 OrsquoReilly Media (Video)

113 Nodejs教學網站

bull Node Tuts

114 Nodejs課程

bull Introduction To Nodejs Van Nguyen CodeLesson

84 11 參考資源

Page 55: Nodejs Wiki

Nodejs中文電子書

執行結果(範例)

-- coffee-script120

- express256

- connect185

| -- formidable108

-- mime124

-- mkdirp007

-- qs041

檢視某個套件的詳細資訊例如

npm show express

升級所有套件(如果該套件已發佈更新版本)

npm update

升級指定的套件

npm update express

移除指定的套件

npm uninstall express

54 使用 packagejson

對於正式的 Nodejs專案可以建立一個命名為 packagejson的設

定檔(純文字格式)檔案內容參考範例如下

54 使用 packagejson 49

Nodejs中文電子書

packagejson(範例)

rdquonamerdquo rdquoapplication-namerdquo

rdquoversionrdquo rdquo001rdquo

rdquoprivaterdquo true

rdquodependenciesrdquo

rdquoexpressrdquo rdquo255rdquo

rdquocoffee-scriptrdquo rdquolatestrdquo

rdquomongooserdquo rdquogt= 253rdquo

其中 name與 version依照專案的需求設置

需要注意的是 dependencies的設定它用於指定專案相依的套件名

稱及版本

bull rdquoexpressrdquo rdquo255rdquo

代表此專案相依版本 255的 express套件

bull rdquocoffee-scriptrdquo rdquolatestrdquo

使用最新版的 coffee-script套件(每次更新都會檢查新版)

bull rdquomongooserdquo rdquogt= 253rdquo

使用版本大於 253的 mongoose套件

假設某個套件的新版可能造成專案無法正常運作就必須指定套件的

版本避免專案的程式碼來不及更新以相容新版套件通常在開發初期的

專案需要盡可能維持新套件的相容性(以取得套件的更新或修正)可以

用「gt=」設定最低相容的版本或是使用「latest」設定永遠保持最新

套件

50 5 NPM套件管理工具

6

Express介紹

在前面的 nodejs基礎當中介紹許多許多開設 http的使用方法及介紹以及許多基本的 nodejs基本應用

接下來要介紹一個套件稱為 express [Express](httpexpressjscom)這個套件主要幫忙解決許多 nodejs http server所需要的基本服務讓開發 httpservice變得更為容易不需要像之前需要透過層層模組(module)才有辦法開始編寫自己的程式

這個套件是由 TJ Holowaychuk 製作而成的套件裡面包含基本的路由處理 (route)http資料處理(GETPOSTPUT)另外還與樣板套件(jshtml template engine)搭配同時也可以處理許多複雜化的問題

61 Express安裝

安裝方式十分簡單只要透過之前介紹的 NPM就可以使用簡單的指令安裝指令如下

這邊建議需要將此套件安裝成為全域模組方便日後使用

51

Nodejs中文電子書

62 Express基本操作

express的使用也十分簡單先來建立一個基本的 hello world

var app = require(rsquoexpressrsquo)createServer()

port = 1337

applisten(port)

appget(rsquorsquo function(req res)

ressend(rsquohello worldrsquo)

)

consolelog(rsquostart express servernrsquo)

可以從上面的程式碼發現基本操作與 nodejs http的建立方式沒有太大差異主要差在當我們設定路由時可以直接透過 appget方式設定回應與接受方式

63 Express路由處理

Express對於 http服務上有許多包裝讓開發者使用及設定上更為方便例如有幾個路由設定那我們就統一藉由 appget來處理

Create http server

appget(rsquorsquo function(req res)

ressend(rsquohello worldrsquo)

)

appget(rsquotestrsquo function(req res)

ressend(rsquotest renderrsquo)

)

appget(rsquouserrsquo function(req res)

ressend(rsquouser pagersquo)

)

52 6 Express介紹

Nodejs中文電子書

如上面的程式碼所表示appget可以帶入兩個參數第一個是路徑名稱設定第二個為回應函式 (call back function)回應函式裡面就如同之前的 createServer方法裡面包含 requestresponse兩個物件可供使用使用者就可以透過瀏覽器輸入不同的 url切換到不同的頁面顯示不同的結果

路由設定上也有基本的配對方式讓使用者從瀏覽器輸入的網址可以

是一個變數只要符合型態就可以有對應的頁面產出例如

Create http server

appget(rsquouseridrsquo function(req res)

ressend(rsquouser rsquo + reqparamsid)

)

appget(rsquonumberrsquo function(req res)

ressend(rsquonumber rsquo + reqparamsnumber)

)

裡 面 使 用 到number 從 網 址 輸 入 之 後 就 可 以 直 接 使 用reqparamsnumber 取得所輸入的資料變成 url 參數使用當然前面也是可以加上路徑的設定userid在瀏覽器上路徑必須符合userxxx透

過 reqparamsid就可以取到 xxx這個字串值

另外express參數處理也提供了路由參數配對處理也可以透過正規表示法作為參數設定

var app = require(rsquoexpressrsquo)createServer()

port = 1337

applisten(port)

appget(^ip((d23)((d23))((d23))((d23))) function(req res)

ressend(reqparams)

)

上面程式碼可以發現後面路由設定的型態是正規表示法裡面設定

格式為ip之後必須要加上 ip型態才會符合資料格式同時取得 ip資料已經由正規表示法將資料做分群因此可以取得 ip的四個數字

此程式執行之後可以透過瀏覽器測試輸入網址為 localhost3000ip25525510010可以從頁面獲得資料

63 Express路由處理 53

Nodejs中文電子書

此章節全部範例程式碼如下

overview

author Caesar Chi

blog clonnblogspotcom

version 20120226

create server

var app = require(rsquoexpressrsquo)createServer()

port = 1337

applisten(port)

normal style

appget(rsquorsquo function(req res)

ressend(rsquohello worldrsquo)

)

appget(rsquotestrsquo function(req res)

ressend(rsquotest renderrsquo)

)

parameter style

appget(rsquouseridrsquo function(req res)

ressend(rsquouser rsquo + reqparamsid)

)

appget(rsquonumberrsquo function(req res)

ressend(rsquonumber rsquo + reqparamsnumber)

)

REGX style

appget(^ip((d23)((d23))((d23))) function(req res)

ressend(reqparams)

)

appget(rsquorsquo function(req res)

ressend(rsquoPage not foundrsquo 404)

54 6 Express介紹

Nodejs中文電子書

)

consolelog(rsquostart express servernrsquo)

64 Express middleware

Express 裡面有一個十分好用的應用概念稱為 middleware可以透過middleware做出複雜的效果同時上面也有介紹 next方法參數傳遞就是靠 middleware的概念來傳遞參數讓開發者可以明確的控制程式邏輯

create http server

appuse(expressbodyParser())

appuse(expressmethodOverride())

appuse(expresssession())

上面都是一種 middleware的使用方式透過 appuse方式裡面載入函式執行方法回應函式會包含三個基本參數responserequestnext其中next表示下一個 middleware執行函式同時會自動將預設三個參數繼續帶往下個函式執行底下有個實驗

上面的片段程式執行後開啟瀏覽器連結上 localhost1337會發現伺服器回應結果順序如下

first middle ware

second middle ware

execute middle ware

end middleware function

從上面的結果可以得知剛才設定的 middleware都生效了在 appuse設定的 middleware是所有 url皆會執行方法如果有指定特定方法就可以使用 appget的 middleware設定在 appget函式的第二個參數就可以帶入函式或者是匿名函式只要函式裡面最後會接受 request response next這三個參數同時也有正確指定 next函式的執行時機最後都會執行到最後一個方法當然開發者也可以評估程式邏輯要執行到哪一個階段讓邏輯

可以更為分明

64 Express middleware 55

Nodejs中文電子書

65 Express路由應用

在實際開發上可能會遇到需要使用參數等方式混和變數一起使用

express裡面提供了一個很棒的處理方法 appall這個方式可以先採用基本路由配對再將設定為每個不同的處理方式開發者可以透過這個方式簡

化自己的程式邏輯

overview

author

version 20120226

create server

var app = require(rsquoexpressrsquo)createServer()

port = 1337

users = [

name rsquoClonnrsquo

name rsquoChirsquo

]

applisten(port)

appall(rsquouseridoprsquo function(req res next)

requser = users[reqparamsid]

if (requser)

next()

else

next(new Error(rsquocannot find user rsquo + reqparamsid))

)

appget(rsquouseridrsquo function(req res)

ressend(rsquoviewing rsquo + requsername)

)

appget(rsquouserideditrsquo function(req res)

ressend(rsquoediting rsquo + requsername)

)

56 6 Express介紹

Nodejs中文電子書

appget(rsquouseriddeletersquo function(req res)

ressend(rsquodeleting rsquo + requsername)

)

appget(rsquorsquo function(req res)

ressend(rsquoPage not foundrsquo 404)

)

consolelog(rsquostart express servernrsquo)

內部宣告一組預設的使用者分別給予名稱設定藉由 appall這個方法可以先將路由雛形建立再接下來設定 appget的路徑格式只要符合格式就會分配進入對應的方法中像上面的程式當中如果使用者輸入路徑為user0除了執行 appall程式之後執行 next方法就會對應到路徑設定為userid的這個方法當中如果使用者輸入路徑為user0edit就會執行到useridedit的對應方法

66 Express GET應用範例

我們準備一個使用 GET方法傳送資料的表單

ltDOCTYPE htmlgt

lthtmlgt

ltheadgt

ltmeta http-equiv=rdquoContent-Typerdquo content=rdquotexthtml charset=utf8rdquogt

lttitlegtNodejs 菜鳥筆記 (1)lttitlegt

ltlink rel=rdquostylesheetrdquo href=rdquocssstylecssrdquo type=rdquotextcssrdquo media=rdquoallrdquo gt

ltheadgt

ltbodygt

lth1gtNodejs 菜鳥筆記-註冊lth1gt

ltform id=rdquosignuprdquo method=rdquoGETrdquo action=rdquohttplocalhost3000Signuprdquogt

ltlabelgt 使用者名稱ltlabelgtltinput type=rdquotextrdquo id=rdquousernamerdquo name=rdquousernamerdquo gtltbrgt

ltlabelgt 電子郵件ltlabelgtltinput type=rdquotextrdquo id=rdquoemailrdquo name=rdquoemailrdquo gtltbrgt

ltinput type=rdquosubmitrdquo value=rdquo 註冊我的帳號rdquo gtltbrgt

ltformgt

ltbodygt

lthtmlgt

66 Express GET應用範例 57

Nodejs中文電子書

這個表單沒有什麼特別的地方我們只需要看第 9 行form 使用的method是 GET然後 action是rdquohttplocalhost3000Signupldquo等一下我們要來撰寫Signup這個 URL Path的處理程式

處理 Signup行為

我們知道所謂的 GET方法會透過 URL來把表單的值給帶過去以上面的表單來說到時候 URL會以這樣的形式傳遞

httplocalhost3000Signupusername=xxxampemail=xxx

所以要能處理這樣的資料必須有以下功能

bull 解析 URL

bull 辨別動作是 Signup

bull 解析出 username和 email

一旦能取得 username和 email的值程式就能加以應用了

處理 Signup的程式碼雛形

load module

var url = require(rsquourlrsquo)

urlData = urlparse(requrltrue)

action = urlDatapathname

reswriteHead(200 rdquoContent-Typerdquordquotexthtml charset=utf-8rdquo)

if (action === rdquoSignuprdquo)

user = urlDataquery

resend(rdquolth1gtrdquo + userusername + rdquo 歡迎您的加入 lth1gtltpgt 我們已經將會員啟用信寄至rdquo + useremail + rdquoltpgtrdquo)

首先需要加載 url module它是用來協助我們解析 URL的模組接著使用 urlparse方法第一個傳入 url字串變數也就是 requrl另外第二個參數的用意是設為 ture則引進 querystring模組來協助處理預設是 false它影響到的是 urlDataquery設為 true會傳回物件不然就只是一般的字串urlparse會將字串內容整理成一個物件我們把它指定給 urlData

action變數作為記錄 pathname這是我們稍後要來判斷目前網頁的動作是什麼接著先將 html表頭資訊 (Header)準備好再來判斷路徑邏輯如

58 6 Express介紹

Nodejs中文電子書

果是 Signup這個動作就把 urlDataquery裡的資料指定給 user然後輸出userusername和 useremail把使用者從表單註冊的資料顯示於頁面中

最後進行程式測試啟動 nodejs主程式之後開啟瀏覽器就會看到表單填寫完畢按下送出就可以看到結果了

完整 nodejs程式碼如下

var http = require(rsquohttprsquo)

url = require(rsquourlrsquo)

fs = require(rdquofsrdquo)

server

server = httpcreateServer(function (reqres)

var urlData

encode = rdquoutf8rdquo

filePath = rdquoviewexpress_get_example_formhtmlrdquo

action

urlData = urlparse(requrltrue)

action = urlDatapathname

reswriteHead(200 rdquoContent-Typerdquordquotexthtml charset=utf-8rdquo)

if (action === rdquoSignuprdquo)

user = urlDataquery

resend(rdquolth1gtrdquo + userusername + rdquo 歡迎您的加入 lth1gtltpgt 我們已經將會員啟用信寄至rdquo + useremail + rdquoltpgtrdquo)

else

fsreadFile(filePath encode function(err file)

reswrite(file)

resend()

)

)

serverlisten(3000)

consolelog(rsquoServer 跑起來了現在時間是rsquo + new Date())

66 Express GET應用範例 59

Nodejs中文電子書

67 Express POST應用範例

一開始準備基本的 html表單傳送內容以 POST方式form的 action屬性設定為 POST其餘 html內容與前一個範例應用相同

ltDOCTYPE htmlgt

lthtmlgt

ltheadgt

ltmeta http-equiv=rdquoContent-Typerdquo content=rdquotexthtml charset=utf8rdquogt

lttitlegtNodejs 菜鳥筆記 (1)lttitlegt

ltlink rel=rdquostylesheetrdquo href=rdquocssstylecssrdquo type=rdquotextcssrdquo media=rdquoallrdquo gt

ltheadgt

ltbodygt

lth1gtNodejs 菜鳥筆記-註冊lth1gt

ltform id=rdquosignuprdquo method=rdquoPOSTrdquo action=rdquohttplocalhost3000Signuprdquogt

ltlabelgt 使用者名稱ltlabelgtltinput type=rdquotextrdquo id=rdquousernamerdquo name=rdquousernamerdquo gtltbrgt

ltlabelgt 電子郵件ltlabelgtltinput type=rdquotextrdquo id=rdquoemailrdquo name=rdquoemailrdquo gtltbrgt

ltinput type=rdquosubmitrdquo value=rdquo 註冊我的帳號rdquo gtltbrgt

ltformgt

ltbodygt

lthtmlgt

nodejs的程式處理邏輯與前面 GET範例類似部分程式碼如下

qs = require(rsquoquerystringrsquo)

if (action === rdquoSignuprdquo)

formData = rsquorsquo

reqon(rdquodatardquo function (data)

formData += data

)

reqon(rdquoendrdquo function ()

user = qsparse(formData)

resend(rdquolth1gtrdquo + userusername + rdquo 歡迎您的加入 lth1gtltpgt 我們已經將會員啟用信寄至rdquo + useremail + rdquoltpgtrdquo)

)

60 6 Express介紹

Nodejs中文電子書

主要加入了rsquoquerystringrsquo這個moduel方便我們等一下解析由表單 POST回來的資料另外加入一個 formData的變數用來搜集待等一下表單回傳的資料前面的 GET範例我們只從 req拿出 url的資料這次要在利用req身上的事件處理

JavaScript 在訂閱事件時使用 addEventListener而 nodejs 使用的則是on這邊加上了監聽 data的事件會在瀏覽器傳送資料到Web Server時被執行參數是它所接收到的資料型態是字串

接著再增加 end的事件當瀏覽器的請求事件結束時它就會動作

由於瀏覽器使用 POST在上傳資料時會將資料一塊塊地上傳因為我們在監聽 data事件時透過 formData變數將它累加起來lt不過由於我們

上傳的資料很少一次就結束不過如果日後需要傳的是資料比較大的檔

案這個累加動作就很重要

當資料傳完就進到 end 事件中會用到 qsparse 來解析 formDataformData的內容是字串內容是

username=wordsmithampemail=wordsmith40somewhere

而 qsparse可以幫我們把這個 querystring轉成物件的格式也就是

username=wordsmithampemail=wordsmith40somewhere

一旦轉成物件並指定給 user之後其他的事情就和 GET方法時操作的一樣寫 response的表頭將內容回傳並將 userusername和 useremail代入到內容中

修改完成後接著執行 nodejs程式啟動 web server開啟瀏覽器進入表單測試看看POST的方式能否順利運作

完整程式碼如下

var http = require(rsquohttprsquo)

url = require(rsquourlrsquo)

fs = require(rdquofsrdquo)

qs = require(rsquoquerystringrsquo)

server

server = httpcreateServer(function (reqres)

var urlData

67 Express POST應用範例 61

Nodejs中文電子書

encode = rdquoutf8rdquo

filePath = rdquoviewexpress_post_example_formhtmlrdquo

formData

action

urlData = urlparse(requrltrue)

action = urlDatapathname

reswriteHead(200 rdquoContent-Typerdquordquotexthtml charset=utf-8rdquo)

if (action === rdquoSignuprdquo)

formData = rsquorsquo

reqon(rdquodatardquo function (data)

formData += data

)

reqon(rdquoendrdquo function ()

user = qsparse(formData)

resend(rdquolth1gtrdquo + userusername + rdquo 歡迎您的加入 lth1gtltpgt 我們已經將會員啟用信寄至rdquo + useremail + rdquoltpgtrdquo)

)

else

fsreadFile(filePath encode function(err file)

reswrite(file)

resend()

)

)

serverlisten(3000)

consolelog(rsquoServer 跑起來了現在時間是rsquo + new Date())

62 6 Express介紹

Nodejs中文電子書

68 Express AJAX應用範例

在 Nodejs要使用 Ajax傳送資料並且與之互動在接受資料的部份沒有太大的差別client端不是用 GET就是用 POST來傳資料重點在處理完後用 JSON格式回傳當然 Ajax不見得只傳 JSON格式有時是回傳一段 HTML碼不過後者對伺服器來說基本上就和前兩篇沒有差別了所以我們還是以回傳 JSON做為這一回的主題

這一回其實大多數的工作都會落在前端 Ajax上面前端要負責發送與接收資料並在接收資料後撤掉原先發送資料的表單並將取得的資料

改成 HTML格式之後放上頁面

首先先準備 HTML靜態頁面資料

ltDOCTYPE htmlgt

lthtmlgt

ltheadgt

lttitlegtNodejs 菜鳥筆記 (3)lttitlegt

ltlink rel=rdquostylesheetrdquo href=rdquocssstylecssrdquo type=rdquotextcssrdquo media=rdquoallrdquo gt

ltscript src=rdquohttpsajaxgoogleapiscomajaxlibsjquery171jqueryminjsrdquogtltscriptgt

ltheadgt

ltbodygt

lth1gtNodejs 菜鳥筆記-註冊 (用 Ajax)lth1gt

ltform id=rdquosignuprdquo action=rdquoSignuprdquo method=rdquoPOSTrdquogt

ltlabelgt 使用者名稱ltlabelgtltinput type=rdquotextrdquo id=rdquousernamerdquo name=rdquousernamerdquo gtltbrgt

ltlabelgt 電子郵件ltlabelgtltinput type=rdquotextrdquo id=rdquoemailrdquo name=rdquoemailrdquo gtltbrgt

ltinput type=rdquosubmitrdquo value=rdquo 註冊我的帳號rdquo gtltbrgt

ltformgt

ltdiv id=rdquoresultrdquogt

ltdivgt

ltscript type=rdquotextjavascriptrdquogt

$(function()$(rsquosignup input[type=rdquosubmitrdquo]rsquo)click(function(e)

var user =

userusername = $(rdquousernamerdquo)val()useremail = $(rdquoemailrdquo)val()

$post(rdquoSignuprdquouserfunction(data)greet(data)

)

68 Express AJAX應用範例 63

Nodejs中文電子書

epreventDefault()

)

var greet = function(msg)

var resultNode = $(rsquoresultrsquo)greeting = $(rsquolth2gtHirsquo+msgusername+rsquo 你的會員 id 是rsquo+ msgid +rsquo 我們會將會員啟動信寄至rsquo+msgemail+rsquolth2gtrsquo)

$(rsquosignuprsquo)hide()resultNodehtml(rdquordquo)

resultNodeappend(greeting)

)

ltscriptgt

ltbodygt

lthtmlgt

HTML頁面上準備了一個表單用來傳送註冊資料接著直接引用了Google CDN來載入 jQuery用來幫我們處理 Ajax的工作這次要傳送和接收的工作很大的變動都在 HTML頁面上的 JavaScript當中我們要做的事有 (相關 jQuery處理這邊不多做贅述指提起主要功能解說)

bull 用 jQUery取得 submit按鈕綁定它的 click動作

bull 取得表單 username和 email的值存放在 user這個物件中

bull 用 jQuery的 $post方法將 user的資料傳到 Server

bull 一旦成功取得資料後透過 greet這個 function組成回報給 user的訊息

bull 清空原本給使用者填資料的表單

bull 將 Server回傳的 usernameemail和 id這 3個資料組成回應的訊息

bull 將訊息放到原本表單的位置

經過以上的處理後一個 Ajax的表單的基本功能已經完成

接著進行 nodejs主要程式的編輯部分程式碼如下

var fs = require(rdquofsrdquo)

64 6 Express介紹

Nodejs中文電子書

qs = require(rsquoquerystringrsquo)

if (action === rdquoSignuprdquo)

formData = rsquorsquo

reqon(rdquodatardquo function (data)

formData += data

)

reqon(rdquoendrdquo function ()

var msg

user = qsparse(formData)

userid = rdquo123456rdquo

msg = JSONstringify(user)

reswriteHead(200 rdquoContent-Typerdquordquoapplicationjson charset=utf-8rdquordquoContent-Lengthrdquomsglength)

resend(msg)

)

這裡的程式和前面 POST範例基本上大同小異差別在

bull 幫 user的資料加上 id隨意存放一些文字進去讓 Server回傳的資料多於 Client端傳上來的不然會覺得 Server都沒做事

bull 增加了 msg 這個變數存放將 user 物件 JSON 文字化的結果JSONstringify這個轉換函式是 V8引擎所提供的如果你好奇的話

bull 大重點來了我們要告訴 Client端這次回傳的資料格式是 JSON所在 Content-type和 Content-Length要提供給 Client

Server很輕鬆就完成任務了最後進行程式測試啟動 nodejs主程式之後開啟瀏覽器就會看到表單填寫完畢按下送出就可以看到結果了

最後 nodejs本篇範例程式碼如下

var http = require(rsquohttprsquo)

url = require(rsquourlrsquo)

fs = require(rdquofsrdquo)

qs = require(rsquoquerystringrsquo)

server

68 Express AJAX應用範例 65

Nodejs中文電子書

server = httpcreateServer(function (reqres)

var urlData

encode = rdquoutf8rdquo

filePath = rdquoviewexpress_ajax_example_formhtmlrdquo

formData

action

urlData = urlparse(requrltrue)

action = urlDatapathname

if (action === rdquoSignuprdquo)

formData = rsquorsquo

reqon(rdquodatardquo function (data)

formData += data

)

reqon(rdquoendrdquo function ()

var msg

user = qsparse(formData)

userid = rdquo123456rdquo

msg = JSONstringify(user)

reswriteHead(200 rdquoContent-TyperdquordquoapplicationjsonrdquordquoContent-Lengthrdquomsglength)

resend(msg)

)

else

fsreadFile(filePath encode function(err file)

reswriteHead(200 rdquoContent-Typerdquordquotexthtml charset=utf-8rdquo)

reswrite(file)

resend()

)

)

serverlisten(3000)

66 6 Express介紹

Nodejs中文電子書

consolelog(rsquoServer 跑起來了現在時間是rsquo + new Date())

69 原始資料提供

bull [NodeJS初學者筆記 (1)-用 GET傳送資料] (httpithelpithomecomtwquestion10087402)

bull [NodeJS初學者筆記 (2)-用 POST傳送資料] (httpithelpithomecomtwquestion10087489)

bull [NodeJS 初學者筆記 (3)-用 Ajax 傳送資料] (httpithelpithomecomtwquestion10087627)

69 原始資料提供 67

Nodejs中文電子書

68 6 Express介紹

7

CoffeeScript

bull Spine

bull Hem

bull Spineapp

Letrsquos Have a Cup of CoffeeScript

bull es5-shim ECMAScript 5 compatibility shims for legacy JavaScript engines

ESnext

node-iform - A middleware for node-validator httpsgithubcomguileennode-iform

69

Nodejs中文電子書

70 7 CoffeeScript

8

製作一個 Hubot的 Plurk Adapter

81 應用事項提醒

此應用範例以CoffeeScript編寫主要程式架構採用Github釋放的Hubot專案以下有幾個主要注意事項請讀者注意

bull Hubot 一開始的架構除了內建的兩個 Adapter 之外其餘都要以Nodejs的Module方式才能運作

bull Hubot的 binhubot裡面寫著 npm install所以不管你怎麼改原始碼也不能改變第一點的狀況

其實上述都在討論同一件事情NodeJS的模組而且模組不能設定相對路徑之類的來安裝一定要包裝成 npm相符和格式才有辦法正確執行

82 建立 Adapter

首先需要準備兩個檔案

bull packagejson

bull plurkcoffee

71

Nodejs中文電子書

有這兩個就足以變成 NodeJS的模組了在開始 Coding之前先來設定一下 packagejson相依性

rdquonamerdquo rdquohubot-plurkrdquo

rdquoversionrdquo rdquo011rdquo

rdquomainrdquo rdquoplurkrdquo

rdquodependenciesrdquo

rdquohubotrdquo rdquogt=205rdquo

rdquooauthrdquo rdquordquo

rdquocronrdquordquordquo

這邊會使用到NodeJS的 cron模組(Module)而登入 Plurk需要OAuth標準授權才行所以也需要加載 OAuth模組

注意Hubot在讀取非內建模組時會自動在前面加上 hubot-的前置

83 建立 Robot跟 API

本篇應用基本上程式碼大部分參考 Hubot - Twitter Adapter來製作主要差異只有在使用 OAuth這個模組Twitter有 Streaming可用而 Plurk則得用 Comet方式來達到即時讀取

plurkcoffee程式碼

Robot = require(rdquohubotrdquo)robot()

Adapter = require(rdquohubtordquo)adapter()

EventEmitter = require(rdquoeventsrdquo)EventEmitter

oauth = require(rdquooauthrdquo)

cronJob = require(rdquocronrdquo)CronJob

class Plurk exntends Adapter

class PlurkStreaming exnteds EventEmitter

72 8 製作一個 Hubot的 Plurk Adapter

Nodejs中文電子書

先弄個基本架構主要 Class皆參考 Twitter Adapter命名方式接著再將 Plurk這個 Class增加幾個Method基本上只要有 run send reply就夠了而 run用來做初始化的部分

class Plurk entends Adapter

send (plurk id strings⋯) -gt

reply (plurk id strings⋯) -gt

run -gt

看起來有點東西接著來處理主要的 Plurk API結合部分

class PlurkStreaming extends EventEmitter

consuctor (options) -gt

plurk (callback) -gt

觀察河道

getChannel -gt

取得 Comet 網址

reply (plurk id message) -gt

回噗

acceptFriends -gt

接受好友

get (path callback) -gt

GET 請求

post (path body callback)-gt

POST 請求(其實是裝飾)

request (method path body callback)-gt

主要的 OAuth 請求

comet (server callback)-gt

噗浪的 Comet 傳回是 JavaScript Callback 要另外處理後才會變成 JSON

然後把注意力集中到 constructor上先把建構子弄好

constructor (options) -gt

super()

if optionskey and optionssecret and optionstoken and optionstoken secret

key = optionskey

secret = optionssecret

token = optionstoken

token secret = optionstoken secret

83 建立 Robot跟 API 73

Nodejs中文電子書

建立 OAuth 連接

consumer = new oauthOAuth(

rdquohttpwwwplurkcomOAuthrequest tokenrdquo

rdquohttpwwwplurkcomOAuthaccess tokenrdquo

key

secret

rdquo10rdquo

rdquohttpwwwplurkcomOAuthauthorizerdquo

rdquoHMAC-SHA1rdquo

)

domain = rdquowwwplurkcomrdquo

初始化取得 Comet 網址

do getChannel

else

throw new Error(rdquo 參數不足需要 Key Secret Token Token Secretrdquo)

接著來處理 request這個 method

request (method path body callback) -gt

記錄一下這次的 Request

consolelog(rdquohttpdomainpathrdquo)

Callback 這邊先不丟進去要用另一種方式處理

request = consumerget(rdquohttpdomainpathrdquo token token secret null)

requeston rdquoresponserdquo (res) -gt

reson rdquodatardquo (chunk) -gt

parseResponse(chunk+rsquorsquo callback)

reson rdquoendrdquo (data) -gt

consolelog rdquoEnd Request pathrdquo

reson rdquoerrorrdquo (data) -gt

consolelog rdquoError rdquo + data

requestend()

處理資料

parseResponse = (data callback) -gt

if datalength gt 0

用 TryCatch 避免處理 JSON 出錯導致整個中斷

try

callback null JSONparse(data)

catch err

74 8 製作一個 Hubot的 Plurk Adapter

Nodejs中文電子書

consolelog(rdquoError Parse JSONrdquo + data err)

繼續執行

callback null data ||

大致上就是這樣上面程式的架構已經將整個 Hubot Plurk Adapter完成因為在測試時竟然因為噗浪 Lag而沒讀到完整的 Comet資料然後造成程式異常為了避免這個問題發生因此需要加上為了完美呈現需要再

加上 Comet的處理所以要使用到 EventEmitter的功能

comet (server callback) -gt

在 Callback 裡面會找不到自身所以設定區域變數

self =

記錄一下這次的 Request

consolelog(rdquo[Comet] serverrdquo)

Callback 這邊先不丟進去要用另一種方式處理

request = consumerget(rdquohttpdomainpathrdquo token token secret null)

requeston rdquoresponserdquo (res) -gt

reson rdquodatardquo (chunk) -gt

parseResponse(chunk+rsquorsquo callback)

reson rdquoendrdquo (data) -gt

consolelog rdquoEnd Request pathrdquo

請求結束發出事件通知可以進行下一次請求

selfemit rdquonextPlurkrdquo

reson rdquoerrorrdquo (data) -gt

consolelog rdquoError rdquo + data

requestend()

處理資料

parseResponse = (data callback) -gt

if datalength gt 0

用 trycatch 避免失敗中斷

try

去掉 JavaScript 的 Callback

data = datamatch(CometChannelscriptCallback((+))s)

jsonData = rdquordquo

if data

83 建立 Robot跟 API 75

Nodejs中文電子書

jsonData = JSONparse(data[1])

else

如果沒有任何 Match 嘗試直接 parse

jsonData = JSONparse(data)

catch err

consolelog(rdquo[Comet] Errorrdquo data err)

用 TryCatch 避免處理 JSON 出錯導致整個中斷

try

只傳入 json 的 data 部分

callback null jsonDatadata

catch err

consolelog(rdquo[Comet]Error Parse JSONrdquo + data err)

繼續執行

callback null data ||

後面的 get跟 post就簡單多了

get (path callback) -gt

request(rdquoGETrdquo path null callback)

post (path body callback) -gt

request(rdquoPOSTrdquo path body callback)

接著處理取的 Comet網址的 getChannel

getChannel -gt

self =

get rdquoAPPRealtimegetUserChannelrdquo (error data) -gt

if error

檢查是否有 comet server

if datacomet server

selfchannel = datacomet server

如果沒有 Channel Ready 就嘗試連接會失敗

selfemit(rsquochannel readyrsquo)

那麼先來處理 Plurk Adaper好處理的部份

send (plruk id strings⋯)-gt

跟 Reply 一樣直接交給 reply 做

reply plurk id strings⋯

76 8 製作一個 Hubot的 Plurk Adapter

Nodejs中文電子書

reply (plurk id strings⋯) -gt

stringsforEach (message) =gt

botreply(plruk id message)

接著把 run處理好就可以上線運作摟

run -gt

self =

options =

key processenvHUBOT PLURK KEY

secret processenvHUBOT PLURK SECRET

token processenvHUBOT PLURK TOKEN

token secret processenvHUBOT PLURK TOKEN SECRET

創建剛剛的 API

bot = new PlurkStreaming(options)

依照 Twitter 的 new RobotTextMessage 會沒有反應所以參考 hubot-minecraft 的方式

r = robotconstructor

處理噗浪河道訊息

doPlurk = (data)-gt

檢查是否為回噗

if dataresponse

datacontent raw = dataresponsecontent raw

datauser id = dataresponseuser id

確定有噗浪 ID 跟訊息

if dataplurk id and datacontent raw

selfreceive new rTextMessage(dataplurk id datacontent raw)

取得 Comet Server 完成開始第一次 Comet 連接

boton rdquochannel readyrdquo () -gt

botplurk selfdoPlurk

上一次 Comet 完成繼續 Polling

boton rdquonextPlurkrdquo ()-gt

botplurk selfdoPlurk

定時接受好友邀請

do botacceptFriends

bot = bot

83 建立 Robot跟 API 77

Nodejs中文電子書

終於完成 AdapterHubot專案裡面的 scripts資料夾內是互動部分不需要像 Adapter如此大費周章處理只新增檔案並且設計好對白之後就會回噗了我開發用的機器人在此大家可以去跟他玩玩[httpplurkcomelct9620 bot](httpplurkcomelct9620 bot)

84 原始資料提供

bull [製 作 一 個 Hubot 的 噗 浪 Adapter](http revo-skillfrosttw blog20120318create-a-hubot-plurk-adapter)

78 8 製作一個 Hubot的 Plurk Adapter

9

Nodejs好用工具介紹

91 logio

Real-time log monitoring in your browser

httplogioorg

nodejs (gt=0412 lt=0611)

npm config set unsafe-perm true

npm install -g --prefix=usrlocal logio

logio server start

httplocalhost8998

79

Nodejs中文電子書

80 9 Nodejs好用工具介紹

10

精選文章

101 Express

bull 使用 NodeJS + Express從 GETPOST Request取值 [邀稿中]

bull link2 [邀稿中]

bull link3 [邀稿中]

81

Nodejs中文電子書

82 10 精選文章

11

參考資源

111 Nodejs書籍

bull Programming Nodejs April 2012 (est) Travis Swicegood Pragmatic Bookshelf

bull e Little Book on CoffeeScript January 2012 Alex MacCaw OrsquoReilly Media

bull Node for Front-End Developers January 2012 Garann Means OrsquoReilly Media

bull Building Hypermedia APIs with HTML5 and Node November 2011 MikeAmundsen OrsquoReilly Media

bull Node Web Development August 2011 David Herron PacktPub

bull CoffeeScript Accelerated JavaScript Development July 2011 Trevor BurnhamPragmatic Bookshelf

bull Getting Started with GEO CouchDB and Nodejs July 2011 Mick omp-son OrsquoReilly Media

bull What Is Node July 2011 Brett McLaughlin OrsquoReilly Media

bull Node Up and Running May 2011 Tom Hughes-Croucher Mike WilsonOrsquoReilly Media

83

Nodejs中文電子書

bull Hands-on Nodejs e Nodejs introduction and API reference May 2011 Pe-dro Teixeira LeanPub

bull e Node Beginner Book Manuel Kiessling

bull Mastering Nodejs visionmedia

112 Nodejs影音教學

bull e Node Sessions e Best of OSCON 2011 August 2011 OrsquoReilly Media(Video)

bull Tom Hughes-Croucher on Node March 2011 OrsquoReilly Media (Video)

113 Nodejs教學網站

bull Node Tuts

114 Nodejs課程

bull Introduction To Nodejs Van Nguyen CodeLesson

84 11 參考資源

Page 56: Nodejs Wiki

Nodejs中文電子書

packagejson(範例)

rdquonamerdquo rdquoapplication-namerdquo

rdquoversionrdquo rdquo001rdquo

rdquoprivaterdquo true

rdquodependenciesrdquo

rdquoexpressrdquo rdquo255rdquo

rdquocoffee-scriptrdquo rdquolatestrdquo

rdquomongooserdquo rdquogt= 253rdquo

其中 name與 version依照專案的需求設置

需要注意的是 dependencies的設定它用於指定專案相依的套件名

稱及版本

bull rdquoexpressrdquo rdquo255rdquo

代表此專案相依版本 255的 express套件

bull rdquocoffee-scriptrdquo rdquolatestrdquo

使用最新版的 coffee-script套件(每次更新都會檢查新版)

bull rdquomongooserdquo rdquogt= 253rdquo

使用版本大於 253的 mongoose套件

假設某個套件的新版可能造成專案無法正常運作就必須指定套件的

版本避免專案的程式碼來不及更新以相容新版套件通常在開發初期的

專案需要盡可能維持新套件的相容性(以取得套件的更新或修正)可以

用「gt=」設定最低相容的版本或是使用「latest」設定永遠保持最新

套件

50 5 NPM套件管理工具

6

Express介紹

在前面的 nodejs基礎當中介紹許多許多開設 http的使用方法及介紹以及許多基本的 nodejs基本應用

接下來要介紹一個套件稱為 express [Express](httpexpressjscom)這個套件主要幫忙解決許多 nodejs http server所需要的基本服務讓開發 httpservice變得更為容易不需要像之前需要透過層層模組(module)才有辦法開始編寫自己的程式

這個套件是由 TJ Holowaychuk 製作而成的套件裡面包含基本的路由處理 (route)http資料處理(GETPOSTPUT)另外還與樣板套件(jshtml template engine)搭配同時也可以處理許多複雜化的問題

61 Express安裝

安裝方式十分簡單只要透過之前介紹的 NPM就可以使用簡單的指令安裝指令如下

這邊建議需要將此套件安裝成為全域模組方便日後使用

51

Nodejs中文電子書

62 Express基本操作

express的使用也十分簡單先來建立一個基本的 hello world

var app = require(rsquoexpressrsquo)createServer()

port = 1337

applisten(port)

appget(rsquorsquo function(req res)

ressend(rsquohello worldrsquo)

)

consolelog(rsquostart express servernrsquo)

可以從上面的程式碼發現基本操作與 nodejs http的建立方式沒有太大差異主要差在當我們設定路由時可以直接透過 appget方式設定回應與接受方式

63 Express路由處理

Express對於 http服務上有許多包裝讓開發者使用及設定上更為方便例如有幾個路由設定那我們就統一藉由 appget來處理

Create http server

appget(rsquorsquo function(req res)

ressend(rsquohello worldrsquo)

)

appget(rsquotestrsquo function(req res)

ressend(rsquotest renderrsquo)

)

appget(rsquouserrsquo function(req res)

ressend(rsquouser pagersquo)

)

52 6 Express介紹

Nodejs中文電子書

如上面的程式碼所表示appget可以帶入兩個參數第一個是路徑名稱設定第二個為回應函式 (call back function)回應函式裡面就如同之前的 createServer方法裡面包含 requestresponse兩個物件可供使用使用者就可以透過瀏覽器輸入不同的 url切換到不同的頁面顯示不同的結果

路由設定上也有基本的配對方式讓使用者從瀏覽器輸入的網址可以

是一個變數只要符合型態就可以有對應的頁面產出例如

Create http server

appget(rsquouseridrsquo function(req res)

ressend(rsquouser rsquo + reqparamsid)

)

appget(rsquonumberrsquo function(req res)

ressend(rsquonumber rsquo + reqparamsnumber)

)

裡 面 使 用 到number 從 網 址 輸 入 之 後 就 可 以 直 接 使 用reqparamsnumber 取得所輸入的資料變成 url 參數使用當然前面也是可以加上路徑的設定userid在瀏覽器上路徑必須符合userxxx透

過 reqparamsid就可以取到 xxx這個字串值

另外express參數處理也提供了路由參數配對處理也可以透過正規表示法作為參數設定

var app = require(rsquoexpressrsquo)createServer()

port = 1337

applisten(port)

appget(^ip((d23)((d23))((d23))((d23))) function(req res)

ressend(reqparams)

)

上面程式碼可以發現後面路由設定的型態是正規表示法裡面設定

格式為ip之後必須要加上 ip型態才會符合資料格式同時取得 ip資料已經由正規表示法將資料做分群因此可以取得 ip的四個數字

此程式執行之後可以透過瀏覽器測試輸入網址為 localhost3000ip25525510010可以從頁面獲得資料

63 Express路由處理 53

Nodejs中文電子書

此章節全部範例程式碼如下

overview

author Caesar Chi

blog clonnblogspotcom

version 20120226

create server

var app = require(rsquoexpressrsquo)createServer()

port = 1337

applisten(port)

normal style

appget(rsquorsquo function(req res)

ressend(rsquohello worldrsquo)

)

appget(rsquotestrsquo function(req res)

ressend(rsquotest renderrsquo)

)

parameter style

appget(rsquouseridrsquo function(req res)

ressend(rsquouser rsquo + reqparamsid)

)

appget(rsquonumberrsquo function(req res)

ressend(rsquonumber rsquo + reqparamsnumber)

)

REGX style

appget(^ip((d23)((d23))((d23))) function(req res)

ressend(reqparams)

)

appget(rsquorsquo function(req res)

ressend(rsquoPage not foundrsquo 404)

54 6 Express介紹

Nodejs中文電子書

)

consolelog(rsquostart express servernrsquo)

64 Express middleware

Express 裡面有一個十分好用的應用概念稱為 middleware可以透過middleware做出複雜的效果同時上面也有介紹 next方法參數傳遞就是靠 middleware的概念來傳遞參數讓開發者可以明確的控制程式邏輯

create http server

appuse(expressbodyParser())

appuse(expressmethodOverride())

appuse(expresssession())

上面都是一種 middleware的使用方式透過 appuse方式裡面載入函式執行方法回應函式會包含三個基本參數responserequestnext其中next表示下一個 middleware執行函式同時會自動將預設三個參數繼續帶往下個函式執行底下有個實驗

上面的片段程式執行後開啟瀏覽器連結上 localhost1337會發現伺服器回應結果順序如下

first middle ware

second middle ware

execute middle ware

end middleware function

從上面的結果可以得知剛才設定的 middleware都生效了在 appuse設定的 middleware是所有 url皆會執行方法如果有指定特定方法就可以使用 appget的 middleware設定在 appget函式的第二個參數就可以帶入函式或者是匿名函式只要函式裡面最後會接受 request response next這三個參數同時也有正確指定 next函式的執行時機最後都會執行到最後一個方法當然開發者也可以評估程式邏輯要執行到哪一個階段讓邏輯

可以更為分明

64 Express middleware 55

Nodejs中文電子書

65 Express路由應用

在實際開發上可能會遇到需要使用參數等方式混和變數一起使用

express裡面提供了一個很棒的處理方法 appall這個方式可以先採用基本路由配對再將設定為每個不同的處理方式開發者可以透過這個方式簡

化自己的程式邏輯

overview

author

version 20120226

create server

var app = require(rsquoexpressrsquo)createServer()

port = 1337

users = [

name rsquoClonnrsquo

name rsquoChirsquo

]

applisten(port)

appall(rsquouseridoprsquo function(req res next)

requser = users[reqparamsid]

if (requser)

next()

else

next(new Error(rsquocannot find user rsquo + reqparamsid))

)

appget(rsquouseridrsquo function(req res)

ressend(rsquoviewing rsquo + requsername)

)

appget(rsquouserideditrsquo function(req res)

ressend(rsquoediting rsquo + requsername)

)

56 6 Express介紹

Nodejs中文電子書

appget(rsquouseriddeletersquo function(req res)

ressend(rsquodeleting rsquo + requsername)

)

appget(rsquorsquo function(req res)

ressend(rsquoPage not foundrsquo 404)

)

consolelog(rsquostart express servernrsquo)

內部宣告一組預設的使用者分別給予名稱設定藉由 appall這個方法可以先將路由雛形建立再接下來設定 appget的路徑格式只要符合格式就會分配進入對應的方法中像上面的程式當中如果使用者輸入路徑為user0除了執行 appall程式之後執行 next方法就會對應到路徑設定為userid的這個方法當中如果使用者輸入路徑為user0edit就會執行到useridedit的對應方法

66 Express GET應用範例

我們準備一個使用 GET方法傳送資料的表單

ltDOCTYPE htmlgt

lthtmlgt

ltheadgt

ltmeta http-equiv=rdquoContent-Typerdquo content=rdquotexthtml charset=utf8rdquogt

lttitlegtNodejs 菜鳥筆記 (1)lttitlegt

ltlink rel=rdquostylesheetrdquo href=rdquocssstylecssrdquo type=rdquotextcssrdquo media=rdquoallrdquo gt

ltheadgt

ltbodygt

lth1gtNodejs 菜鳥筆記-註冊lth1gt

ltform id=rdquosignuprdquo method=rdquoGETrdquo action=rdquohttplocalhost3000Signuprdquogt

ltlabelgt 使用者名稱ltlabelgtltinput type=rdquotextrdquo id=rdquousernamerdquo name=rdquousernamerdquo gtltbrgt

ltlabelgt 電子郵件ltlabelgtltinput type=rdquotextrdquo id=rdquoemailrdquo name=rdquoemailrdquo gtltbrgt

ltinput type=rdquosubmitrdquo value=rdquo 註冊我的帳號rdquo gtltbrgt

ltformgt

ltbodygt

lthtmlgt

66 Express GET應用範例 57

Nodejs中文電子書

這個表單沒有什麼特別的地方我們只需要看第 9 行form 使用的method是 GET然後 action是rdquohttplocalhost3000Signupldquo等一下我們要來撰寫Signup這個 URL Path的處理程式

處理 Signup行為

我們知道所謂的 GET方法會透過 URL來把表單的值給帶過去以上面的表單來說到時候 URL會以這樣的形式傳遞

httplocalhost3000Signupusername=xxxampemail=xxx

所以要能處理這樣的資料必須有以下功能

bull 解析 URL

bull 辨別動作是 Signup

bull 解析出 username和 email

一旦能取得 username和 email的值程式就能加以應用了

處理 Signup的程式碼雛形

load module

var url = require(rsquourlrsquo)

urlData = urlparse(requrltrue)

action = urlDatapathname

reswriteHead(200 rdquoContent-Typerdquordquotexthtml charset=utf-8rdquo)

if (action === rdquoSignuprdquo)

user = urlDataquery

resend(rdquolth1gtrdquo + userusername + rdquo 歡迎您的加入 lth1gtltpgt 我們已經將會員啟用信寄至rdquo + useremail + rdquoltpgtrdquo)

首先需要加載 url module它是用來協助我們解析 URL的模組接著使用 urlparse方法第一個傳入 url字串變數也就是 requrl另外第二個參數的用意是設為 ture則引進 querystring模組來協助處理預設是 false它影響到的是 urlDataquery設為 true會傳回物件不然就只是一般的字串urlparse會將字串內容整理成一個物件我們把它指定給 urlData

action變數作為記錄 pathname這是我們稍後要來判斷目前網頁的動作是什麼接著先將 html表頭資訊 (Header)準備好再來判斷路徑邏輯如

58 6 Express介紹

Nodejs中文電子書

果是 Signup這個動作就把 urlDataquery裡的資料指定給 user然後輸出userusername和 useremail把使用者從表單註冊的資料顯示於頁面中

最後進行程式測試啟動 nodejs主程式之後開啟瀏覽器就會看到表單填寫完畢按下送出就可以看到結果了

完整 nodejs程式碼如下

var http = require(rsquohttprsquo)

url = require(rsquourlrsquo)

fs = require(rdquofsrdquo)

server

server = httpcreateServer(function (reqres)

var urlData

encode = rdquoutf8rdquo

filePath = rdquoviewexpress_get_example_formhtmlrdquo

action

urlData = urlparse(requrltrue)

action = urlDatapathname

reswriteHead(200 rdquoContent-Typerdquordquotexthtml charset=utf-8rdquo)

if (action === rdquoSignuprdquo)

user = urlDataquery

resend(rdquolth1gtrdquo + userusername + rdquo 歡迎您的加入 lth1gtltpgt 我們已經將會員啟用信寄至rdquo + useremail + rdquoltpgtrdquo)

else

fsreadFile(filePath encode function(err file)

reswrite(file)

resend()

)

)

serverlisten(3000)

consolelog(rsquoServer 跑起來了現在時間是rsquo + new Date())

66 Express GET應用範例 59

Nodejs中文電子書

67 Express POST應用範例

一開始準備基本的 html表單傳送內容以 POST方式form的 action屬性設定為 POST其餘 html內容與前一個範例應用相同

ltDOCTYPE htmlgt

lthtmlgt

ltheadgt

ltmeta http-equiv=rdquoContent-Typerdquo content=rdquotexthtml charset=utf8rdquogt

lttitlegtNodejs 菜鳥筆記 (1)lttitlegt

ltlink rel=rdquostylesheetrdquo href=rdquocssstylecssrdquo type=rdquotextcssrdquo media=rdquoallrdquo gt

ltheadgt

ltbodygt

lth1gtNodejs 菜鳥筆記-註冊lth1gt

ltform id=rdquosignuprdquo method=rdquoPOSTrdquo action=rdquohttplocalhost3000Signuprdquogt

ltlabelgt 使用者名稱ltlabelgtltinput type=rdquotextrdquo id=rdquousernamerdquo name=rdquousernamerdquo gtltbrgt

ltlabelgt 電子郵件ltlabelgtltinput type=rdquotextrdquo id=rdquoemailrdquo name=rdquoemailrdquo gtltbrgt

ltinput type=rdquosubmitrdquo value=rdquo 註冊我的帳號rdquo gtltbrgt

ltformgt

ltbodygt

lthtmlgt

nodejs的程式處理邏輯與前面 GET範例類似部分程式碼如下

qs = require(rsquoquerystringrsquo)

if (action === rdquoSignuprdquo)

formData = rsquorsquo

reqon(rdquodatardquo function (data)

formData += data

)

reqon(rdquoendrdquo function ()

user = qsparse(formData)

resend(rdquolth1gtrdquo + userusername + rdquo 歡迎您的加入 lth1gtltpgt 我們已經將會員啟用信寄至rdquo + useremail + rdquoltpgtrdquo)

)

60 6 Express介紹

Nodejs中文電子書

主要加入了rsquoquerystringrsquo這個moduel方便我們等一下解析由表單 POST回來的資料另外加入一個 formData的變數用來搜集待等一下表單回傳的資料前面的 GET範例我們只從 req拿出 url的資料這次要在利用req身上的事件處理

JavaScript 在訂閱事件時使用 addEventListener而 nodejs 使用的則是on這邊加上了監聽 data的事件會在瀏覽器傳送資料到Web Server時被執行參數是它所接收到的資料型態是字串

接著再增加 end的事件當瀏覽器的請求事件結束時它就會動作

由於瀏覽器使用 POST在上傳資料時會將資料一塊塊地上傳因為我們在監聽 data事件時透過 formData變數將它累加起來lt不過由於我們

上傳的資料很少一次就結束不過如果日後需要傳的是資料比較大的檔

案這個累加動作就很重要

當資料傳完就進到 end 事件中會用到 qsparse 來解析 formDataformData的內容是字串內容是

username=wordsmithampemail=wordsmith40somewhere

而 qsparse可以幫我們把這個 querystring轉成物件的格式也就是

username=wordsmithampemail=wordsmith40somewhere

一旦轉成物件並指定給 user之後其他的事情就和 GET方法時操作的一樣寫 response的表頭將內容回傳並將 userusername和 useremail代入到內容中

修改完成後接著執行 nodejs程式啟動 web server開啟瀏覽器進入表單測試看看POST的方式能否順利運作

完整程式碼如下

var http = require(rsquohttprsquo)

url = require(rsquourlrsquo)

fs = require(rdquofsrdquo)

qs = require(rsquoquerystringrsquo)

server

server = httpcreateServer(function (reqres)

var urlData

67 Express POST應用範例 61

Nodejs中文電子書

encode = rdquoutf8rdquo

filePath = rdquoviewexpress_post_example_formhtmlrdquo

formData

action

urlData = urlparse(requrltrue)

action = urlDatapathname

reswriteHead(200 rdquoContent-Typerdquordquotexthtml charset=utf-8rdquo)

if (action === rdquoSignuprdquo)

formData = rsquorsquo

reqon(rdquodatardquo function (data)

formData += data

)

reqon(rdquoendrdquo function ()

user = qsparse(formData)

resend(rdquolth1gtrdquo + userusername + rdquo 歡迎您的加入 lth1gtltpgt 我們已經將會員啟用信寄至rdquo + useremail + rdquoltpgtrdquo)

)

else

fsreadFile(filePath encode function(err file)

reswrite(file)

resend()

)

)

serverlisten(3000)

consolelog(rsquoServer 跑起來了現在時間是rsquo + new Date())

62 6 Express介紹

Nodejs中文電子書

68 Express AJAX應用範例

在 Nodejs要使用 Ajax傳送資料並且與之互動在接受資料的部份沒有太大的差別client端不是用 GET就是用 POST來傳資料重點在處理完後用 JSON格式回傳當然 Ajax不見得只傳 JSON格式有時是回傳一段 HTML碼不過後者對伺服器來說基本上就和前兩篇沒有差別了所以我們還是以回傳 JSON做為這一回的主題

這一回其實大多數的工作都會落在前端 Ajax上面前端要負責發送與接收資料並在接收資料後撤掉原先發送資料的表單並將取得的資料

改成 HTML格式之後放上頁面

首先先準備 HTML靜態頁面資料

ltDOCTYPE htmlgt

lthtmlgt

ltheadgt

lttitlegtNodejs 菜鳥筆記 (3)lttitlegt

ltlink rel=rdquostylesheetrdquo href=rdquocssstylecssrdquo type=rdquotextcssrdquo media=rdquoallrdquo gt

ltscript src=rdquohttpsajaxgoogleapiscomajaxlibsjquery171jqueryminjsrdquogtltscriptgt

ltheadgt

ltbodygt

lth1gtNodejs 菜鳥筆記-註冊 (用 Ajax)lth1gt

ltform id=rdquosignuprdquo action=rdquoSignuprdquo method=rdquoPOSTrdquogt

ltlabelgt 使用者名稱ltlabelgtltinput type=rdquotextrdquo id=rdquousernamerdquo name=rdquousernamerdquo gtltbrgt

ltlabelgt 電子郵件ltlabelgtltinput type=rdquotextrdquo id=rdquoemailrdquo name=rdquoemailrdquo gtltbrgt

ltinput type=rdquosubmitrdquo value=rdquo 註冊我的帳號rdquo gtltbrgt

ltformgt

ltdiv id=rdquoresultrdquogt

ltdivgt

ltscript type=rdquotextjavascriptrdquogt

$(function()$(rsquosignup input[type=rdquosubmitrdquo]rsquo)click(function(e)

var user =

userusername = $(rdquousernamerdquo)val()useremail = $(rdquoemailrdquo)val()

$post(rdquoSignuprdquouserfunction(data)greet(data)

)

68 Express AJAX應用範例 63

Nodejs中文電子書

epreventDefault()

)

var greet = function(msg)

var resultNode = $(rsquoresultrsquo)greeting = $(rsquolth2gtHirsquo+msgusername+rsquo 你的會員 id 是rsquo+ msgid +rsquo 我們會將會員啟動信寄至rsquo+msgemail+rsquolth2gtrsquo)

$(rsquosignuprsquo)hide()resultNodehtml(rdquordquo)

resultNodeappend(greeting)

)

ltscriptgt

ltbodygt

lthtmlgt

HTML頁面上準備了一個表單用來傳送註冊資料接著直接引用了Google CDN來載入 jQuery用來幫我們處理 Ajax的工作這次要傳送和接收的工作很大的變動都在 HTML頁面上的 JavaScript當中我們要做的事有 (相關 jQuery處理這邊不多做贅述指提起主要功能解說)

bull 用 jQUery取得 submit按鈕綁定它的 click動作

bull 取得表單 username和 email的值存放在 user這個物件中

bull 用 jQuery的 $post方法將 user的資料傳到 Server

bull 一旦成功取得資料後透過 greet這個 function組成回報給 user的訊息

bull 清空原本給使用者填資料的表單

bull 將 Server回傳的 usernameemail和 id這 3個資料組成回應的訊息

bull 將訊息放到原本表單的位置

經過以上的處理後一個 Ajax的表單的基本功能已經完成

接著進行 nodejs主要程式的編輯部分程式碼如下

var fs = require(rdquofsrdquo)

64 6 Express介紹

Nodejs中文電子書

qs = require(rsquoquerystringrsquo)

if (action === rdquoSignuprdquo)

formData = rsquorsquo

reqon(rdquodatardquo function (data)

formData += data

)

reqon(rdquoendrdquo function ()

var msg

user = qsparse(formData)

userid = rdquo123456rdquo

msg = JSONstringify(user)

reswriteHead(200 rdquoContent-Typerdquordquoapplicationjson charset=utf-8rdquordquoContent-Lengthrdquomsglength)

resend(msg)

)

這裡的程式和前面 POST範例基本上大同小異差別在

bull 幫 user的資料加上 id隨意存放一些文字進去讓 Server回傳的資料多於 Client端傳上來的不然會覺得 Server都沒做事

bull 增加了 msg 這個變數存放將 user 物件 JSON 文字化的結果JSONstringify這個轉換函式是 V8引擎所提供的如果你好奇的話

bull 大重點來了我們要告訴 Client端這次回傳的資料格式是 JSON所在 Content-type和 Content-Length要提供給 Client

Server很輕鬆就完成任務了最後進行程式測試啟動 nodejs主程式之後開啟瀏覽器就會看到表單填寫完畢按下送出就可以看到結果了

最後 nodejs本篇範例程式碼如下

var http = require(rsquohttprsquo)

url = require(rsquourlrsquo)

fs = require(rdquofsrdquo)

qs = require(rsquoquerystringrsquo)

server

68 Express AJAX應用範例 65

Nodejs中文電子書

server = httpcreateServer(function (reqres)

var urlData

encode = rdquoutf8rdquo

filePath = rdquoviewexpress_ajax_example_formhtmlrdquo

formData

action

urlData = urlparse(requrltrue)

action = urlDatapathname

if (action === rdquoSignuprdquo)

formData = rsquorsquo

reqon(rdquodatardquo function (data)

formData += data

)

reqon(rdquoendrdquo function ()

var msg

user = qsparse(formData)

userid = rdquo123456rdquo

msg = JSONstringify(user)

reswriteHead(200 rdquoContent-TyperdquordquoapplicationjsonrdquordquoContent-Lengthrdquomsglength)

resend(msg)

)

else

fsreadFile(filePath encode function(err file)

reswriteHead(200 rdquoContent-Typerdquordquotexthtml charset=utf-8rdquo)

reswrite(file)

resend()

)

)

serverlisten(3000)

66 6 Express介紹

Nodejs中文電子書

consolelog(rsquoServer 跑起來了現在時間是rsquo + new Date())

69 原始資料提供

bull [NodeJS初學者筆記 (1)-用 GET傳送資料] (httpithelpithomecomtwquestion10087402)

bull [NodeJS初學者筆記 (2)-用 POST傳送資料] (httpithelpithomecomtwquestion10087489)

bull [NodeJS 初學者筆記 (3)-用 Ajax 傳送資料] (httpithelpithomecomtwquestion10087627)

69 原始資料提供 67

Nodejs中文電子書

68 6 Express介紹

7

CoffeeScript

bull Spine

bull Hem

bull Spineapp

Letrsquos Have a Cup of CoffeeScript

bull es5-shim ECMAScript 5 compatibility shims for legacy JavaScript engines

ESnext

node-iform - A middleware for node-validator httpsgithubcomguileennode-iform

69

Nodejs中文電子書

70 7 CoffeeScript

8

製作一個 Hubot的 Plurk Adapter

81 應用事項提醒

此應用範例以CoffeeScript編寫主要程式架構採用Github釋放的Hubot專案以下有幾個主要注意事項請讀者注意

bull Hubot 一開始的架構除了內建的兩個 Adapter 之外其餘都要以Nodejs的Module方式才能運作

bull Hubot的 binhubot裡面寫著 npm install所以不管你怎麼改原始碼也不能改變第一點的狀況

其實上述都在討論同一件事情NodeJS的模組而且模組不能設定相對路徑之類的來安裝一定要包裝成 npm相符和格式才有辦法正確執行

82 建立 Adapter

首先需要準備兩個檔案

bull packagejson

bull plurkcoffee

71

Nodejs中文電子書

有這兩個就足以變成 NodeJS的模組了在開始 Coding之前先來設定一下 packagejson相依性

rdquonamerdquo rdquohubot-plurkrdquo

rdquoversionrdquo rdquo011rdquo

rdquomainrdquo rdquoplurkrdquo

rdquodependenciesrdquo

rdquohubotrdquo rdquogt=205rdquo

rdquooauthrdquo rdquordquo

rdquocronrdquordquordquo

這邊會使用到NodeJS的 cron模組(Module)而登入 Plurk需要OAuth標準授權才行所以也需要加載 OAuth模組

注意Hubot在讀取非內建模組時會自動在前面加上 hubot-的前置

83 建立 Robot跟 API

本篇應用基本上程式碼大部分參考 Hubot - Twitter Adapter來製作主要差異只有在使用 OAuth這個模組Twitter有 Streaming可用而 Plurk則得用 Comet方式來達到即時讀取

plurkcoffee程式碼

Robot = require(rdquohubotrdquo)robot()

Adapter = require(rdquohubtordquo)adapter()

EventEmitter = require(rdquoeventsrdquo)EventEmitter

oauth = require(rdquooauthrdquo)

cronJob = require(rdquocronrdquo)CronJob

class Plurk exntends Adapter

class PlurkStreaming exnteds EventEmitter

72 8 製作一個 Hubot的 Plurk Adapter

Nodejs中文電子書

先弄個基本架構主要 Class皆參考 Twitter Adapter命名方式接著再將 Plurk這個 Class增加幾個Method基本上只要有 run send reply就夠了而 run用來做初始化的部分

class Plurk entends Adapter

send (plurk id strings⋯) -gt

reply (plurk id strings⋯) -gt

run -gt

看起來有點東西接著來處理主要的 Plurk API結合部分

class PlurkStreaming extends EventEmitter

consuctor (options) -gt

plurk (callback) -gt

觀察河道

getChannel -gt

取得 Comet 網址

reply (plurk id message) -gt

回噗

acceptFriends -gt

接受好友

get (path callback) -gt

GET 請求

post (path body callback)-gt

POST 請求(其實是裝飾)

request (method path body callback)-gt

主要的 OAuth 請求

comet (server callback)-gt

噗浪的 Comet 傳回是 JavaScript Callback 要另外處理後才會變成 JSON

然後把注意力集中到 constructor上先把建構子弄好

constructor (options) -gt

super()

if optionskey and optionssecret and optionstoken and optionstoken secret

key = optionskey

secret = optionssecret

token = optionstoken

token secret = optionstoken secret

83 建立 Robot跟 API 73

Nodejs中文電子書

建立 OAuth 連接

consumer = new oauthOAuth(

rdquohttpwwwplurkcomOAuthrequest tokenrdquo

rdquohttpwwwplurkcomOAuthaccess tokenrdquo

key

secret

rdquo10rdquo

rdquohttpwwwplurkcomOAuthauthorizerdquo

rdquoHMAC-SHA1rdquo

)

domain = rdquowwwplurkcomrdquo

初始化取得 Comet 網址

do getChannel

else

throw new Error(rdquo 參數不足需要 Key Secret Token Token Secretrdquo)

接著來處理 request這個 method

request (method path body callback) -gt

記錄一下這次的 Request

consolelog(rdquohttpdomainpathrdquo)

Callback 這邊先不丟進去要用另一種方式處理

request = consumerget(rdquohttpdomainpathrdquo token token secret null)

requeston rdquoresponserdquo (res) -gt

reson rdquodatardquo (chunk) -gt

parseResponse(chunk+rsquorsquo callback)

reson rdquoendrdquo (data) -gt

consolelog rdquoEnd Request pathrdquo

reson rdquoerrorrdquo (data) -gt

consolelog rdquoError rdquo + data

requestend()

處理資料

parseResponse = (data callback) -gt

if datalength gt 0

用 TryCatch 避免處理 JSON 出錯導致整個中斷

try

callback null JSONparse(data)

catch err

74 8 製作一個 Hubot的 Plurk Adapter

Nodejs中文電子書

consolelog(rdquoError Parse JSONrdquo + data err)

繼續執行

callback null data ||

大致上就是這樣上面程式的架構已經將整個 Hubot Plurk Adapter完成因為在測試時竟然因為噗浪 Lag而沒讀到完整的 Comet資料然後造成程式異常為了避免這個問題發生因此需要加上為了完美呈現需要再

加上 Comet的處理所以要使用到 EventEmitter的功能

comet (server callback) -gt

在 Callback 裡面會找不到自身所以設定區域變數

self =

記錄一下這次的 Request

consolelog(rdquo[Comet] serverrdquo)

Callback 這邊先不丟進去要用另一種方式處理

request = consumerget(rdquohttpdomainpathrdquo token token secret null)

requeston rdquoresponserdquo (res) -gt

reson rdquodatardquo (chunk) -gt

parseResponse(chunk+rsquorsquo callback)

reson rdquoendrdquo (data) -gt

consolelog rdquoEnd Request pathrdquo

請求結束發出事件通知可以進行下一次請求

selfemit rdquonextPlurkrdquo

reson rdquoerrorrdquo (data) -gt

consolelog rdquoError rdquo + data

requestend()

處理資料

parseResponse = (data callback) -gt

if datalength gt 0

用 trycatch 避免失敗中斷

try

去掉 JavaScript 的 Callback

data = datamatch(CometChannelscriptCallback((+))s)

jsonData = rdquordquo

if data

83 建立 Robot跟 API 75

Nodejs中文電子書

jsonData = JSONparse(data[1])

else

如果沒有任何 Match 嘗試直接 parse

jsonData = JSONparse(data)

catch err

consolelog(rdquo[Comet] Errorrdquo data err)

用 TryCatch 避免處理 JSON 出錯導致整個中斷

try

只傳入 json 的 data 部分

callback null jsonDatadata

catch err

consolelog(rdquo[Comet]Error Parse JSONrdquo + data err)

繼續執行

callback null data ||

後面的 get跟 post就簡單多了

get (path callback) -gt

request(rdquoGETrdquo path null callback)

post (path body callback) -gt

request(rdquoPOSTrdquo path body callback)

接著處理取的 Comet網址的 getChannel

getChannel -gt

self =

get rdquoAPPRealtimegetUserChannelrdquo (error data) -gt

if error

檢查是否有 comet server

if datacomet server

selfchannel = datacomet server

如果沒有 Channel Ready 就嘗試連接會失敗

selfemit(rsquochannel readyrsquo)

那麼先來處理 Plurk Adaper好處理的部份

send (plruk id strings⋯)-gt

跟 Reply 一樣直接交給 reply 做

reply plurk id strings⋯

76 8 製作一個 Hubot的 Plurk Adapter

Nodejs中文電子書

reply (plurk id strings⋯) -gt

stringsforEach (message) =gt

botreply(plruk id message)

接著把 run處理好就可以上線運作摟

run -gt

self =

options =

key processenvHUBOT PLURK KEY

secret processenvHUBOT PLURK SECRET

token processenvHUBOT PLURK TOKEN

token secret processenvHUBOT PLURK TOKEN SECRET

創建剛剛的 API

bot = new PlurkStreaming(options)

依照 Twitter 的 new RobotTextMessage 會沒有反應所以參考 hubot-minecraft 的方式

r = robotconstructor

處理噗浪河道訊息

doPlurk = (data)-gt

檢查是否為回噗

if dataresponse

datacontent raw = dataresponsecontent raw

datauser id = dataresponseuser id

確定有噗浪 ID 跟訊息

if dataplurk id and datacontent raw

selfreceive new rTextMessage(dataplurk id datacontent raw)

取得 Comet Server 完成開始第一次 Comet 連接

boton rdquochannel readyrdquo () -gt

botplurk selfdoPlurk

上一次 Comet 完成繼續 Polling

boton rdquonextPlurkrdquo ()-gt

botplurk selfdoPlurk

定時接受好友邀請

do botacceptFriends

bot = bot

83 建立 Robot跟 API 77

Nodejs中文電子書

終於完成 AdapterHubot專案裡面的 scripts資料夾內是互動部分不需要像 Adapter如此大費周章處理只新增檔案並且設計好對白之後就會回噗了我開發用的機器人在此大家可以去跟他玩玩[httpplurkcomelct9620 bot](httpplurkcomelct9620 bot)

84 原始資料提供

bull [製 作 一 個 Hubot 的 噗 浪 Adapter](http revo-skillfrosttw blog20120318create-a-hubot-plurk-adapter)

78 8 製作一個 Hubot的 Plurk Adapter

9

Nodejs好用工具介紹

91 logio

Real-time log monitoring in your browser

httplogioorg

nodejs (gt=0412 lt=0611)

npm config set unsafe-perm true

npm install -g --prefix=usrlocal logio

logio server start

httplocalhost8998

79

Nodejs中文電子書

80 9 Nodejs好用工具介紹

10

精選文章

101 Express

bull 使用 NodeJS + Express從 GETPOST Request取值 [邀稿中]

bull link2 [邀稿中]

bull link3 [邀稿中]

81

Nodejs中文電子書

82 10 精選文章

11

參考資源

111 Nodejs書籍

bull Programming Nodejs April 2012 (est) Travis Swicegood Pragmatic Bookshelf

bull e Little Book on CoffeeScript January 2012 Alex MacCaw OrsquoReilly Media

bull Node for Front-End Developers January 2012 Garann Means OrsquoReilly Media

bull Building Hypermedia APIs with HTML5 and Node November 2011 MikeAmundsen OrsquoReilly Media

bull Node Web Development August 2011 David Herron PacktPub

bull CoffeeScript Accelerated JavaScript Development July 2011 Trevor BurnhamPragmatic Bookshelf

bull Getting Started with GEO CouchDB and Nodejs July 2011 Mick omp-son OrsquoReilly Media

bull What Is Node July 2011 Brett McLaughlin OrsquoReilly Media

bull Node Up and Running May 2011 Tom Hughes-Croucher Mike WilsonOrsquoReilly Media

83

Nodejs中文電子書

bull Hands-on Nodejs e Nodejs introduction and API reference May 2011 Pe-dro Teixeira LeanPub

bull e Node Beginner Book Manuel Kiessling

bull Mastering Nodejs visionmedia

112 Nodejs影音教學

bull e Node Sessions e Best of OSCON 2011 August 2011 OrsquoReilly Media(Video)

bull Tom Hughes-Croucher on Node March 2011 OrsquoReilly Media (Video)

113 Nodejs教學網站

bull Node Tuts

114 Nodejs課程

bull Introduction To Nodejs Van Nguyen CodeLesson

84 11 參考資源

Page 57: Nodejs Wiki

6

Express介紹

在前面的 nodejs基礎當中介紹許多許多開設 http的使用方法及介紹以及許多基本的 nodejs基本應用

接下來要介紹一個套件稱為 express [Express](httpexpressjscom)這個套件主要幫忙解決許多 nodejs http server所需要的基本服務讓開發 httpservice變得更為容易不需要像之前需要透過層層模組(module)才有辦法開始編寫自己的程式

這個套件是由 TJ Holowaychuk 製作而成的套件裡面包含基本的路由處理 (route)http資料處理(GETPOSTPUT)另外還與樣板套件(jshtml template engine)搭配同時也可以處理許多複雜化的問題

61 Express安裝

安裝方式十分簡單只要透過之前介紹的 NPM就可以使用簡單的指令安裝指令如下

這邊建議需要將此套件安裝成為全域模組方便日後使用

51

Nodejs中文電子書

62 Express基本操作

express的使用也十分簡單先來建立一個基本的 hello world

var app = require(rsquoexpressrsquo)createServer()

port = 1337

applisten(port)

appget(rsquorsquo function(req res)

ressend(rsquohello worldrsquo)

)

consolelog(rsquostart express servernrsquo)

可以從上面的程式碼發現基本操作與 nodejs http的建立方式沒有太大差異主要差在當我們設定路由時可以直接透過 appget方式設定回應與接受方式

63 Express路由處理

Express對於 http服務上有許多包裝讓開發者使用及設定上更為方便例如有幾個路由設定那我們就統一藉由 appget來處理

Create http server

appget(rsquorsquo function(req res)

ressend(rsquohello worldrsquo)

)

appget(rsquotestrsquo function(req res)

ressend(rsquotest renderrsquo)

)

appget(rsquouserrsquo function(req res)

ressend(rsquouser pagersquo)

)

52 6 Express介紹

Nodejs中文電子書

如上面的程式碼所表示appget可以帶入兩個參數第一個是路徑名稱設定第二個為回應函式 (call back function)回應函式裡面就如同之前的 createServer方法裡面包含 requestresponse兩個物件可供使用使用者就可以透過瀏覽器輸入不同的 url切換到不同的頁面顯示不同的結果

路由設定上也有基本的配對方式讓使用者從瀏覽器輸入的網址可以

是一個變數只要符合型態就可以有對應的頁面產出例如

Create http server

appget(rsquouseridrsquo function(req res)

ressend(rsquouser rsquo + reqparamsid)

)

appget(rsquonumberrsquo function(req res)

ressend(rsquonumber rsquo + reqparamsnumber)

)

裡 面 使 用 到number 從 網 址 輸 入 之 後 就 可 以 直 接 使 用reqparamsnumber 取得所輸入的資料變成 url 參數使用當然前面也是可以加上路徑的設定userid在瀏覽器上路徑必須符合userxxx透

過 reqparamsid就可以取到 xxx這個字串值

另外express參數處理也提供了路由參數配對處理也可以透過正規表示法作為參數設定

var app = require(rsquoexpressrsquo)createServer()

port = 1337

applisten(port)

appget(^ip((d23)((d23))((d23))((d23))) function(req res)

ressend(reqparams)

)

上面程式碼可以發現後面路由設定的型態是正規表示法裡面設定

格式為ip之後必須要加上 ip型態才會符合資料格式同時取得 ip資料已經由正規表示法將資料做分群因此可以取得 ip的四個數字

此程式執行之後可以透過瀏覽器測試輸入網址為 localhost3000ip25525510010可以從頁面獲得資料

63 Express路由處理 53

Nodejs中文電子書

此章節全部範例程式碼如下

overview

author Caesar Chi

blog clonnblogspotcom

version 20120226

create server

var app = require(rsquoexpressrsquo)createServer()

port = 1337

applisten(port)

normal style

appget(rsquorsquo function(req res)

ressend(rsquohello worldrsquo)

)

appget(rsquotestrsquo function(req res)

ressend(rsquotest renderrsquo)

)

parameter style

appget(rsquouseridrsquo function(req res)

ressend(rsquouser rsquo + reqparamsid)

)

appget(rsquonumberrsquo function(req res)

ressend(rsquonumber rsquo + reqparamsnumber)

)

REGX style

appget(^ip((d23)((d23))((d23))) function(req res)

ressend(reqparams)

)

appget(rsquorsquo function(req res)

ressend(rsquoPage not foundrsquo 404)

54 6 Express介紹

Nodejs中文電子書

)

consolelog(rsquostart express servernrsquo)

64 Express middleware

Express 裡面有一個十分好用的應用概念稱為 middleware可以透過middleware做出複雜的效果同時上面也有介紹 next方法參數傳遞就是靠 middleware的概念來傳遞參數讓開發者可以明確的控制程式邏輯

create http server

appuse(expressbodyParser())

appuse(expressmethodOverride())

appuse(expresssession())

上面都是一種 middleware的使用方式透過 appuse方式裡面載入函式執行方法回應函式會包含三個基本參數responserequestnext其中next表示下一個 middleware執行函式同時會自動將預設三個參數繼續帶往下個函式執行底下有個實驗

上面的片段程式執行後開啟瀏覽器連結上 localhost1337會發現伺服器回應結果順序如下

first middle ware

second middle ware

execute middle ware

end middleware function

從上面的結果可以得知剛才設定的 middleware都生效了在 appuse設定的 middleware是所有 url皆會執行方法如果有指定特定方法就可以使用 appget的 middleware設定在 appget函式的第二個參數就可以帶入函式或者是匿名函式只要函式裡面最後會接受 request response next這三個參數同時也有正確指定 next函式的執行時機最後都會執行到最後一個方法當然開發者也可以評估程式邏輯要執行到哪一個階段讓邏輯

可以更為分明

64 Express middleware 55

Nodejs中文電子書

65 Express路由應用

在實際開發上可能會遇到需要使用參數等方式混和變數一起使用

express裡面提供了一個很棒的處理方法 appall這個方式可以先採用基本路由配對再將設定為每個不同的處理方式開發者可以透過這個方式簡

化自己的程式邏輯

overview

author

version 20120226

create server

var app = require(rsquoexpressrsquo)createServer()

port = 1337

users = [

name rsquoClonnrsquo

name rsquoChirsquo

]

applisten(port)

appall(rsquouseridoprsquo function(req res next)

requser = users[reqparamsid]

if (requser)

next()

else

next(new Error(rsquocannot find user rsquo + reqparamsid))

)

appget(rsquouseridrsquo function(req res)

ressend(rsquoviewing rsquo + requsername)

)

appget(rsquouserideditrsquo function(req res)

ressend(rsquoediting rsquo + requsername)

)

56 6 Express介紹

Nodejs中文電子書

appget(rsquouseriddeletersquo function(req res)

ressend(rsquodeleting rsquo + requsername)

)

appget(rsquorsquo function(req res)

ressend(rsquoPage not foundrsquo 404)

)

consolelog(rsquostart express servernrsquo)

內部宣告一組預設的使用者分別給予名稱設定藉由 appall這個方法可以先將路由雛形建立再接下來設定 appget的路徑格式只要符合格式就會分配進入對應的方法中像上面的程式當中如果使用者輸入路徑為user0除了執行 appall程式之後執行 next方法就會對應到路徑設定為userid的這個方法當中如果使用者輸入路徑為user0edit就會執行到useridedit的對應方法

66 Express GET應用範例

我們準備一個使用 GET方法傳送資料的表單

ltDOCTYPE htmlgt

lthtmlgt

ltheadgt

ltmeta http-equiv=rdquoContent-Typerdquo content=rdquotexthtml charset=utf8rdquogt

lttitlegtNodejs 菜鳥筆記 (1)lttitlegt

ltlink rel=rdquostylesheetrdquo href=rdquocssstylecssrdquo type=rdquotextcssrdquo media=rdquoallrdquo gt

ltheadgt

ltbodygt

lth1gtNodejs 菜鳥筆記-註冊lth1gt

ltform id=rdquosignuprdquo method=rdquoGETrdquo action=rdquohttplocalhost3000Signuprdquogt

ltlabelgt 使用者名稱ltlabelgtltinput type=rdquotextrdquo id=rdquousernamerdquo name=rdquousernamerdquo gtltbrgt

ltlabelgt 電子郵件ltlabelgtltinput type=rdquotextrdquo id=rdquoemailrdquo name=rdquoemailrdquo gtltbrgt

ltinput type=rdquosubmitrdquo value=rdquo 註冊我的帳號rdquo gtltbrgt

ltformgt

ltbodygt

lthtmlgt

66 Express GET應用範例 57

Nodejs中文電子書

這個表單沒有什麼特別的地方我們只需要看第 9 行form 使用的method是 GET然後 action是rdquohttplocalhost3000Signupldquo等一下我們要來撰寫Signup這個 URL Path的處理程式

處理 Signup行為

我們知道所謂的 GET方法會透過 URL來把表單的值給帶過去以上面的表單來說到時候 URL會以這樣的形式傳遞

httplocalhost3000Signupusername=xxxampemail=xxx

所以要能處理這樣的資料必須有以下功能

bull 解析 URL

bull 辨別動作是 Signup

bull 解析出 username和 email

一旦能取得 username和 email的值程式就能加以應用了

處理 Signup的程式碼雛形

load module

var url = require(rsquourlrsquo)

urlData = urlparse(requrltrue)

action = urlDatapathname

reswriteHead(200 rdquoContent-Typerdquordquotexthtml charset=utf-8rdquo)

if (action === rdquoSignuprdquo)

user = urlDataquery

resend(rdquolth1gtrdquo + userusername + rdquo 歡迎您的加入 lth1gtltpgt 我們已經將會員啟用信寄至rdquo + useremail + rdquoltpgtrdquo)

首先需要加載 url module它是用來協助我們解析 URL的模組接著使用 urlparse方法第一個傳入 url字串變數也就是 requrl另外第二個參數的用意是設為 ture則引進 querystring模組來協助處理預設是 false它影響到的是 urlDataquery設為 true會傳回物件不然就只是一般的字串urlparse會將字串內容整理成一個物件我們把它指定給 urlData

action變數作為記錄 pathname這是我們稍後要來判斷目前網頁的動作是什麼接著先將 html表頭資訊 (Header)準備好再來判斷路徑邏輯如

58 6 Express介紹

Nodejs中文電子書

果是 Signup這個動作就把 urlDataquery裡的資料指定給 user然後輸出userusername和 useremail把使用者從表單註冊的資料顯示於頁面中

最後進行程式測試啟動 nodejs主程式之後開啟瀏覽器就會看到表單填寫完畢按下送出就可以看到結果了

完整 nodejs程式碼如下

var http = require(rsquohttprsquo)

url = require(rsquourlrsquo)

fs = require(rdquofsrdquo)

server

server = httpcreateServer(function (reqres)

var urlData

encode = rdquoutf8rdquo

filePath = rdquoviewexpress_get_example_formhtmlrdquo

action

urlData = urlparse(requrltrue)

action = urlDatapathname

reswriteHead(200 rdquoContent-Typerdquordquotexthtml charset=utf-8rdquo)

if (action === rdquoSignuprdquo)

user = urlDataquery

resend(rdquolth1gtrdquo + userusername + rdquo 歡迎您的加入 lth1gtltpgt 我們已經將會員啟用信寄至rdquo + useremail + rdquoltpgtrdquo)

else

fsreadFile(filePath encode function(err file)

reswrite(file)

resend()

)

)

serverlisten(3000)

consolelog(rsquoServer 跑起來了現在時間是rsquo + new Date())

66 Express GET應用範例 59

Nodejs中文電子書

67 Express POST應用範例

一開始準備基本的 html表單傳送內容以 POST方式form的 action屬性設定為 POST其餘 html內容與前一個範例應用相同

ltDOCTYPE htmlgt

lthtmlgt

ltheadgt

ltmeta http-equiv=rdquoContent-Typerdquo content=rdquotexthtml charset=utf8rdquogt

lttitlegtNodejs 菜鳥筆記 (1)lttitlegt

ltlink rel=rdquostylesheetrdquo href=rdquocssstylecssrdquo type=rdquotextcssrdquo media=rdquoallrdquo gt

ltheadgt

ltbodygt

lth1gtNodejs 菜鳥筆記-註冊lth1gt

ltform id=rdquosignuprdquo method=rdquoPOSTrdquo action=rdquohttplocalhost3000Signuprdquogt

ltlabelgt 使用者名稱ltlabelgtltinput type=rdquotextrdquo id=rdquousernamerdquo name=rdquousernamerdquo gtltbrgt

ltlabelgt 電子郵件ltlabelgtltinput type=rdquotextrdquo id=rdquoemailrdquo name=rdquoemailrdquo gtltbrgt

ltinput type=rdquosubmitrdquo value=rdquo 註冊我的帳號rdquo gtltbrgt

ltformgt

ltbodygt

lthtmlgt

nodejs的程式處理邏輯與前面 GET範例類似部分程式碼如下

qs = require(rsquoquerystringrsquo)

if (action === rdquoSignuprdquo)

formData = rsquorsquo

reqon(rdquodatardquo function (data)

formData += data

)

reqon(rdquoendrdquo function ()

user = qsparse(formData)

resend(rdquolth1gtrdquo + userusername + rdquo 歡迎您的加入 lth1gtltpgt 我們已經將會員啟用信寄至rdquo + useremail + rdquoltpgtrdquo)

)

60 6 Express介紹

Nodejs中文電子書

主要加入了rsquoquerystringrsquo這個moduel方便我們等一下解析由表單 POST回來的資料另外加入一個 formData的變數用來搜集待等一下表單回傳的資料前面的 GET範例我們只從 req拿出 url的資料這次要在利用req身上的事件處理

JavaScript 在訂閱事件時使用 addEventListener而 nodejs 使用的則是on這邊加上了監聽 data的事件會在瀏覽器傳送資料到Web Server時被執行參數是它所接收到的資料型態是字串

接著再增加 end的事件當瀏覽器的請求事件結束時它就會動作

由於瀏覽器使用 POST在上傳資料時會將資料一塊塊地上傳因為我們在監聽 data事件時透過 formData變數將它累加起來lt不過由於我們

上傳的資料很少一次就結束不過如果日後需要傳的是資料比較大的檔

案這個累加動作就很重要

當資料傳完就進到 end 事件中會用到 qsparse 來解析 formDataformData的內容是字串內容是

username=wordsmithampemail=wordsmith40somewhere

而 qsparse可以幫我們把這個 querystring轉成物件的格式也就是

username=wordsmithampemail=wordsmith40somewhere

一旦轉成物件並指定給 user之後其他的事情就和 GET方法時操作的一樣寫 response的表頭將內容回傳並將 userusername和 useremail代入到內容中

修改完成後接著執行 nodejs程式啟動 web server開啟瀏覽器進入表單測試看看POST的方式能否順利運作

完整程式碼如下

var http = require(rsquohttprsquo)

url = require(rsquourlrsquo)

fs = require(rdquofsrdquo)

qs = require(rsquoquerystringrsquo)

server

server = httpcreateServer(function (reqres)

var urlData

67 Express POST應用範例 61

Nodejs中文電子書

encode = rdquoutf8rdquo

filePath = rdquoviewexpress_post_example_formhtmlrdquo

formData

action

urlData = urlparse(requrltrue)

action = urlDatapathname

reswriteHead(200 rdquoContent-Typerdquordquotexthtml charset=utf-8rdquo)

if (action === rdquoSignuprdquo)

formData = rsquorsquo

reqon(rdquodatardquo function (data)

formData += data

)

reqon(rdquoendrdquo function ()

user = qsparse(formData)

resend(rdquolth1gtrdquo + userusername + rdquo 歡迎您的加入 lth1gtltpgt 我們已經將會員啟用信寄至rdquo + useremail + rdquoltpgtrdquo)

)

else

fsreadFile(filePath encode function(err file)

reswrite(file)

resend()

)

)

serverlisten(3000)

consolelog(rsquoServer 跑起來了現在時間是rsquo + new Date())

62 6 Express介紹

Nodejs中文電子書

68 Express AJAX應用範例

在 Nodejs要使用 Ajax傳送資料並且與之互動在接受資料的部份沒有太大的差別client端不是用 GET就是用 POST來傳資料重點在處理完後用 JSON格式回傳當然 Ajax不見得只傳 JSON格式有時是回傳一段 HTML碼不過後者對伺服器來說基本上就和前兩篇沒有差別了所以我們還是以回傳 JSON做為這一回的主題

這一回其實大多數的工作都會落在前端 Ajax上面前端要負責發送與接收資料並在接收資料後撤掉原先發送資料的表單並將取得的資料

改成 HTML格式之後放上頁面

首先先準備 HTML靜態頁面資料

ltDOCTYPE htmlgt

lthtmlgt

ltheadgt

lttitlegtNodejs 菜鳥筆記 (3)lttitlegt

ltlink rel=rdquostylesheetrdquo href=rdquocssstylecssrdquo type=rdquotextcssrdquo media=rdquoallrdquo gt

ltscript src=rdquohttpsajaxgoogleapiscomajaxlibsjquery171jqueryminjsrdquogtltscriptgt

ltheadgt

ltbodygt

lth1gtNodejs 菜鳥筆記-註冊 (用 Ajax)lth1gt

ltform id=rdquosignuprdquo action=rdquoSignuprdquo method=rdquoPOSTrdquogt

ltlabelgt 使用者名稱ltlabelgtltinput type=rdquotextrdquo id=rdquousernamerdquo name=rdquousernamerdquo gtltbrgt

ltlabelgt 電子郵件ltlabelgtltinput type=rdquotextrdquo id=rdquoemailrdquo name=rdquoemailrdquo gtltbrgt

ltinput type=rdquosubmitrdquo value=rdquo 註冊我的帳號rdquo gtltbrgt

ltformgt

ltdiv id=rdquoresultrdquogt

ltdivgt

ltscript type=rdquotextjavascriptrdquogt

$(function()$(rsquosignup input[type=rdquosubmitrdquo]rsquo)click(function(e)

var user =

userusername = $(rdquousernamerdquo)val()useremail = $(rdquoemailrdquo)val()

$post(rdquoSignuprdquouserfunction(data)greet(data)

)

68 Express AJAX應用範例 63

Nodejs中文電子書

epreventDefault()

)

var greet = function(msg)

var resultNode = $(rsquoresultrsquo)greeting = $(rsquolth2gtHirsquo+msgusername+rsquo 你的會員 id 是rsquo+ msgid +rsquo 我們會將會員啟動信寄至rsquo+msgemail+rsquolth2gtrsquo)

$(rsquosignuprsquo)hide()resultNodehtml(rdquordquo)

resultNodeappend(greeting)

)

ltscriptgt

ltbodygt

lthtmlgt

HTML頁面上準備了一個表單用來傳送註冊資料接著直接引用了Google CDN來載入 jQuery用來幫我們處理 Ajax的工作這次要傳送和接收的工作很大的變動都在 HTML頁面上的 JavaScript當中我們要做的事有 (相關 jQuery處理這邊不多做贅述指提起主要功能解說)

bull 用 jQUery取得 submit按鈕綁定它的 click動作

bull 取得表單 username和 email的值存放在 user這個物件中

bull 用 jQuery的 $post方法將 user的資料傳到 Server

bull 一旦成功取得資料後透過 greet這個 function組成回報給 user的訊息

bull 清空原本給使用者填資料的表單

bull 將 Server回傳的 usernameemail和 id這 3個資料組成回應的訊息

bull 將訊息放到原本表單的位置

經過以上的處理後一個 Ajax的表單的基本功能已經完成

接著進行 nodejs主要程式的編輯部分程式碼如下

var fs = require(rdquofsrdquo)

64 6 Express介紹

Nodejs中文電子書

qs = require(rsquoquerystringrsquo)

if (action === rdquoSignuprdquo)

formData = rsquorsquo

reqon(rdquodatardquo function (data)

formData += data

)

reqon(rdquoendrdquo function ()

var msg

user = qsparse(formData)

userid = rdquo123456rdquo

msg = JSONstringify(user)

reswriteHead(200 rdquoContent-Typerdquordquoapplicationjson charset=utf-8rdquordquoContent-Lengthrdquomsglength)

resend(msg)

)

這裡的程式和前面 POST範例基本上大同小異差別在

bull 幫 user的資料加上 id隨意存放一些文字進去讓 Server回傳的資料多於 Client端傳上來的不然會覺得 Server都沒做事

bull 增加了 msg 這個變數存放將 user 物件 JSON 文字化的結果JSONstringify這個轉換函式是 V8引擎所提供的如果你好奇的話

bull 大重點來了我們要告訴 Client端這次回傳的資料格式是 JSON所在 Content-type和 Content-Length要提供給 Client

Server很輕鬆就完成任務了最後進行程式測試啟動 nodejs主程式之後開啟瀏覽器就會看到表單填寫完畢按下送出就可以看到結果了

最後 nodejs本篇範例程式碼如下

var http = require(rsquohttprsquo)

url = require(rsquourlrsquo)

fs = require(rdquofsrdquo)

qs = require(rsquoquerystringrsquo)

server

68 Express AJAX應用範例 65

Nodejs中文電子書

server = httpcreateServer(function (reqres)

var urlData

encode = rdquoutf8rdquo

filePath = rdquoviewexpress_ajax_example_formhtmlrdquo

formData

action

urlData = urlparse(requrltrue)

action = urlDatapathname

if (action === rdquoSignuprdquo)

formData = rsquorsquo

reqon(rdquodatardquo function (data)

formData += data

)

reqon(rdquoendrdquo function ()

var msg

user = qsparse(formData)

userid = rdquo123456rdquo

msg = JSONstringify(user)

reswriteHead(200 rdquoContent-TyperdquordquoapplicationjsonrdquordquoContent-Lengthrdquomsglength)

resend(msg)

)

else

fsreadFile(filePath encode function(err file)

reswriteHead(200 rdquoContent-Typerdquordquotexthtml charset=utf-8rdquo)

reswrite(file)

resend()

)

)

serverlisten(3000)

66 6 Express介紹

Nodejs中文電子書

consolelog(rsquoServer 跑起來了現在時間是rsquo + new Date())

69 原始資料提供

bull [NodeJS初學者筆記 (1)-用 GET傳送資料] (httpithelpithomecomtwquestion10087402)

bull [NodeJS初學者筆記 (2)-用 POST傳送資料] (httpithelpithomecomtwquestion10087489)

bull [NodeJS 初學者筆記 (3)-用 Ajax 傳送資料] (httpithelpithomecomtwquestion10087627)

69 原始資料提供 67

Nodejs中文電子書

68 6 Express介紹

7

CoffeeScript

bull Spine

bull Hem

bull Spineapp

Letrsquos Have a Cup of CoffeeScript

bull es5-shim ECMAScript 5 compatibility shims for legacy JavaScript engines

ESnext

node-iform - A middleware for node-validator httpsgithubcomguileennode-iform

69

Nodejs中文電子書

70 7 CoffeeScript

8

製作一個 Hubot的 Plurk Adapter

81 應用事項提醒

此應用範例以CoffeeScript編寫主要程式架構採用Github釋放的Hubot專案以下有幾個主要注意事項請讀者注意

bull Hubot 一開始的架構除了內建的兩個 Adapter 之外其餘都要以Nodejs的Module方式才能運作

bull Hubot的 binhubot裡面寫著 npm install所以不管你怎麼改原始碼也不能改變第一點的狀況

其實上述都在討論同一件事情NodeJS的模組而且模組不能設定相對路徑之類的來安裝一定要包裝成 npm相符和格式才有辦法正確執行

82 建立 Adapter

首先需要準備兩個檔案

bull packagejson

bull plurkcoffee

71

Nodejs中文電子書

有這兩個就足以變成 NodeJS的模組了在開始 Coding之前先來設定一下 packagejson相依性

rdquonamerdquo rdquohubot-plurkrdquo

rdquoversionrdquo rdquo011rdquo

rdquomainrdquo rdquoplurkrdquo

rdquodependenciesrdquo

rdquohubotrdquo rdquogt=205rdquo

rdquooauthrdquo rdquordquo

rdquocronrdquordquordquo

這邊會使用到NodeJS的 cron模組(Module)而登入 Plurk需要OAuth標準授權才行所以也需要加載 OAuth模組

注意Hubot在讀取非內建模組時會自動在前面加上 hubot-的前置

83 建立 Robot跟 API

本篇應用基本上程式碼大部分參考 Hubot - Twitter Adapter來製作主要差異只有在使用 OAuth這個模組Twitter有 Streaming可用而 Plurk則得用 Comet方式來達到即時讀取

plurkcoffee程式碼

Robot = require(rdquohubotrdquo)robot()

Adapter = require(rdquohubtordquo)adapter()

EventEmitter = require(rdquoeventsrdquo)EventEmitter

oauth = require(rdquooauthrdquo)

cronJob = require(rdquocronrdquo)CronJob

class Plurk exntends Adapter

class PlurkStreaming exnteds EventEmitter

72 8 製作一個 Hubot的 Plurk Adapter

Nodejs中文電子書

先弄個基本架構主要 Class皆參考 Twitter Adapter命名方式接著再將 Plurk這個 Class增加幾個Method基本上只要有 run send reply就夠了而 run用來做初始化的部分

class Plurk entends Adapter

send (plurk id strings⋯) -gt

reply (plurk id strings⋯) -gt

run -gt

看起來有點東西接著來處理主要的 Plurk API結合部分

class PlurkStreaming extends EventEmitter

consuctor (options) -gt

plurk (callback) -gt

觀察河道

getChannel -gt

取得 Comet 網址

reply (plurk id message) -gt

回噗

acceptFriends -gt

接受好友

get (path callback) -gt

GET 請求

post (path body callback)-gt

POST 請求(其實是裝飾)

request (method path body callback)-gt

主要的 OAuth 請求

comet (server callback)-gt

噗浪的 Comet 傳回是 JavaScript Callback 要另外處理後才會變成 JSON

然後把注意力集中到 constructor上先把建構子弄好

constructor (options) -gt

super()

if optionskey and optionssecret and optionstoken and optionstoken secret

key = optionskey

secret = optionssecret

token = optionstoken

token secret = optionstoken secret

83 建立 Robot跟 API 73

Nodejs中文電子書

建立 OAuth 連接

consumer = new oauthOAuth(

rdquohttpwwwplurkcomOAuthrequest tokenrdquo

rdquohttpwwwplurkcomOAuthaccess tokenrdquo

key

secret

rdquo10rdquo

rdquohttpwwwplurkcomOAuthauthorizerdquo

rdquoHMAC-SHA1rdquo

)

domain = rdquowwwplurkcomrdquo

初始化取得 Comet 網址

do getChannel

else

throw new Error(rdquo 參數不足需要 Key Secret Token Token Secretrdquo)

接著來處理 request這個 method

request (method path body callback) -gt

記錄一下這次的 Request

consolelog(rdquohttpdomainpathrdquo)

Callback 這邊先不丟進去要用另一種方式處理

request = consumerget(rdquohttpdomainpathrdquo token token secret null)

requeston rdquoresponserdquo (res) -gt

reson rdquodatardquo (chunk) -gt

parseResponse(chunk+rsquorsquo callback)

reson rdquoendrdquo (data) -gt

consolelog rdquoEnd Request pathrdquo

reson rdquoerrorrdquo (data) -gt

consolelog rdquoError rdquo + data

requestend()

處理資料

parseResponse = (data callback) -gt

if datalength gt 0

用 TryCatch 避免處理 JSON 出錯導致整個中斷

try

callback null JSONparse(data)

catch err

74 8 製作一個 Hubot的 Plurk Adapter

Nodejs中文電子書

consolelog(rdquoError Parse JSONrdquo + data err)

繼續執行

callback null data ||

大致上就是這樣上面程式的架構已經將整個 Hubot Plurk Adapter完成因為在測試時竟然因為噗浪 Lag而沒讀到完整的 Comet資料然後造成程式異常為了避免這個問題發生因此需要加上為了完美呈現需要再

加上 Comet的處理所以要使用到 EventEmitter的功能

comet (server callback) -gt

在 Callback 裡面會找不到自身所以設定區域變數

self =

記錄一下這次的 Request

consolelog(rdquo[Comet] serverrdquo)

Callback 這邊先不丟進去要用另一種方式處理

request = consumerget(rdquohttpdomainpathrdquo token token secret null)

requeston rdquoresponserdquo (res) -gt

reson rdquodatardquo (chunk) -gt

parseResponse(chunk+rsquorsquo callback)

reson rdquoendrdquo (data) -gt

consolelog rdquoEnd Request pathrdquo

請求結束發出事件通知可以進行下一次請求

selfemit rdquonextPlurkrdquo

reson rdquoerrorrdquo (data) -gt

consolelog rdquoError rdquo + data

requestend()

處理資料

parseResponse = (data callback) -gt

if datalength gt 0

用 trycatch 避免失敗中斷

try

去掉 JavaScript 的 Callback

data = datamatch(CometChannelscriptCallback((+))s)

jsonData = rdquordquo

if data

83 建立 Robot跟 API 75

Nodejs中文電子書

jsonData = JSONparse(data[1])

else

如果沒有任何 Match 嘗試直接 parse

jsonData = JSONparse(data)

catch err

consolelog(rdquo[Comet] Errorrdquo data err)

用 TryCatch 避免處理 JSON 出錯導致整個中斷

try

只傳入 json 的 data 部分

callback null jsonDatadata

catch err

consolelog(rdquo[Comet]Error Parse JSONrdquo + data err)

繼續執行

callback null data ||

後面的 get跟 post就簡單多了

get (path callback) -gt

request(rdquoGETrdquo path null callback)

post (path body callback) -gt

request(rdquoPOSTrdquo path body callback)

接著處理取的 Comet網址的 getChannel

getChannel -gt

self =

get rdquoAPPRealtimegetUserChannelrdquo (error data) -gt

if error

檢查是否有 comet server

if datacomet server

selfchannel = datacomet server

如果沒有 Channel Ready 就嘗試連接會失敗

selfemit(rsquochannel readyrsquo)

那麼先來處理 Plurk Adaper好處理的部份

send (plruk id strings⋯)-gt

跟 Reply 一樣直接交給 reply 做

reply plurk id strings⋯

76 8 製作一個 Hubot的 Plurk Adapter

Nodejs中文電子書

reply (plurk id strings⋯) -gt

stringsforEach (message) =gt

botreply(plruk id message)

接著把 run處理好就可以上線運作摟

run -gt

self =

options =

key processenvHUBOT PLURK KEY

secret processenvHUBOT PLURK SECRET

token processenvHUBOT PLURK TOKEN

token secret processenvHUBOT PLURK TOKEN SECRET

創建剛剛的 API

bot = new PlurkStreaming(options)

依照 Twitter 的 new RobotTextMessage 會沒有反應所以參考 hubot-minecraft 的方式

r = robotconstructor

處理噗浪河道訊息

doPlurk = (data)-gt

檢查是否為回噗

if dataresponse

datacontent raw = dataresponsecontent raw

datauser id = dataresponseuser id

確定有噗浪 ID 跟訊息

if dataplurk id and datacontent raw

selfreceive new rTextMessage(dataplurk id datacontent raw)

取得 Comet Server 完成開始第一次 Comet 連接

boton rdquochannel readyrdquo () -gt

botplurk selfdoPlurk

上一次 Comet 完成繼續 Polling

boton rdquonextPlurkrdquo ()-gt

botplurk selfdoPlurk

定時接受好友邀請

do botacceptFriends

bot = bot

83 建立 Robot跟 API 77

Nodejs中文電子書

終於完成 AdapterHubot專案裡面的 scripts資料夾內是互動部分不需要像 Adapter如此大費周章處理只新增檔案並且設計好對白之後就會回噗了我開發用的機器人在此大家可以去跟他玩玩[httpplurkcomelct9620 bot](httpplurkcomelct9620 bot)

84 原始資料提供

bull [製 作 一 個 Hubot 的 噗 浪 Adapter](http revo-skillfrosttw blog20120318create-a-hubot-plurk-adapter)

78 8 製作一個 Hubot的 Plurk Adapter

9

Nodejs好用工具介紹

91 logio

Real-time log monitoring in your browser

httplogioorg

nodejs (gt=0412 lt=0611)

npm config set unsafe-perm true

npm install -g --prefix=usrlocal logio

logio server start

httplocalhost8998

79

Nodejs中文電子書

80 9 Nodejs好用工具介紹

10

精選文章

101 Express

bull 使用 NodeJS + Express從 GETPOST Request取值 [邀稿中]

bull link2 [邀稿中]

bull link3 [邀稿中]

81

Nodejs中文電子書

82 10 精選文章

11

參考資源

111 Nodejs書籍

bull Programming Nodejs April 2012 (est) Travis Swicegood Pragmatic Bookshelf

bull e Little Book on CoffeeScript January 2012 Alex MacCaw OrsquoReilly Media

bull Node for Front-End Developers January 2012 Garann Means OrsquoReilly Media

bull Building Hypermedia APIs with HTML5 and Node November 2011 MikeAmundsen OrsquoReilly Media

bull Node Web Development August 2011 David Herron PacktPub

bull CoffeeScript Accelerated JavaScript Development July 2011 Trevor BurnhamPragmatic Bookshelf

bull Getting Started with GEO CouchDB and Nodejs July 2011 Mick omp-son OrsquoReilly Media

bull What Is Node July 2011 Brett McLaughlin OrsquoReilly Media

bull Node Up and Running May 2011 Tom Hughes-Croucher Mike WilsonOrsquoReilly Media

83

Nodejs中文電子書

bull Hands-on Nodejs e Nodejs introduction and API reference May 2011 Pe-dro Teixeira LeanPub

bull e Node Beginner Book Manuel Kiessling

bull Mastering Nodejs visionmedia

112 Nodejs影音教學

bull e Node Sessions e Best of OSCON 2011 August 2011 OrsquoReilly Media(Video)

bull Tom Hughes-Croucher on Node March 2011 OrsquoReilly Media (Video)

113 Nodejs教學網站

bull Node Tuts

114 Nodejs課程

bull Introduction To Nodejs Van Nguyen CodeLesson

84 11 參考資源

Page 58: Nodejs Wiki

Nodejs中文電子書

62 Express基本操作

express的使用也十分簡單先來建立一個基本的 hello world

var app = require(rsquoexpressrsquo)createServer()

port = 1337

applisten(port)

appget(rsquorsquo function(req res)

ressend(rsquohello worldrsquo)

)

consolelog(rsquostart express servernrsquo)

可以從上面的程式碼發現基本操作與 nodejs http的建立方式沒有太大差異主要差在當我們設定路由時可以直接透過 appget方式設定回應與接受方式

63 Express路由處理

Express對於 http服務上有許多包裝讓開發者使用及設定上更為方便例如有幾個路由設定那我們就統一藉由 appget來處理

Create http server

appget(rsquorsquo function(req res)

ressend(rsquohello worldrsquo)

)

appget(rsquotestrsquo function(req res)

ressend(rsquotest renderrsquo)

)

appget(rsquouserrsquo function(req res)

ressend(rsquouser pagersquo)

)

52 6 Express介紹

Nodejs中文電子書

如上面的程式碼所表示appget可以帶入兩個參數第一個是路徑名稱設定第二個為回應函式 (call back function)回應函式裡面就如同之前的 createServer方法裡面包含 requestresponse兩個物件可供使用使用者就可以透過瀏覽器輸入不同的 url切換到不同的頁面顯示不同的結果

路由設定上也有基本的配對方式讓使用者從瀏覽器輸入的網址可以

是一個變數只要符合型態就可以有對應的頁面產出例如

Create http server

appget(rsquouseridrsquo function(req res)

ressend(rsquouser rsquo + reqparamsid)

)

appget(rsquonumberrsquo function(req res)

ressend(rsquonumber rsquo + reqparamsnumber)

)

裡 面 使 用 到number 從 網 址 輸 入 之 後 就 可 以 直 接 使 用reqparamsnumber 取得所輸入的資料變成 url 參數使用當然前面也是可以加上路徑的設定userid在瀏覽器上路徑必須符合userxxx透

過 reqparamsid就可以取到 xxx這個字串值

另外express參數處理也提供了路由參數配對處理也可以透過正規表示法作為參數設定

var app = require(rsquoexpressrsquo)createServer()

port = 1337

applisten(port)

appget(^ip((d23)((d23))((d23))((d23))) function(req res)

ressend(reqparams)

)

上面程式碼可以發現後面路由設定的型態是正規表示法裡面設定

格式為ip之後必須要加上 ip型態才會符合資料格式同時取得 ip資料已經由正規表示法將資料做分群因此可以取得 ip的四個數字

此程式執行之後可以透過瀏覽器測試輸入網址為 localhost3000ip25525510010可以從頁面獲得資料

63 Express路由處理 53

Nodejs中文電子書

此章節全部範例程式碼如下

overview

author Caesar Chi

blog clonnblogspotcom

version 20120226

create server

var app = require(rsquoexpressrsquo)createServer()

port = 1337

applisten(port)

normal style

appget(rsquorsquo function(req res)

ressend(rsquohello worldrsquo)

)

appget(rsquotestrsquo function(req res)

ressend(rsquotest renderrsquo)

)

parameter style

appget(rsquouseridrsquo function(req res)

ressend(rsquouser rsquo + reqparamsid)

)

appget(rsquonumberrsquo function(req res)

ressend(rsquonumber rsquo + reqparamsnumber)

)

REGX style

appget(^ip((d23)((d23))((d23))) function(req res)

ressend(reqparams)

)

appget(rsquorsquo function(req res)

ressend(rsquoPage not foundrsquo 404)

54 6 Express介紹

Nodejs中文電子書

)

consolelog(rsquostart express servernrsquo)

64 Express middleware

Express 裡面有一個十分好用的應用概念稱為 middleware可以透過middleware做出複雜的效果同時上面也有介紹 next方法參數傳遞就是靠 middleware的概念來傳遞參數讓開發者可以明確的控制程式邏輯

create http server

appuse(expressbodyParser())

appuse(expressmethodOverride())

appuse(expresssession())

上面都是一種 middleware的使用方式透過 appuse方式裡面載入函式執行方法回應函式會包含三個基本參數responserequestnext其中next表示下一個 middleware執行函式同時會自動將預設三個參數繼續帶往下個函式執行底下有個實驗

上面的片段程式執行後開啟瀏覽器連結上 localhost1337會發現伺服器回應結果順序如下

first middle ware

second middle ware

execute middle ware

end middleware function

從上面的結果可以得知剛才設定的 middleware都生效了在 appuse設定的 middleware是所有 url皆會執行方法如果有指定特定方法就可以使用 appget的 middleware設定在 appget函式的第二個參數就可以帶入函式或者是匿名函式只要函式裡面最後會接受 request response next這三個參數同時也有正確指定 next函式的執行時機最後都會執行到最後一個方法當然開發者也可以評估程式邏輯要執行到哪一個階段讓邏輯

可以更為分明

64 Express middleware 55

Nodejs中文電子書

65 Express路由應用

在實際開發上可能會遇到需要使用參數等方式混和變數一起使用

express裡面提供了一個很棒的處理方法 appall這個方式可以先採用基本路由配對再將設定為每個不同的處理方式開發者可以透過這個方式簡

化自己的程式邏輯

overview

author

version 20120226

create server

var app = require(rsquoexpressrsquo)createServer()

port = 1337

users = [

name rsquoClonnrsquo

name rsquoChirsquo

]

applisten(port)

appall(rsquouseridoprsquo function(req res next)

requser = users[reqparamsid]

if (requser)

next()

else

next(new Error(rsquocannot find user rsquo + reqparamsid))

)

appget(rsquouseridrsquo function(req res)

ressend(rsquoviewing rsquo + requsername)

)

appget(rsquouserideditrsquo function(req res)

ressend(rsquoediting rsquo + requsername)

)

56 6 Express介紹

Nodejs中文電子書

appget(rsquouseriddeletersquo function(req res)

ressend(rsquodeleting rsquo + requsername)

)

appget(rsquorsquo function(req res)

ressend(rsquoPage not foundrsquo 404)

)

consolelog(rsquostart express servernrsquo)

內部宣告一組預設的使用者分別給予名稱設定藉由 appall這個方法可以先將路由雛形建立再接下來設定 appget的路徑格式只要符合格式就會分配進入對應的方法中像上面的程式當中如果使用者輸入路徑為user0除了執行 appall程式之後執行 next方法就會對應到路徑設定為userid的這個方法當中如果使用者輸入路徑為user0edit就會執行到useridedit的對應方法

66 Express GET應用範例

我們準備一個使用 GET方法傳送資料的表單

ltDOCTYPE htmlgt

lthtmlgt

ltheadgt

ltmeta http-equiv=rdquoContent-Typerdquo content=rdquotexthtml charset=utf8rdquogt

lttitlegtNodejs 菜鳥筆記 (1)lttitlegt

ltlink rel=rdquostylesheetrdquo href=rdquocssstylecssrdquo type=rdquotextcssrdquo media=rdquoallrdquo gt

ltheadgt

ltbodygt

lth1gtNodejs 菜鳥筆記-註冊lth1gt

ltform id=rdquosignuprdquo method=rdquoGETrdquo action=rdquohttplocalhost3000Signuprdquogt

ltlabelgt 使用者名稱ltlabelgtltinput type=rdquotextrdquo id=rdquousernamerdquo name=rdquousernamerdquo gtltbrgt

ltlabelgt 電子郵件ltlabelgtltinput type=rdquotextrdquo id=rdquoemailrdquo name=rdquoemailrdquo gtltbrgt

ltinput type=rdquosubmitrdquo value=rdquo 註冊我的帳號rdquo gtltbrgt

ltformgt

ltbodygt

lthtmlgt

66 Express GET應用範例 57

Nodejs中文電子書

這個表單沒有什麼特別的地方我們只需要看第 9 行form 使用的method是 GET然後 action是rdquohttplocalhost3000Signupldquo等一下我們要來撰寫Signup這個 URL Path的處理程式

處理 Signup行為

我們知道所謂的 GET方法會透過 URL來把表單的值給帶過去以上面的表單來說到時候 URL會以這樣的形式傳遞

httplocalhost3000Signupusername=xxxampemail=xxx

所以要能處理這樣的資料必須有以下功能

bull 解析 URL

bull 辨別動作是 Signup

bull 解析出 username和 email

一旦能取得 username和 email的值程式就能加以應用了

處理 Signup的程式碼雛形

load module

var url = require(rsquourlrsquo)

urlData = urlparse(requrltrue)

action = urlDatapathname

reswriteHead(200 rdquoContent-Typerdquordquotexthtml charset=utf-8rdquo)

if (action === rdquoSignuprdquo)

user = urlDataquery

resend(rdquolth1gtrdquo + userusername + rdquo 歡迎您的加入 lth1gtltpgt 我們已經將會員啟用信寄至rdquo + useremail + rdquoltpgtrdquo)

首先需要加載 url module它是用來協助我們解析 URL的模組接著使用 urlparse方法第一個傳入 url字串變數也就是 requrl另外第二個參數的用意是設為 ture則引進 querystring模組來協助處理預設是 false它影響到的是 urlDataquery設為 true會傳回物件不然就只是一般的字串urlparse會將字串內容整理成一個物件我們把它指定給 urlData

action變數作為記錄 pathname這是我們稍後要來判斷目前網頁的動作是什麼接著先將 html表頭資訊 (Header)準備好再來判斷路徑邏輯如

58 6 Express介紹

Nodejs中文電子書

果是 Signup這個動作就把 urlDataquery裡的資料指定給 user然後輸出userusername和 useremail把使用者從表單註冊的資料顯示於頁面中

最後進行程式測試啟動 nodejs主程式之後開啟瀏覽器就會看到表單填寫完畢按下送出就可以看到結果了

完整 nodejs程式碼如下

var http = require(rsquohttprsquo)

url = require(rsquourlrsquo)

fs = require(rdquofsrdquo)

server

server = httpcreateServer(function (reqres)

var urlData

encode = rdquoutf8rdquo

filePath = rdquoviewexpress_get_example_formhtmlrdquo

action

urlData = urlparse(requrltrue)

action = urlDatapathname

reswriteHead(200 rdquoContent-Typerdquordquotexthtml charset=utf-8rdquo)

if (action === rdquoSignuprdquo)

user = urlDataquery

resend(rdquolth1gtrdquo + userusername + rdquo 歡迎您的加入 lth1gtltpgt 我們已經將會員啟用信寄至rdquo + useremail + rdquoltpgtrdquo)

else

fsreadFile(filePath encode function(err file)

reswrite(file)

resend()

)

)

serverlisten(3000)

consolelog(rsquoServer 跑起來了現在時間是rsquo + new Date())

66 Express GET應用範例 59

Nodejs中文電子書

67 Express POST應用範例

一開始準備基本的 html表單傳送內容以 POST方式form的 action屬性設定為 POST其餘 html內容與前一個範例應用相同

ltDOCTYPE htmlgt

lthtmlgt

ltheadgt

ltmeta http-equiv=rdquoContent-Typerdquo content=rdquotexthtml charset=utf8rdquogt

lttitlegtNodejs 菜鳥筆記 (1)lttitlegt

ltlink rel=rdquostylesheetrdquo href=rdquocssstylecssrdquo type=rdquotextcssrdquo media=rdquoallrdquo gt

ltheadgt

ltbodygt

lth1gtNodejs 菜鳥筆記-註冊lth1gt

ltform id=rdquosignuprdquo method=rdquoPOSTrdquo action=rdquohttplocalhost3000Signuprdquogt

ltlabelgt 使用者名稱ltlabelgtltinput type=rdquotextrdquo id=rdquousernamerdquo name=rdquousernamerdquo gtltbrgt

ltlabelgt 電子郵件ltlabelgtltinput type=rdquotextrdquo id=rdquoemailrdquo name=rdquoemailrdquo gtltbrgt

ltinput type=rdquosubmitrdquo value=rdquo 註冊我的帳號rdquo gtltbrgt

ltformgt

ltbodygt

lthtmlgt

nodejs的程式處理邏輯與前面 GET範例類似部分程式碼如下

qs = require(rsquoquerystringrsquo)

if (action === rdquoSignuprdquo)

formData = rsquorsquo

reqon(rdquodatardquo function (data)

formData += data

)

reqon(rdquoendrdquo function ()

user = qsparse(formData)

resend(rdquolth1gtrdquo + userusername + rdquo 歡迎您的加入 lth1gtltpgt 我們已經將會員啟用信寄至rdquo + useremail + rdquoltpgtrdquo)

)

60 6 Express介紹

Nodejs中文電子書

主要加入了rsquoquerystringrsquo這個moduel方便我們等一下解析由表單 POST回來的資料另外加入一個 formData的變數用來搜集待等一下表單回傳的資料前面的 GET範例我們只從 req拿出 url的資料這次要在利用req身上的事件處理

JavaScript 在訂閱事件時使用 addEventListener而 nodejs 使用的則是on這邊加上了監聽 data的事件會在瀏覽器傳送資料到Web Server時被執行參數是它所接收到的資料型態是字串

接著再增加 end的事件當瀏覽器的請求事件結束時它就會動作

由於瀏覽器使用 POST在上傳資料時會將資料一塊塊地上傳因為我們在監聽 data事件時透過 formData變數將它累加起來lt不過由於我們

上傳的資料很少一次就結束不過如果日後需要傳的是資料比較大的檔

案這個累加動作就很重要

當資料傳完就進到 end 事件中會用到 qsparse 來解析 formDataformData的內容是字串內容是

username=wordsmithampemail=wordsmith40somewhere

而 qsparse可以幫我們把這個 querystring轉成物件的格式也就是

username=wordsmithampemail=wordsmith40somewhere

一旦轉成物件並指定給 user之後其他的事情就和 GET方法時操作的一樣寫 response的表頭將內容回傳並將 userusername和 useremail代入到內容中

修改完成後接著執行 nodejs程式啟動 web server開啟瀏覽器進入表單測試看看POST的方式能否順利運作

完整程式碼如下

var http = require(rsquohttprsquo)

url = require(rsquourlrsquo)

fs = require(rdquofsrdquo)

qs = require(rsquoquerystringrsquo)

server

server = httpcreateServer(function (reqres)

var urlData

67 Express POST應用範例 61

Nodejs中文電子書

encode = rdquoutf8rdquo

filePath = rdquoviewexpress_post_example_formhtmlrdquo

formData

action

urlData = urlparse(requrltrue)

action = urlDatapathname

reswriteHead(200 rdquoContent-Typerdquordquotexthtml charset=utf-8rdquo)

if (action === rdquoSignuprdquo)

formData = rsquorsquo

reqon(rdquodatardquo function (data)

formData += data

)

reqon(rdquoendrdquo function ()

user = qsparse(formData)

resend(rdquolth1gtrdquo + userusername + rdquo 歡迎您的加入 lth1gtltpgt 我們已經將會員啟用信寄至rdquo + useremail + rdquoltpgtrdquo)

)

else

fsreadFile(filePath encode function(err file)

reswrite(file)

resend()

)

)

serverlisten(3000)

consolelog(rsquoServer 跑起來了現在時間是rsquo + new Date())

62 6 Express介紹

Nodejs中文電子書

68 Express AJAX應用範例

在 Nodejs要使用 Ajax傳送資料並且與之互動在接受資料的部份沒有太大的差別client端不是用 GET就是用 POST來傳資料重點在處理完後用 JSON格式回傳當然 Ajax不見得只傳 JSON格式有時是回傳一段 HTML碼不過後者對伺服器來說基本上就和前兩篇沒有差別了所以我們還是以回傳 JSON做為這一回的主題

這一回其實大多數的工作都會落在前端 Ajax上面前端要負責發送與接收資料並在接收資料後撤掉原先發送資料的表單並將取得的資料

改成 HTML格式之後放上頁面

首先先準備 HTML靜態頁面資料

ltDOCTYPE htmlgt

lthtmlgt

ltheadgt

lttitlegtNodejs 菜鳥筆記 (3)lttitlegt

ltlink rel=rdquostylesheetrdquo href=rdquocssstylecssrdquo type=rdquotextcssrdquo media=rdquoallrdquo gt

ltscript src=rdquohttpsajaxgoogleapiscomajaxlibsjquery171jqueryminjsrdquogtltscriptgt

ltheadgt

ltbodygt

lth1gtNodejs 菜鳥筆記-註冊 (用 Ajax)lth1gt

ltform id=rdquosignuprdquo action=rdquoSignuprdquo method=rdquoPOSTrdquogt

ltlabelgt 使用者名稱ltlabelgtltinput type=rdquotextrdquo id=rdquousernamerdquo name=rdquousernamerdquo gtltbrgt

ltlabelgt 電子郵件ltlabelgtltinput type=rdquotextrdquo id=rdquoemailrdquo name=rdquoemailrdquo gtltbrgt

ltinput type=rdquosubmitrdquo value=rdquo 註冊我的帳號rdquo gtltbrgt

ltformgt

ltdiv id=rdquoresultrdquogt

ltdivgt

ltscript type=rdquotextjavascriptrdquogt

$(function()$(rsquosignup input[type=rdquosubmitrdquo]rsquo)click(function(e)

var user =

userusername = $(rdquousernamerdquo)val()useremail = $(rdquoemailrdquo)val()

$post(rdquoSignuprdquouserfunction(data)greet(data)

)

68 Express AJAX應用範例 63

Nodejs中文電子書

epreventDefault()

)

var greet = function(msg)

var resultNode = $(rsquoresultrsquo)greeting = $(rsquolth2gtHirsquo+msgusername+rsquo 你的會員 id 是rsquo+ msgid +rsquo 我們會將會員啟動信寄至rsquo+msgemail+rsquolth2gtrsquo)

$(rsquosignuprsquo)hide()resultNodehtml(rdquordquo)

resultNodeappend(greeting)

)

ltscriptgt

ltbodygt

lthtmlgt

HTML頁面上準備了一個表單用來傳送註冊資料接著直接引用了Google CDN來載入 jQuery用來幫我們處理 Ajax的工作這次要傳送和接收的工作很大的變動都在 HTML頁面上的 JavaScript當中我們要做的事有 (相關 jQuery處理這邊不多做贅述指提起主要功能解說)

bull 用 jQUery取得 submit按鈕綁定它的 click動作

bull 取得表單 username和 email的值存放在 user這個物件中

bull 用 jQuery的 $post方法將 user的資料傳到 Server

bull 一旦成功取得資料後透過 greet這個 function組成回報給 user的訊息

bull 清空原本給使用者填資料的表單

bull 將 Server回傳的 usernameemail和 id這 3個資料組成回應的訊息

bull 將訊息放到原本表單的位置

經過以上的處理後一個 Ajax的表單的基本功能已經完成

接著進行 nodejs主要程式的編輯部分程式碼如下

var fs = require(rdquofsrdquo)

64 6 Express介紹

Nodejs中文電子書

qs = require(rsquoquerystringrsquo)

if (action === rdquoSignuprdquo)

formData = rsquorsquo

reqon(rdquodatardquo function (data)

formData += data

)

reqon(rdquoendrdquo function ()

var msg

user = qsparse(formData)

userid = rdquo123456rdquo

msg = JSONstringify(user)

reswriteHead(200 rdquoContent-Typerdquordquoapplicationjson charset=utf-8rdquordquoContent-Lengthrdquomsglength)

resend(msg)

)

這裡的程式和前面 POST範例基本上大同小異差別在

bull 幫 user的資料加上 id隨意存放一些文字進去讓 Server回傳的資料多於 Client端傳上來的不然會覺得 Server都沒做事

bull 增加了 msg 這個變數存放將 user 物件 JSON 文字化的結果JSONstringify這個轉換函式是 V8引擎所提供的如果你好奇的話

bull 大重點來了我們要告訴 Client端這次回傳的資料格式是 JSON所在 Content-type和 Content-Length要提供給 Client

Server很輕鬆就完成任務了最後進行程式測試啟動 nodejs主程式之後開啟瀏覽器就會看到表單填寫完畢按下送出就可以看到結果了

最後 nodejs本篇範例程式碼如下

var http = require(rsquohttprsquo)

url = require(rsquourlrsquo)

fs = require(rdquofsrdquo)

qs = require(rsquoquerystringrsquo)

server

68 Express AJAX應用範例 65

Nodejs中文電子書

server = httpcreateServer(function (reqres)

var urlData

encode = rdquoutf8rdquo

filePath = rdquoviewexpress_ajax_example_formhtmlrdquo

formData

action

urlData = urlparse(requrltrue)

action = urlDatapathname

if (action === rdquoSignuprdquo)

formData = rsquorsquo

reqon(rdquodatardquo function (data)

formData += data

)

reqon(rdquoendrdquo function ()

var msg

user = qsparse(formData)

userid = rdquo123456rdquo

msg = JSONstringify(user)

reswriteHead(200 rdquoContent-TyperdquordquoapplicationjsonrdquordquoContent-Lengthrdquomsglength)

resend(msg)

)

else

fsreadFile(filePath encode function(err file)

reswriteHead(200 rdquoContent-Typerdquordquotexthtml charset=utf-8rdquo)

reswrite(file)

resend()

)

)

serverlisten(3000)

66 6 Express介紹

Nodejs中文電子書

consolelog(rsquoServer 跑起來了現在時間是rsquo + new Date())

69 原始資料提供

bull [NodeJS初學者筆記 (1)-用 GET傳送資料] (httpithelpithomecomtwquestion10087402)

bull [NodeJS初學者筆記 (2)-用 POST傳送資料] (httpithelpithomecomtwquestion10087489)

bull [NodeJS 初學者筆記 (3)-用 Ajax 傳送資料] (httpithelpithomecomtwquestion10087627)

69 原始資料提供 67

Nodejs中文電子書

68 6 Express介紹

7

CoffeeScript

bull Spine

bull Hem

bull Spineapp

Letrsquos Have a Cup of CoffeeScript

bull es5-shim ECMAScript 5 compatibility shims for legacy JavaScript engines

ESnext

node-iform - A middleware for node-validator httpsgithubcomguileennode-iform

69

Nodejs中文電子書

70 7 CoffeeScript

8

製作一個 Hubot的 Plurk Adapter

81 應用事項提醒

此應用範例以CoffeeScript編寫主要程式架構採用Github釋放的Hubot專案以下有幾個主要注意事項請讀者注意

bull Hubot 一開始的架構除了內建的兩個 Adapter 之外其餘都要以Nodejs的Module方式才能運作

bull Hubot的 binhubot裡面寫著 npm install所以不管你怎麼改原始碼也不能改變第一點的狀況

其實上述都在討論同一件事情NodeJS的模組而且模組不能設定相對路徑之類的來安裝一定要包裝成 npm相符和格式才有辦法正確執行

82 建立 Adapter

首先需要準備兩個檔案

bull packagejson

bull plurkcoffee

71

Nodejs中文電子書

有這兩個就足以變成 NodeJS的模組了在開始 Coding之前先來設定一下 packagejson相依性

rdquonamerdquo rdquohubot-plurkrdquo

rdquoversionrdquo rdquo011rdquo

rdquomainrdquo rdquoplurkrdquo

rdquodependenciesrdquo

rdquohubotrdquo rdquogt=205rdquo

rdquooauthrdquo rdquordquo

rdquocronrdquordquordquo

這邊會使用到NodeJS的 cron模組(Module)而登入 Plurk需要OAuth標準授權才行所以也需要加載 OAuth模組

注意Hubot在讀取非內建模組時會自動在前面加上 hubot-的前置

83 建立 Robot跟 API

本篇應用基本上程式碼大部分參考 Hubot - Twitter Adapter來製作主要差異只有在使用 OAuth這個模組Twitter有 Streaming可用而 Plurk則得用 Comet方式來達到即時讀取

plurkcoffee程式碼

Robot = require(rdquohubotrdquo)robot()

Adapter = require(rdquohubtordquo)adapter()

EventEmitter = require(rdquoeventsrdquo)EventEmitter

oauth = require(rdquooauthrdquo)

cronJob = require(rdquocronrdquo)CronJob

class Plurk exntends Adapter

class PlurkStreaming exnteds EventEmitter

72 8 製作一個 Hubot的 Plurk Adapter

Nodejs中文電子書

先弄個基本架構主要 Class皆參考 Twitter Adapter命名方式接著再將 Plurk這個 Class增加幾個Method基本上只要有 run send reply就夠了而 run用來做初始化的部分

class Plurk entends Adapter

send (plurk id strings⋯) -gt

reply (plurk id strings⋯) -gt

run -gt

看起來有點東西接著來處理主要的 Plurk API結合部分

class PlurkStreaming extends EventEmitter

consuctor (options) -gt

plurk (callback) -gt

觀察河道

getChannel -gt

取得 Comet 網址

reply (plurk id message) -gt

回噗

acceptFriends -gt

接受好友

get (path callback) -gt

GET 請求

post (path body callback)-gt

POST 請求(其實是裝飾)

request (method path body callback)-gt

主要的 OAuth 請求

comet (server callback)-gt

噗浪的 Comet 傳回是 JavaScript Callback 要另外處理後才會變成 JSON

然後把注意力集中到 constructor上先把建構子弄好

constructor (options) -gt

super()

if optionskey and optionssecret and optionstoken and optionstoken secret

key = optionskey

secret = optionssecret

token = optionstoken

token secret = optionstoken secret

83 建立 Robot跟 API 73

Nodejs中文電子書

建立 OAuth 連接

consumer = new oauthOAuth(

rdquohttpwwwplurkcomOAuthrequest tokenrdquo

rdquohttpwwwplurkcomOAuthaccess tokenrdquo

key

secret

rdquo10rdquo

rdquohttpwwwplurkcomOAuthauthorizerdquo

rdquoHMAC-SHA1rdquo

)

domain = rdquowwwplurkcomrdquo

初始化取得 Comet 網址

do getChannel

else

throw new Error(rdquo 參數不足需要 Key Secret Token Token Secretrdquo)

接著來處理 request這個 method

request (method path body callback) -gt

記錄一下這次的 Request

consolelog(rdquohttpdomainpathrdquo)

Callback 這邊先不丟進去要用另一種方式處理

request = consumerget(rdquohttpdomainpathrdquo token token secret null)

requeston rdquoresponserdquo (res) -gt

reson rdquodatardquo (chunk) -gt

parseResponse(chunk+rsquorsquo callback)

reson rdquoendrdquo (data) -gt

consolelog rdquoEnd Request pathrdquo

reson rdquoerrorrdquo (data) -gt

consolelog rdquoError rdquo + data

requestend()

處理資料

parseResponse = (data callback) -gt

if datalength gt 0

用 TryCatch 避免處理 JSON 出錯導致整個中斷

try

callback null JSONparse(data)

catch err

74 8 製作一個 Hubot的 Plurk Adapter

Nodejs中文電子書

consolelog(rdquoError Parse JSONrdquo + data err)

繼續執行

callback null data ||

大致上就是這樣上面程式的架構已經將整個 Hubot Plurk Adapter完成因為在測試時竟然因為噗浪 Lag而沒讀到完整的 Comet資料然後造成程式異常為了避免這個問題發生因此需要加上為了完美呈現需要再

加上 Comet的處理所以要使用到 EventEmitter的功能

comet (server callback) -gt

在 Callback 裡面會找不到自身所以設定區域變數

self =

記錄一下這次的 Request

consolelog(rdquo[Comet] serverrdquo)

Callback 這邊先不丟進去要用另一種方式處理

request = consumerget(rdquohttpdomainpathrdquo token token secret null)

requeston rdquoresponserdquo (res) -gt

reson rdquodatardquo (chunk) -gt

parseResponse(chunk+rsquorsquo callback)

reson rdquoendrdquo (data) -gt

consolelog rdquoEnd Request pathrdquo

請求結束發出事件通知可以進行下一次請求

selfemit rdquonextPlurkrdquo

reson rdquoerrorrdquo (data) -gt

consolelog rdquoError rdquo + data

requestend()

處理資料

parseResponse = (data callback) -gt

if datalength gt 0

用 trycatch 避免失敗中斷

try

去掉 JavaScript 的 Callback

data = datamatch(CometChannelscriptCallback((+))s)

jsonData = rdquordquo

if data

83 建立 Robot跟 API 75

Nodejs中文電子書

jsonData = JSONparse(data[1])

else

如果沒有任何 Match 嘗試直接 parse

jsonData = JSONparse(data)

catch err

consolelog(rdquo[Comet] Errorrdquo data err)

用 TryCatch 避免處理 JSON 出錯導致整個中斷

try

只傳入 json 的 data 部分

callback null jsonDatadata

catch err

consolelog(rdquo[Comet]Error Parse JSONrdquo + data err)

繼續執行

callback null data ||

後面的 get跟 post就簡單多了

get (path callback) -gt

request(rdquoGETrdquo path null callback)

post (path body callback) -gt

request(rdquoPOSTrdquo path body callback)

接著處理取的 Comet網址的 getChannel

getChannel -gt

self =

get rdquoAPPRealtimegetUserChannelrdquo (error data) -gt

if error

檢查是否有 comet server

if datacomet server

selfchannel = datacomet server

如果沒有 Channel Ready 就嘗試連接會失敗

selfemit(rsquochannel readyrsquo)

那麼先來處理 Plurk Adaper好處理的部份

send (plruk id strings⋯)-gt

跟 Reply 一樣直接交給 reply 做

reply plurk id strings⋯

76 8 製作一個 Hubot的 Plurk Adapter

Nodejs中文電子書

reply (plurk id strings⋯) -gt

stringsforEach (message) =gt

botreply(plruk id message)

接著把 run處理好就可以上線運作摟

run -gt

self =

options =

key processenvHUBOT PLURK KEY

secret processenvHUBOT PLURK SECRET

token processenvHUBOT PLURK TOKEN

token secret processenvHUBOT PLURK TOKEN SECRET

創建剛剛的 API

bot = new PlurkStreaming(options)

依照 Twitter 的 new RobotTextMessage 會沒有反應所以參考 hubot-minecraft 的方式

r = robotconstructor

處理噗浪河道訊息

doPlurk = (data)-gt

檢查是否為回噗

if dataresponse

datacontent raw = dataresponsecontent raw

datauser id = dataresponseuser id

確定有噗浪 ID 跟訊息

if dataplurk id and datacontent raw

selfreceive new rTextMessage(dataplurk id datacontent raw)

取得 Comet Server 完成開始第一次 Comet 連接

boton rdquochannel readyrdquo () -gt

botplurk selfdoPlurk

上一次 Comet 完成繼續 Polling

boton rdquonextPlurkrdquo ()-gt

botplurk selfdoPlurk

定時接受好友邀請

do botacceptFriends

bot = bot

83 建立 Robot跟 API 77

Nodejs中文電子書

終於完成 AdapterHubot專案裡面的 scripts資料夾內是互動部分不需要像 Adapter如此大費周章處理只新增檔案並且設計好對白之後就會回噗了我開發用的機器人在此大家可以去跟他玩玩[httpplurkcomelct9620 bot](httpplurkcomelct9620 bot)

84 原始資料提供

bull [製 作 一 個 Hubot 的 噗 浪 Adapter](http revo-skillfrosttw blog20120318create-a-hubot-plurk-adapter)

78 8 製作一個 Hubot的 Plurk Adapter

9

Nodejs好用工具介紹

91 logio

Real-time log monitoring in your browser

httplogioorg

nodejs (gt=0412 lt=0611)

npm config set unsafe-perm true

npm install -g --prefix=usrlocal logio

logio server start

httplocalhost8998

79

Nodejs中文電子書

80 9 Nodejs好用工具介紹

10

精選文章

101 Express

bull 使用 NodeJS + Express從 GETPOST Request取值 [邀稿中]

bull link2 [邀稿中]

bull link3 [邀稿中]

81

Nodejs中文電子書

82 10 精選文章

11

參考資源

111 Nodejs書籍

bull Programming Nodejs April 2012 (est) Travis Swicegood Pragmatic Bookshelf

bull e Little Book on CoffeeScript January 2012 Alex MacCaw OrsquoReilly Media

bull Node for Front-End Developers January 2012 Garann Means OrsquoReilly Media

bull Building Hypermedia APIs with HTML5 and Node November 2011 MikeAmundsen OrsquoReilly Media

bull Node Web Development August 2011 David Herron PacktPub

bull CoffeeScript Accelerated JavaScript Development July 2011 Trevor BurnhamPragmatic Bookshelf

bull Getting Started with GEO CouchDB and Nodejs July 2011 Mick omp-son OrsquoReilly Media

bull What Is Node July 2011 Brett McLaughlin OrsquoReilly Media

bull Node Up and Running May 2011 Tom Hughes-Croucher Mike WilsonOrsquoReilly Media

83

Nodejs中文電子書

bull Hands-on Nodejs e Nodejs introduction and API reference May 2011 Pe-dro Teixeira LeanPub

bull e Node Beginner Book Manuel Kiessling

bull Mastering Nodejs visionmedia

112 Nodejs影音教學

bull e Node Sessions e Best of OSCON 2011 August 2011 OrsquoReilly Media(Video)

bull Tom Hughes-Croucher on Node March 2011 OrsquoReilly Media (Video)

113 Nodejs教學網站

bull Node Tuts

114 Nodejs課程

bull Introduction To Nodejs Van Nguyen CodeLesson

84 11 參考資源

Page 59: Nodejs Wiki

Nodejs中文電子書

如上面的程式碼所表示appget可以帶入兩個參數第一個是路徑名稱設定第二個為回應函式 (call back function)回應函式裡面就如同之前的 createServer方法裡面包含 requestresponse兩個物件可供使用使用者就可以透過瀏覽器輸入不同的 url切換到不同的頁面顯示不同的結果

路由設定上也有基本的配對方式讓使用者從瀏覽器輸入的網址可以

是一個變數只要符合型態就可以有對應的頁面產出例如

Create http server

appget(rsquouseridrsquo function(req res)

ressend(rsquouser rsquo + reqparamsid)

)

appget(rsquonumberrsquo function(req res)

ressend(rsquonumber rsquo + reqparamsnumber)

)

裡 面 使 用 到number 從 網 址 輸 入 之 後 就 可 以 直 接 使 用reqparamsnumber 取得所輸入的資料變成 url 參數使用當然前面也是可以加上路徑的設定userid在瀏覽器上路徑必須符合userxxx透

過 reqparamsid就可以取到 xxx這個字串值

另外express參數處理也提供了路由參數配對處理也可以透過正規表示法作為參數設定

var app = require(rsquoexpressrsquo)createServer()

port = 1337

applisten(port)

appget(^ip((d23)((d23))((d23))((d23))) function(req res)

ressend(reqparams)

)

上面程式碼可以發現後面路由設定的型態是正規表示法裡面設定

格式為ip之後必須要加上 ip型態才會符合資料格式同時取得 ip資料已經由正規表示法將資料做分群因此可以取得 ip的四個數字

此程式執行之後可以透過瀏覽器測試輸入網址為 localhost3000ip25525510010可以從頁面獲得資料

63 Express路由處理 53

Nodejs中文電子書

此章節全部範例程式碼如下

overview

author Caesar Chi

blog clonnblogspotcom

version 20120226

create server

var app = require(rsquoexpressrsquo)createServer()

port = 1337

applisten(port)

normal style

appget(rsquorsquo function(req res)

ressend(rsquohello worldrsquo)

)

appget(rsquotestrsquo function(req res)

ressend(rsquotest renderrsquo)

)

parameter style

appget(rsquouseridrsquo function(req res)

ressend(rsquouser rsquo + reqparamsid)

)

appget(rsquonumberrsquo function(req res)

ressend(rsquonumber rsquo + reqparamsnumber)

)

REGX style

appget(^ip((d23)((d23))((d23))) function(req res)

ressend(reqparams)

)

appget(rsquorsquo function(req res)

ressend(rsquoPage not foundrsquo 404)

54 6 Express介紹

Nodejs中文電子書

)

consolelog(rsquostart express servernrsquo)

64 Express middleware

Express 裡面有一個十分好用的應用概念稱為 middleware可以透過middleware做出複雜的效果同時上面也有介紹 next方法參數傳遞就是靠 middleware的概念來傳遞參數讓開發者可以明確的控制程式邏輯

create http server

appuse(expressbodyParser())

appuse(expressmethodOverride())

appuse(expresssession())

上面都是一種 middleware的使用方式透過 appuse方式裡面載入函式執行方法回應函式會包含三個基本參數responserequestnext其中next表示下一個 middleware執行函式同時會自動將預設三個參數繼續帶往下個函式執行底下有個實驗

上面的片段程式執行後開啟瀏覽器連結上 localhost1337會發現伺服器回應結果順序如下

first middle ware

second middle ware

execute middle ware

end middleware function

從上面的結果可以得知剛才設定的 middleware都生效了在 appuse設定的 middleware是所有 url皆會執行方法如果有指定特定方法就可以使用 appget的 middleware設定在 appget函式的第二個參數就可以帶入函式或者是匿名函式只要函式裡面最後會接受 request response next這三個參數同時也有正確指定 next函式的執行時機最後都會執行到最後一個方法當然開發者也可以評估程式邏輯要執行到哪一個階段讓邏輯

可以更為分明

64 Express middleware 55

Nodejs中文電子書

65 Express路由應用

在實際開發上可能會遇到需要使用參數等方式混和變數一起使用

express裡面提供了一個很棒的處理方法 appall這個方式可以先採用基本路由配對再將設定為每個不同的處理方式開發者可以透過這個方式簡

化自己的程式邏輯

overview

author

version 20120226

create server

var app = require(rsquoexpressrsquo)createServer()

port = 1337

users = [

name rsquoClonnrsquo

name rsquoChirsquo

]

applisten(port)

appall(rsquouseridoprsquo function(req res next)

requser = users[reqparamsid]

if (requser)

next()

else

next(new Error(rsquocannot find user rsquo + reqparamsid))

)

appget(rsquouseridrsquo function(req res)

ressend(rsquoviewing rsquo + requsername)

)

appget(rsquouserideditrsquo function(req res)

ressend(rsquoediting rsquo + requsername)

)

56 6 Express介紹

Nodejs中文電子書

appget(rsquouseriddeletersquo function(req res)

ressend(rsquodeleting rsquo + requsername)

)

appget(rsquorsquo function(req res)

ressend(rsquoPage not foundrsquo 404)

)

consolelog(rsquostart express servernrsquo)

內部宣告一組預設的使用者分別給予名稱設定藉由 appall這個方法可以先將路由雛形建立再接下來設定 appget的路徑格式只要符合格式就會分配進入對應的方法中像上面的程式當中如果使用者輸入路徑為user0除了執行 appall程式之後執行 next方法就會對應到路徑設定為userid的這個方法當中如果使用者輸入路徑為user0edit就會執行到useridedit的對應方法

66 Express GET應用範例

我們準備一個使用 GET方法傳送資料的表單

ltDOCTYPE htmlgt

lthtmlgt

ltheadgt

ltmeta http-equiv=rdquoContent-Typerdquo content=rdquotexthtml charset=utf8rdquogt

lttitlegtNodejs 菜鳥筆記 (1)lttitlegt

ltlink rel=rdquostylesheetrdquo href=rdquocssstylecssrdquo type=rdquotextcssrdquo media=rdquoallrdquo gt

ltheadgt

ltbodygt

lth1gtNodejs 菜鳥筆記-註冊lth1gt

ltform id=rdquosignuprdquo method=rdquoGETrdquo action=rdquohttplocalhost3000Signuprdquogt

ltlabelgt 使用者名稱ltlabelgtltinput type=rdquotextrdquo id=rdquousernamerdquo name=rdquousernamerdquo gtltbrgt

ltlabelgt 電子郵件ltlabelgtltinput type=rdquotextrdquo id=rdquoemailrdquo name=rdquoemailrdquo gtltbrgt

ltinput type=rdquosubmitrdquo value=rdquo 註冊我的帳號rdquo gtltbrgt

ltformgt

ltbodygt

lthtmlgt

66 Express GET應用範例 57

Nodejs中文電子書

這個表單沒有什麼特別的地方我們只需要看第 9 行form 使用的method是 GET然後 action是rdquohttplocalhost3000Signupldquo等一下我們要來撰寫Signup這個 URL Path的處理程式

處理 Signup行為

我們知道所謂的 GET方法會透過 URL來把表單的值給帶過去以上面的表單來說到時候 URL會以這樣的形式傳遞

httplocalhost3000Signupusername=xxxampemail=xxx

所以要能處理這樣的資料必須有以下功能

bull 解析 URL

bull 辨別動作是 Signup

bull 解析出 username和 email

一旦能取得 username和 email的值程式就能加以應用了

處理 Signup的程式碼雛形

load module

var url = require(rsquourlrsquo)

urlData = urlparse(requrltrue)

action = urlDatapathname

reswriteHead(200 rdquoContent-Typerdquordquotexthtml charset=utf-8rdquo)

if (action === rdquoSignuprdquo)

user = urlDataquery

resend(rdquolth1gtrdquo + userusername + rdquo 歡迎您的加入 lth1gtltpgt 我們已經將會員啟用信寄至rdquo + useremail + rdquoltpgtrdquo)

首先需要加載 url module它是用來協助我們解析 URL的模組接著使用 urlparse方法第一個傳入 url字串變數也就是 requrl另外第二個參數的用意是設為 ture則引進 querystring模組來協助處理預設是 false它影響到的是 urlDataquery設為 true會傳回物件不然就只是一般的字串urlparse會將字串內容整理成一個物件我們把它指定給 urlData

action變數作為記錄 pathname這是我們稍後要來判斷目前網頁的動作是什麼接著先將 html表頭資訊 (Header)準備好再來判斷路徑邏輯如

58 6 Express介紹

Nodejs中文電子書

果是 Signup這個動作就把 urlDataquery裡的資料指定給 user然後輸出userusername和 useremail把使用者從表單註冊的資料顯示於頁面中

最後進行程式測試啟動 nodejs主程式之後開啟瀏覽器就會看到表單填寫完畢按下送出就可以看到結果了

完整 nodejs程式碼如下

var http = require(rsquohttprsquo)

url = require(rsquourlrsquo)

fs = require(rdquofsrdquo)

server

server = httpcreateServer(function (reqres)

var urlData

encode = rdquoutf8rdquo

filePath = rdquoviewexpress_get_example_formhtmlrdquo

action

urlData = urlparse(requrltrue)

action = urlDatapathname

reswriteHead(200 rdquoContent-Typerdquordquotexthtml charset=utf-8rdquo)

if (action === rdquoSignuprdquo)

user = urlDataquery

resend(rdquolth1gtrdquo + userusername + rdquo 歡迎您的加入 lth1gtltpgt 我們已經將會員啟用信寄至rdquo + useremail + rdquoltpgtrdquo)

else

fsreadFile(filePath encode function(err file)

reswrite(file)

resend()

)

)

serverlisten(3000)

consolelog(rsquoServer 跑起來了現在時間是rsquo + new Date())

66 Express GET應用範例 59

Nodejs中文電子書

67 Express POST應用範例

一開始準備基本的 html表單傳送內容以 POST方式form的 action屬性設定為 POST其餘 html內容與前一個範例應用相同

ltDOCTYPE htmlgt

lthtmlgt

ltheadgt

ltmeta http-equiv=rdquoContent-Typerdquo content=rdquotexthtml charset=utf8rdquogt

lttitlegtNodejs 菜鳥筆記 (1)lttitlegt

ltlink rel=rdquostylesheetrdquo href=rdquocssstylecssrdquo type=rdquotextcssrdquo media=rdquoallrdquo gt

ltheadgt

ltbodygt

lth1gtNodejs 菜鳥筆記-註冊lth1gt

ltform id=rdquosignuprdquo method=rdquoPOSTrdquo action=rdquohttplocalhost3000Signuprdquogt

ltlabelgt 使用者名稱ltlabelgtltinput type=rdquotextrdquo id=rdquousernamerdquo name=rdquousernamerdquo gtltbrgt

ltlabelgt 電子郵件ltlabelgtltinput type=rdquotextrdquo id=rdquoemailrdquo name=rdquoemailrdquo gtltbrgt

ltinput type=rdquosubmitrdquo value=rdquo 註冊我的帳號rdquo gtltbrgt

ltformgt

ltbodygt

lthtmlgt

nodejs的程式處理邏輯與前面 GET範例類似部分程式碼如下

qs = require(rsquoquerystringrsquo)

if (action === rdquoSignuprdquo)

formData = rsquorsquo

reqon(rdquodatardquo function (data)

formData += data

)

reqon(rdquoendrdquo function ()

user = qsparse(formData)

resend(rdquolth1gtrdquo + userusername + rdquo 歡迎您的加入 lth1gtltpgt 我們已經將會員啟用信寄至rdquo + useremail + rdquoltpgtrdquo)

)

60 6 Express介紹

Nodejs中文電子書

主要加入了rsquoquerystringrsquo這個moduel方便我們等一下解析由表單 POST回來的資料另外加入一個 formData的變數用來搜集待等一下表單回傳的資料前面的 GET範例我們只從 req拿出 url的資料這次要在利用req身上的事件處理

JavaScript 在訂閱事件時使用 addEventListener而 nodejs 使用的則是on這邊加上了監聽 data的事件會在瀏覽器傳送資料到Web Server時被執行參數是它所接收到的資料型態是字串

接著再增加 end的事件當瀏覽器的請求事件結束時它就會動作

由於瀏覽器使用 POST在上傳資料時會將資料一塊塊地上傳因為我們在監聽 data事件時透過 formData變數將它累加起來lt不過由於我們

上傳的資料很少一次就結束不過如果日後需要傳的是資料比較大的檔

案這個累加動作就很重要

當資料傳完就進到 end 事件中會用到 qsparse 來解析 formDataformData的內容是字串內容是

username=wordsmithampemail=wordsmith40somewhere

而 qsparse可以幫我們把這個 querystring轉成物件的格式也就是

username=wordsmithampemail=wordsmith40somewhere

一旦轉成物件並指定給 user之後其他的事情就和 GET方法時操作的一樣寫 response的表頭將內容回傳並將 userusername和 useremail代入到內容中

修改完成後接著執行 nodejs程式啟動 web server開啟瀏覽器進入表單測試看看POST的方式能否順利運作

完整程式碼如下

var http = require(rsquohttprsquo)

url = require(rsquourlrsquo)

fs = require(rdquofsrdquo)

qs = require(rsquoquerystringrsquo)

server

server = httpcreateServer(function (reqres)

var urlData

67 Express POST應用範例 61

Nodejs中文電子書

encode = rdquoutf8rdquo

filePath = rdquoviewexpress_post_example_formhtmlrdquo

formData

action

urlData = urlparse(requrltrue)

action = urlDatapathname

reswriteHead(200 rdquoContent-Typerdquordquotexthtml charset=utf-8rdquo)

if (action === rdquoSignuprdquo)

formData = rsquorsquo

reqon(rdquodatardquo function (data)

formData += data

)

reqon(rdquoendrdquo function ()

user = qsparse(formData)

resend(rdquolth1gtrdquo + userusername + rdquo 歡迎您的加入 lth1gtltpgt 我們已經將會員啟用信寄至rdquo + useremail + rdquoltpgtrdquo)

)

else

fsreadFile(filePath encode function(err file)

reswrite(file)

resend()

)

)

serverlisten(3000)

consolelog(rsquoServer 跑起來了現在時間是rsquo + new Date())

62 6 Express介紹

Nodejs中文電子書

68 Express AJAX應用範例

在 Nodejs要使用 Ajax傳送資料並且與之互動在接受資料的部份沒有太大的差別client端不是用 GET就是用 POST來傳資料重點在處理完後用 JSON格式回傳當然 Ajax不見得只傳 JSON格式有時是回傳一段 HTML碼不過後者對伺服器來說基本上就和前兩篇沒有差別了所以我們還是以回傳 JSON做為這一回的主題

這一回其實大多數的工作都會落在前端 Ajax上面前端要負責發送與接收資料並在接收資料後撤掉原先發送資料的表單並將取得的資料

改成 HTML格式之後放上頁面

首先先準備 HTML靜態頁面資料

ltDOCTYPE htmlgt

lthtmlgt

ltheadgt

lttitlegtNodejs 菜鳥筆記 (3)lttitlegt

ltlink rel=rdquostylesheetrdquo href=rdquocssstylecssrdquo type=rdquotextcssrdquo media=rdquoallrdquo gt

ltscript src=rdquohttpsajaxgoogleapiscomajaxlibsjquery171jqueryminjsrdquogtltscriptgt

ltheadgt

ltbodygt

lth1gtNodejs 菜鳥筆記-註冊 (用 Ajax)lth1gt

ltform id=rdquosignuprdquo action=rdquoSignuprdquo method=rdquoPOSTrdquogt

ltlabelgt 使用者名稱ltlabelgtltinput type=rdquotextrdquo id=rdquousernamerdquo name=rdquousernamerdquo gtltbrgt

ltlabelgt 電子郵件ltlabelgtltinput type=rdquotextrdquo id=rdquoemailrdquo name=rdquoemailrdquo gtltbrgt

ltinput type=rdquosubmitrdquo value=rdquo 註冊我的帳號rdquo gtltbrgt

ltformgt

ltdiv id=rdquoresultrdquogt

ltdivgt

ltscript type=rdquotextjavascriptrdquogt

$(function()$(rsquosignup input[type=rdquosubmitrdquo]rsquo)click(function(e)

var user =

userusername = $(rdquousernamerdquo)val()useremail = $(rdquoemailrdquo)val()

$post(rdquoSignuprdquouserfunction(data)greet(data)

)

68 Express AJAX應用範例 63

Nodejs中文電子書

epreventDefault()

)

var greet = function(msg)

var resultNode = $(rsquoresultrsquo)greeting = $(rsquolth2gtHirsquo+msgusername+rsquo 你的會員 id 是rsquo+ msgid +rsquo 我們會將會員啟動信寄至rsquo+msgemail+rsquolth2gtrsquo)

$(rsquosignuprsquo)hide()resultNodehtml(rdquordquo)

resultNodeappend(greeting)

)

ltscriptgt

ltbodygt

lthtmlgt

HTML頁面上準備了一個表單用來傳送註冊資料接著直接引用了Google CDN來載入 jQuery用來幫我們處理 Ajax的工作這次要傳送和接收的工作很大的變動都在 HTML頁面上的 JavaScript當中我們要做的事有 (相關 jQuery處理這邊不多做贅述指提起主要功能解說)

bull 用 jQUery取得 submit按鈕綁定它的 click動作

bull 取得表單 username和 email的值存放在 user這個物件中

bull 用 jQuery的 $post方法將 user的資料傳到 Server

bull 一旦成功取得資料後透過 greet這個 function組成回報給 user的訊息

bull 清空原本給使用者填資料的表單

bull 將 Server回傳的 usernameemail和 id這 3個資料組成回應的訊息

bull 將訊息放到原本表單的位置

經過以上的處理後一個 Ajax的表單的基本功能已經完成

接著進行 nodejs主要程式的編輯部分程式碼如下

var fs = require(rdquofsrdquo)

64 6 Express介紹

Nodejs中文電子書

qs = require(rsquoquerystringrsquo)

if (action === rdquoSignuprdquo)

formData = rsquorsquo

reqon(rdquodatardquo function (data)

formData += data

)

reqon(rdquoendrdquo function ()

var msg

user = qsparse(formData)

userid = rdquo123456rdquo

msg = JSONstringify(user)

reswriteHead(200 rdquoContent-Typerdquordquoapplicationjson charset=utf-8rdquordquoContent-Lengthrdquomsglength)

resend(msg)

)

這裡的程式和前面 POST範例基本上大同小異差別在

bull 幫 user的資料加上 id隨意存放一些文字進去讓 Server回傳的資料多於 Client端傳上來的不然會覺得 Server都沒做事

bull 增加了 msg 這個變數存放將 user 物件 JSON 文字化的結果JSONstringify這個轉換函式是 V8引擎所提供的如果你好奇的話

bull 大重點來了我們要告訴 Client端這次回傳的資料格式是 JSON所在 Content-type和 Content-Length要提供給 Client

Server很輕鬆就完成任務了最後進行程式測試啟動 nodejs主程式之後開啟瀏覽器就會看到表單填寫完畢按下送出就可以看到結果了

最後 nodejs本篇範例程式碼如下

var http = require(rsquohttprsquo)

url = require(rsquourlrsquo)

fs = require(rdquofsrdquo)

qs = require(rsquoquerystringrsquo)

server

68 Express AJAX應用範例 65

Nodejs中文電子書

server = httpcreateServer(function (reqres)

var urlData

encode = rdquoutf8rdquo

filePath = rdquoviewexpress_ajax_example_formhtmlrdquo

formData

action

urlData = urlparse(requrltrue)

action = urlDatapathname

if (action === rdquoSignuprdquo)

formData = rsquorsquo

reqon(rdquodatardquo function (data)

formData += data

)

reqon(rdquoendrdquo function ()

var msg

user = qsparse(formData)

userid = rdquo123456rdquo

msg = JSONstringify(user)

reswriteHead(200 rdquoContent-TyperdquordquoapplicationjsonrdquordquoContent-Lengthrdquomsglength)

resend(msg)

)

else

fsreadFile(filePath encode function(err file)

reswriteHead(200 rdquoContent-Typerdquordquotexthtml charset=utf-8rdquo)

reswrite(file)

resend()

)

)

serverlisten(3000)

66 6 Express介紹

Nodejs中文電子書

consolelog(rsquoServer 跑起來了現在時間是rsquo + new Date())

69 原始資料提供

bull [NodeJS初學者筆記 (1)-用 GET傳送資料] (httpithelpithomecomtwquestion10087402)

bull [NodeJS初學者筆記 (2)-用 POST傳送資料] (httpithelpithomecomtwquestion10087489)

bull [NodeJS 初學者筆記 (3)-用 Ajax 傳送資料] (httpithelpithomecomtwquestion10087627)

69 原始資料提供 67

Nodejs中文電子書

68 6 Express介紹

7

CoffeeScript

bull Spine

bull Hem

bull Spineapp

Letrsquos Have a Cup of CoffeeScript

bull es5-shim ECMAScript 5 compatibility shims for legacy JavaScript engines

ESnext

node-iform - A middleware for node-validator httpsgithubcomguileennode-iform

69

Nodejs中文電子書

70 7 CoffeeScript

8

製作一個 Hubot的 Plurk Adapter

81 應用事項提醒

此應用範例以CoffeeScript編寫主要程式架構採用Github釋放的Hubot專案以下有幾個主要注意事項請讀者注意

bull Hubot 一開始的架構除了內建的兩個 Adapter 之外其餘都要以Nodejs的Module方式才能運作

bull Hubot的 binhubot裡面寫著 npm install所以不管你怎麼改原始碼也不能改變第一點的狀況

其實上述都在討論同一件事情NodeJS的模組而且模組不能設定相對路徑之類的來安裝一定要包裝成 npm相符和格式才有辦法正確執行

82 建立 Adapter

首先需要準備兩個檔案

bull packagejson

bull plurkcoffee

71

Nodejs中文電子書

有這兩個就足以變成 NodeJS的模組了在開始 Coding之前先來設定一下 packagejson相依性

rdquonamerdquo rdquohubot-plurkrdquo

rdquoversionrdquo rdquo011rdquo

rdquomainrdquo rdquoplurkrdquo

rdquodependenciesrdquo

rdquohubotrdquo rdquogt=205rdquo

rdquooauthrdquo rdquordquo

rdquocronrdquordquordquo

這邊會使用到NodeJS的 cron模組(Module)而登入 Plurk需要OAuth標準授權才行所以也需要加載 OAuth模組

注意Hubot在讀取非內建模組時會自動在前面加上 hubot-的前置

83 建立 Robot跟 API

本篇應用基本上程式碼大部分參考 Hubot - Twitter Adapter來製作主要差異只有在使用 OAuth這個模組Twitter有 Streaming可用而 Plurk則得用 Comet方式來達到即時讀取

plurkcoffee程式碼

Robot = require(rdquohubotrdquo)robot()

Adapter = require(rdquohubtordquo)adapter()

EventEmitter = require(rdquoeventsrdquo)EventEmitter

oauth = require(rdquooauthrdquo)

cronJob = require(rdquocronrdquo)CronJob

class Plurk exntends Adapter

class PlurkStreaming exnteds EventEmitter

72 8 製作一個 Hubot的 Plurk Adapter

Nodejs中文電子書

先弄個基本架構主要 Class皆參考 Twitter Adapter命名方式接著再將 Plurk這個 Class增加幾個Method基本上只要有 run send reply就夠了而 run用來做初始化的部分

class Plurk entends Adapter

send (plurk id strings⋯) -gt

reply (plurk id strings⋯) -gt

run -gt

看起來有點東西接著來處理主要的 Plurk API結合部分

class PlurkStreaming extends EventEmitter

consuctor (options) -gt

plurk (callback) -gt

觀察河道

getChannel -gt

取得 Comet 網址

reply (plurk id message) -gt

回噗

acceptFriends -gt

接受好友

get (path callback) -gt

GET 請求

post (path body callback)-gt

POST 請求(其實是裝飾)

request (method path body callback)-gt

主要的 OAuth 請求

comet (server callback)-gt

噗浪的 Comet 傳回是 JavaScript Callback 要另外處理後才會變成 JSON

然後把注意力集中到 constructor上先把建構子弄好

constructor (options) -gt

super()

if optionskey and optionssecret and optionstoken and optionstoken secret

key = optionskey

secret = optionssecret

token = optionstoken

token secret = optionstoken secret

83 建立 Robot跟 API 73

Nodejs中文電子書

建立 OAuth 連接

consumer = new oauthOAuth(

rdquohttpwwwplurkcomOAuthrequest tokenrdquo

rdquohttpwwwplurkcomOAuthaccess tokenrdquo

key

secret

rdquo10rdquo

rdquohttpwwwplurkcomOAuthauthorizerdquo

rdquoHMAC-SHA1rdquo

)

domain = rdquowwwplurkcomrdquo

初始化取得 Comet 網址

do getChannel

else

throw new Error(rdquo 參數不足需要 Key Secret Token Token Secretrdquo)

接著來處理 request這個 method

request (method path body callback) -gt

記錄一下這次的 Request

consolelog(rdquohttpdomainpathrdquo)

Callback 這邊先不丟進去要用另一種方式處理

request = consumerget(rdquohttpdomainpathrdquo token token secret null)

requeston rdquoresponserdquo (res) -gt

reson rdquodatardquo (chunk) -gt

parseResponse(chunk+rsquorsquo callback)

reson rdquoendrdquo (data) -gt

consolelog rdquoEnd Request pathrdquo

reson rdquoerrorrdquo (data) -gt

consolelog rdquoError rdquo + data

requestend()

處理資料

parseResponse = (data callback) -gt

if datalength gt 0

用 TryCatch 避免處理 JSON 出錯導致整個中斷

try

callback null JSONparse(data)

catch err

74 8 製作一個 Hubot的 Plurk Adapter

Nodejs中文電子書

consolelog(rdquoError Parse JSONrdquo + data err)

繼續執行

callback null data ||

大致上就是這樣上面程式的架構已經將整個 Hubot Plurk Adapter完成因為在測試時竟然因為噗浪 Lag而沒讀到完整的 Comet資料然後造成程式異常為了避免這個問題發生因此需要加上為了完美呈現需要再

加上 Comet的處理所以要使用到 EventEmitter的功能

comet (server callback) -gt

在 Callback 裡面會找不到自身所以設定區域變數

self =

記錄一下這次的 Request

consolelog(rdquo[Comet] serverrdquo)

Callback 這邊先不丟進去要用另一種方式處理

request = consumerget(rdquohttpdomainpathrdquo token token secret null)

requeston rdquoresponserdquo (res) -gt

reson rdquodatardquo (chunk) -gt

parseResponse(chunk+rsquorsquo callback)

reson rdquoendrdquo (data) -gt

consolelog rdquoEnd Request pathrdquo

請求結束發出事件通知可以進行下一次請求

selfemit rdquonextPlurkrdquo

reson rdquoerrorrdquo (data) -gt

consolelog rdquoError rdquo + data

requestend()

處理資料

parseResponse = (data callback) -gt

if datalength gt 0

用 trycatch 避免失敗中斷

try

去掉 JavaScript 的 Callback

data = datamatch(CometChannelscriptCallback((+))s)

jsonData = rdquordquo

if data

83 建立 Robot跟 API 75

Nodejs中文電子書

jsonData = JSONparse(data[1])

else

如果沒有任何 Match 嘗試直接 parse

jsonData = JSONparse(data)

catch err

consolelog(rdquo[Comet] Errorrdquo data err)

用 TryCatch 避免處理 JSON 出錯導致整個中斷

try

只傳入 json 的 data 部分

callback null jsonDatadata

catch err

consolelog(rdquo[Comet]Error Parse JSONrdquo + data err)

繼續執行

callback null data ||

後面的 get跟 post就簡單多了

get (path callback) -gt

request(rdquoGETrdquo path null callback)

post (path body callback) -gt

request(rdquoPOSTrdquo path body callback)

接著處理取的 Comet網址的 getChannel

getChannel -gt

self =

get rdquoAPPRealtimegetUserChannelrdquo (error data) -gt

if error

檢查是否有 comet server

if datacomet server

selfchannel = datacomet server

如果沒有 Channel Ready 就嘗試連接會失敗

selfemit(rsquochannel readyrsquo)

那麼先來處理 Plurk Adaper好處理的部份

send (plruk id strings⋯)-gt

跟 Reply 一樣直接交給 reply 做

reply plurk id strings⋯

76 8 製作一個 Hubot的 Plurk Adapter

Nodejs中文電子書

reply (plurk id strings⋯) -gt

stringsforEach (message) =gt

botreply(plruk id message)

接著把 run處理好就可以上線運作摟

run -gt

self =

options =

key processenvHUBOT PLURK KEY

secret processenvHUBOT PLURK SECRET

token processenvHUBOT PLURK TOKEN

token secret processenvHUBOT PLURK TOKEN SECRET

創建剛剛的 API

bot = new PlurkStreaming(options)

依照 Twitter 的 new RobotTextMessage 會沒有反應所以參考 hubot-minecraft 的方式

r = robotconstructor

處理噗浪河道訊息

doPlurk = (data)-gt

檢查是否為回噗

if dataresponse

datacontent raw = dataresponsecontent raw

datauser id = dataresponseuser id

確定有噗浪 ID 跟訊息

if dataplurk id and datacontent raw

selfreceive new rTextMessage(dataplurk id datacontent raw)

取得 Comet Server 完成開始第一次 Comet 連接

boton rdquochannel readyrdquo () -gt

botplurk selfdoPlurk

上一次 Comet 完成繼續 Polling

boton rdquonextPlurkrdquo ()-gt

botplurk selfdoPlurk

定時接受好友邀請

do botacceptFriends

bot = bot

83 建立 Robot跟 API 77

Nodejs中文電子書

終於完成 AdapterHubot專案裡面的 scripts資料夾內是互動部分不需要像 Adapter如此大費周章處理只新增檔案並且設計好對白之後就會回噗了我開發用的機器人在此大家可以去跟他玩玩[httpplurkcomelct9620 bot](httpplurkcomelct9620 bot)

84 原始資料提供

bull [製 作 一 個 Hubot 的 噗 浪 Adapter](http revo-skillfrosttw blog20120318create-a-hubot-plurk-adapter)

78 8 製作一個 Hubot的 Plurk Adapter

9

Nodejs好用工具介紹

91 logio

Real-time log monitoring in your browser

httplogioorg

nodejs (gt=0412 lt=0611)

npm config set unsafe-perm true

npm install -g --prefix=usrlocal logio

logio server start

httplocalhost8998

79

Nodejs中文電子書

80 9 Nodejs好用工具介紹

10

精選文章

101 Express

bull 使用 NodeJS + Express從 GETPOST Request取值 [邀稿中]

bull link2 [邀稿中]

bull link3 [邀稿中]

81

Nodejs中文電子書

82 10 精選文章

11

參考資源

111 Nodejs書籍

bull Programming Nodejs April 2012 (est) Travis Swicegood Pragmatic Bookshelf

bull e Little Book on CoffeeScript January 2012 Alex MacCaw OrsquoReilly Media

bull Node for Front-End Developers January 2012 Garann Means OrsquoReilly Media

bull Building Hypermedia APIs with HTML5 and Node November 2011 MikeAmundsen OrsquoReilly Media

bull Node Web Development August 2011 David Herron PacktPub

bull CoffeeScript Accelerated JavaScript Development July 2011 Trevor BurnhamPragmatic Bookshelf

bull Getting Started with GEO CouchDB and Nodejs July 2011 Mick omp-son OrsquoReilly Media

bull What Is Node July 2011 Brett McLaughlin OrsquoReilly Media

bull Node Up and Running May 2011 Tom Hughes-Croucher Mike WilsonOrsquoReilly Media

83

Nodejs中文電子書

bull Hands-on Nodejs e Nodejs introduction and API reference May 2011 Pe-dro Teixeira LeanPub

bull e Node Beginner Book Manuel Kiessling

bull Mastering Nodejs visionmedia

112 Nodejs影音教學

bull e Node Sessions e Best of OSCON 2011 August 2011 OrsquoReilly Media(Video)

bull Tom Hughes-Croucher on Node March 2011 OrsquoReilly Media (Video)

113 Nodejs教學網站

bull Node Tuts

114 Nodejs課程

bull Introduction To Nodejs Van Nguyen CodeLesson

84 11 參考資源

Page 60: Nodejs Wiki

Nodejs中文電子書

此章節全部範例程式碼如下

overview

author Caesar Chi

blog clonnblogspotcom

version 20120226

create server

var app = require(rsquoexpressrsquo)createServer()

port = 1337

applisten(port)

normal style

appget(rsquorsquo function(req res)

ressend(rsquohello worldrsquo)

)

appget(rsquotestrsquo function(req res)

ressend(rsquotest renderrsquo)

)

parameter style

appget(rsquouseridrsquo function(req res)

ressend(rsquouser rsquo + reqparamsid)

)

appget(rsquonumberrsquo function(req res)

ressend(rsquonumber rsquo + reqparamsnumber)

)

REGX style

appget(^ip((d23)((d23))((d23))) function(req res)

ressend(reqparams)

)

appget(rsquorsquo function(req res)

ressend(rsquoPage not foundrsquo 404)

54 6 Express介紹

Nodejs中文電子書

)

consolelog(rsquostart express servernrsquo)

64 Express middleware

Express 裡面有一個十分好用的應用概念稱為 middleware可以透過middleware做出複雜的效果同時上面也有介紹 next方法參數傳遞就是靠 middleware的概念來傳遞參數讓開發者可以明確的控制程式邏輯

create http server

appuse(expressbodyParser())

appuse(expressmethodOverride())

appuse(expresssession())

上面都是一種 middleware的使用方式透過 appuse方式裡面載入函式執行方法回應函式會包含三個基本參數responserequestnext其中next表示下一個 middleware執行函式同時會自動將預設三個參數繼續帶往下個函式執行底下有個實驗

上面的片段程式執行後開啟瀏覽器連結上 localhost1337會發現伺服器回應結果順序如下

first middle ware

second middle ware

execute middle ware

end middleware function

從上面的結果可以得知剛才設定的 middleware都生效了在 appuse設定的 middleware是所有 url皆會執行方法如果有指定特定方法就可以使用 appget的 middleware設定在 appget函式的第二個參數就可以帶入函式或者是匿名函式只要函式裡面最後會接受 request response next這三個參數同時也有正確指定 next函式的執行時機最後都會執行到最後一個方法當然開發者也可以評估程式邏輯要執行到哪一個階段讓邏輯

可以更為分明

64 Express middleware 55

Nodejs中文電子書

65 Express路由應用

在實際開發上可能會遇到需要使用參數等方式混和變數一起使用

express裡面提供了一個很棒的處理方法 appall這個方式可以先採用基本路由配對再將設定為每個不同的處理方式開發者可以透過這個方式簡

化自己的程式邏輯

overview

author

version 20120226

create server

var app = require(rsquoexpressrsquo)createServer()

port = 1337

users = [

name rsquoClonnrsquo

name rsquoChirsquo

]

applisten(port)

appall(rsquouseridoprsquo function(req res next)

requser = users[reqparamsid]

if (requser)

next()

else

next(new Error(rsquocannot find user rsquo + reqparamsid))

)

appget(rsquouseridrsquo function(req res)

ressend(rsquoviewing rsquo + requsername)

)

appget(rsquouserideditrsquo function(req res)

ressend(rsquoediting rsquo + requsername)

)

56 6 Express介紹

Nodejs中文電子書

appget(rsquouseriddeletersquo function(req res)

ressend(rsquodeleting rsquo + requsername)

)

appget(rsquorsquo function(req res)

ressend(rsquoPage not foundrsquo 404)

)

consolelog(rsquostart express servernrsquo)

內部宣告一組預設的使用者分別給予名稱設定藉由 appall這個方法可以先將路由雛形建立再接下來設定 appget的路徑格式只要符合格式就會分配進入對應的方法中像上面的程式當中如果使用者輸入路徑為user0除了執行 appall程式之後執行 next方法就會對應到路徑設定為userid的這個方法當中如果使用者輸入路徑為user0edit就會執行到useridedit的對應方法

66 Express GET應用範例

我們準備一個使用 GET方法傳送資料的表單

ltDOCTYPE htmlgt

lthtmlgt

ltheadgt

ltmeta http-equiv=rdquoContent-Typerdquo content=rdquotexthtml charset=utf8rdquogt

lttitlegtNodejs 菜鳥筆記 (1)lttitlegt

ltlink rel=rdquostylesheetrdquo href=rdquocssstylecssrdquo type=rdquotextcssrdquo media=rdquoallrdquo gt

ltheadgt

ltbodygt

lth1gtNodejs 菜鳥筆記-註冊lth1gt

ltform id=rdquosignuprdquo method=rdquoGETrdquo action=rdquohttplocalhost3000Signuprdquogt

ltlabelgt 使用者名稱ltlabelgtltinput type=rdquotextrdquo id=rdquousernamerdquo name=rdquousernamerdquo gtltbrgt

ltlabelgt 電子郵件ltlabelgtltinput type=rdquotextrdquo id=rdquoemailrdquo name=rdquoemailrdquo gtltbrgt

ltinput type=rdquosubmitrdquo value=rdquo 註冊我的帳號rdquo gtltbrgt

ltformgt

ltbodygt

lthtmlgt

66 Express GET應用範例 57

Nodejs中文電子書

這個表單沒有什麼特別的地方我們只需要看第 9 行form 使用的method是 GET然後 action是rdquohttplocalhost3000Signupldquo等一下我們要來撰寫Signup這個 URL Path的處理程式

處理 Signup行為

我們知道所謂的 GET方法會透過 URL來把表單的值給帶過去以上面的表單來說到時候 URL會以這樣的形式傳遞

httplocalhost3000Signupusername=xxxampemail=xxx

所以要能處理這樣的資料必須有以下功能

bull 解析 URL

bull 辨別動作是 Signup

bull 解析出 username和 email

一旦能取得 username和 email的值程式就能加以應用了

處理 Signup的程式碼雛形

load module

var url = require(rsquourlrsquo)

urlData = urlparse(requrltrue)

action = urlDatapathname

reswriteHead(200 rdquoContent-Typerdquordquotexthtml charset=utf-8rdquo)

if (action === rdquoSignuprdquo)

user = urlDataquery

resend(rdquolth1gtrdquo + userusername + rdquo 歡迎您的加入 lth1gtltpgt 我們已經將會員啟用信寄至rdquo + useremail + rdquoltpgtrdquo)

首先需要加載 url module它是用來協助我們解析 URL的模組接著使用 urlparse方法第一個傳入 url字串變數也就是 requrl另外第二個參數的用意是設為 ture則引進 querystring模組來協助處理預設是 false它影響到的是 urlDataquery設為 true會傳回物件不然就只是一般的字串urlparse會將字串內容整理成一個物件我們把它指定給 urlData

action變數作為記錄 pathname這是我們稍後要來判斷目前網頁的動作是什麼接著先將 html表頭資訊 (Header)準備好再來判斷路徑邏輯如

58 6 Express介紹

Nodejs中文電子書

果是 Signup這個動作就把 urlDataquery裡的資料指定給 user然後輸出userusername和 useremail把使用者從表單註冊的資料顯示於頁面中

最後進行程式測試啟動 nodejs主程式之後開啟瀏覽器就會看到表單填寫完畢按下送出就可以看到結果了

完整 nodejs程式碼如下

var http = require(rsquohttprsquo)

url = require(rsquourlrsquo)

fs = require(rdquofsrdquo)

server

server = httpcreateServer(function (reqres)

var urlData

encode = rdquoutf8rdquo

filePath = rdquoviewexpress_get_example_formhtmlrdquo

action

urlData = urlparse(requrltrue)

action = urlDatapathname

reswriteHead(200 rdquoContent-Typerdquordquotexthtml charset=utf-8rdquo)

if (action === rdquoSignuprdquo)

user = urlDataquery

resend(rdquolth1gtrdquo + userusername + rdquo 歡迎您的加入 lth1gtltpgt 我們已經將會員啟用信寄至rdquo + useremail + rdquoltpgtrdquo)

else

fsreadFile(filePath encode function(err file)

reswrite(file)

resend()

)

)

serverlisten(3000)

consolelog(rsquoServer 跑起來了現在時間是rsquo + new Date())

66 Express GET應用範例 59

Nodejs中文電子書

67 Express POST應用範例

一開始準備基本的 html表單傳送內容以 POST方式form的 action屬性設定為 POST其餘 html內容與前一個範例應用相同

ltDOCTYPE htmlgt

lthtmlgt

ltheadgt

ltmeta http-equiv=rdquoContent-Typerdquo content=rdquotexthtml charset=utf8rdquogt

lttitlegtNodejs 菜鳥筆記 (1)lttitlegt

ltlink rel=rdquostylesheetrdquo href=rdquocssstylecssrdquo type=rdquotextcssrdquo media=rdquoallrdquo gt

ltheadgt

ltbodygt

lth1gtNodejs 菜鳥筆記-註冊lth1gt

ltform id=rdquosignuprdquo method=rdquoPOSTrdquo action=rdquohttplocalhost3000Signuprdquogt

ltlabelgt 使用者名稱ltlabelgtltinput type=rdquotextrdquo id=rdquousernamerdquo name=rdquousernamerdquo gtltbrgt

ltlabelgt 電子郵件ltlabelgtltinput type=rdquotextrdquo id=rdquoemailrdquo name=rdquoemailrdquo gtltbrgt

ltinput type=rdquosubmitrdquo value=rdquo 註冊我的帳號rdquo gtltbrgt

ltformgt

ltbodygt

lthtmlgt

nodejs的程式處理邏輯與前面 GET範例類似部分程式碼如下

qs = require(rsquoquerystringrsquo)

if (action === rdquoSignuprdquo)

formData = rsquorsquo

reqon(rdquodatardquo function (data)

formData += data

)

reqon(rdquoendrdquo function ()

user = qsparse(formData)

resend(rdquolth1gtrdquo + userusername + rdquo 歡迎您的加入 lth1gtltpgt 我們已經將會員啟用信寄至rdquo + useremail + rdquoltpgtrdquo)

)

60 6 Express介紹

Nodejs中文電子書

主要加入了rsquoquerystringrsquo這個moduel方便我們等一下解析由表單 POST回來的資料另外加入一個 formData的變數用來搜集待等一下表單回傳的資料前面的 GET範例我們只從 req拿出 url的資料這次要在利用req身上的事件處理

JavaScript 在訂閱事件時使用 addEventListener而 nodejs 使用的則是on這邊加上了監聽 data的事件會在瀏覽器傳送資料到Web Server時被執行參數是它所接收到的資料型態是字串

接著再增加 end的事件當瀏覽器的請求事件結束時它就會動作

由於瀏覽器使用 POST在上傳資料時會將資料一塊塊地上傳因為我們在監聽 data事件時透過 formData變數將它累加起來lt不過由於我們

上傳的資料很少一次就結束不過如果日後需要傳的是資料比較大的檔

案這個累加動作就很重要

當資料傳完就進到 end 事件中會用到 qsparse 來解析 formDataformData的內容是字串內容是

username=wordsmithampemail=wordsmith40somewhere

而 qsparse可以幫我們把這個 querystring轉成物件的格式也就是

username=wordsmithampemail=wordsmith40somewhere

一旦轉成物件並指定給 user之後其他的事情就和 GET方法時操作的一樣寫 response的表頭將內容回傳並將 userusername和 useremail代入到內容中

修改完成後接著執行 nodejs程式啟動 web server開啟瀏覽器進入表單測試看看POST的方式能否順利運作

完整程式碼如下

var http = require(rsquohttprsquo)

url = require(rsquourlrsquo)

fs = require(rdquofsrdquo)

qs = require(rsquoquerystringrsquo)

server

server = httpcreateServer(function (reqres)

var urlData

67 Express POST應用範例 61

Nodejs中文電子書

encode = rdquoutf8rdquo

filePath = rdquoviewexpress_post_example_formhtmlrdquo

formData

action

urlData = urlparse(requrltrue)

action = urlDatapathname

reswriteHead(200 rdquoContent-Typerdquordquotexthtml charset=utf-8rdquo)

if (action === rdquoSignuprdquo)

formData = rsquorsquo

reqon(rdquodatardquo function (data)

formData += data

)

reqon(rdquoendrdquo function ()

user = qsparse(formData)

resend(rdquolth1gtrdquo + userusername + rdquo 歡迎您的加入 lth1gtltpgt 我們已經將會員啟用信寄至rdquo + useremail + rdquoltpgtrdquo)

)

else

fsreadFile(filePath encode function(err file)

reswrite(file)

resend()

)

)

serverlisten(3000)

consolelog(rsquoServer 跑起來了現在時間是rsquo + new Date())

62 6 Express介紹

Nodejs中文電子書

68 Express AJAX應用範例

在 Nodejs要使用 Ajax傳送資料並且與之互動在接受資料的部份沒有太大的差別client端不是用 GET就是用 POST來傳資料重點在處理完後用 JSON格式回傳當然 Ajax不見得只傳 JSON格式有時是回傳一段 HTML碼不過後者對伺服器來說基本上就和前兩篇沒有差別了所以我們還是以回傳 JSON做為這一回的主題

這一回其實大多數的工作都會落在前端 Ajax上面前端要負責發送與接收資料並在接收資料後撤掉原先發送資料的表單並將取得的資料

改成 HTML格式之後放上頁面

首先先準備 HTML靜態頁面資料

ltDOCTYPE htmlgt

lthtmlgt

ltheadgt

lttitlegtNodejs 菜鳥筆記 (3)lttitlegt

ltlink rel=rdquostylesheetrdquo href=rdquocssstylecssrdquo type=rdquotextcssrdquo media=rdquoallrdquo gt

ltscript src=rdquohttpsajaxgoogleapiscomajaxlibsjquery171jqueryminjsrdquogtltscriptgt

ltheadgt

ltbodygt

lth1gtNodejs 菜鳥筆記-註冊 (用 Ajax)lth1gt

ltform id=rdquosignuprdquo action=rdquoSignuprdquo method=rdquoPOSTrdquogt

ltlabelgt 使用者名稱ltlabelgtltinput type=rdquotextrdquo id=rdquousernamerdquo name=rdquousernamerdquo gtltbrgt

ltlabelgt 電子郵件ltlabelgtltinput type=rdquotextrdquo id=rdquoemailrdquo name=rdquoemailrdquo gtltbrgt

ltinput type=rdquosubmitrdquo value=rdquo 註冊我的帳號rdquo gtltbrgt

ltformgt

ltdiv id=rdquoresultrdquogt

ltdivgt

ltscript type=rdquotextjavascriptrdquogt

$(function()$(rsquosignup input[type=rdquosubmitrdquo]rsquo)click(function(e)

var user =

userusername = $(rdquousernamerdquo)val()useremail = $(rdquoemailrdquo)val()

$post(rdquoSignuprdquouserfunction(data)greet(data)

)

68 Express AJAX應用範例 63

Nodejs中文電子書

epreventDefault()

)

var greet = function(msg)

var resultNode = $(rsquoresultrsquo)greeting = $(rsquolth2gtHirsquo+msgusername+rsquo 你的會員 id 是rsquo+ msgid +rsquo 我們會將會員啟動信寄至rsquo+msgemail+rsquolth2gtrsquo)

$(rsquosignuprsquo)hide()resultNodehtml(rdquordquo)

resultNodeappend(greeting)

)

ltscriptgt

ltbodygt

lthtmlgt

HTML頁面上準備了一個表單用來傳送註冊資料接著直接引用了Google CDN來載入 jQuery用來幫我們處理 Ajax的工作這次要傳送和接收的工作很大的變動都在 HTML頁面上的 JavaScript當中我們要做的事有 (相關 jQuery處理這邊不多做贅述指提起主要功能解說)

bull 用 jQUery取得 submit按鈕綁定它的 click動作

bull 取得表單 username和 email的值存放在 user這個物件中

bull 用 jQuery的 $post方法將 user的資料傳到 Server

bull 一旦成功取得資料後透過 greet這個 function組成回報給 user的訊息

bull 清空原本給使用者填資料的表單

bull 將 Server回傳的 usernameemail和 id這 3個資料組成回應的訊息

bull 將訊息放到原本表單的位置

經過以上的處理後一個 Ajax的表單的基本功能已經完成

接著進行 nodejs主要程式的編輯部分程式碼如下

var fs = require(rdquofsrdquo)

64 6 Express介紹

Nodejs中文電子書

qs = require(rsquoquerystringrsquo)

if (action === rdquoSignuprdquo)

formData = rsquorsquo

reqon(rdquodatardquo function (data)

formData += data

)

reqon(rdquoendrdquo function ()

var msg

user = qsparse(formData)

userid = rdquo123456rdquo

msg = JSONstringify(user)

reswriteHead(200 rdquoContent-Typerdquordquoapplicationjson charset=utf-8rdquordquoContent-Lengthrdquomsglength)

resend(msg)

)

這裡的程式和前面 POST範例基本上大同小異差別在

bull 幫 user的資料加上 id隨意存放一些文字進去讓 Server回傳的資料多於 Client端傳上來的不然會覺得 Server都沒做事

bull 增加了 msg 這個變數存放將 user 物件 JSON 文字化的結果JSONstringify這個轉換函式是 V8引擎所提供的如果你好奇的話

bull 大重點來了我們要告訴 Client端這次回傳的資料格式是 JSON所在 Content-type和 Content-Length要提供給 Client

Server很輕鬆就完成任務了最後進行程式測試啟動 nodejs主程式之後開啟瀏覽器就會看到表單填寫完畢按下送出就可以看到結果了

最後 nodejs本篇範例程式碼如下

var http = require(rsquohttprsquo)

url = require(rsquourlrsquo)

fs = require(rdquofsrdquo)

qs = require(rsquoquerystringrsquo)

server

68 Express AJAX應用範例 65

Nodejs中文電子書

server = httpcreateServer(function (reqres)

var urlData

encode = rdquoutf8rdquo

filePath = rdquoviewexpress_ajax_example_formhtmlrdquo

formData

action

urlData = urlparse(requrltrue)

action = urlDatapathname

if (action === rdquoSignuprdquo)

formData = rsquorsquo

reqon(rdquodatardquo function (data)

formData += data

)

reqon(rdquoendrdquo function ()

var msg

user = qsparse(formData)

userid = rdquo123456rdquo

msg = JSONstringify(user)

reswriteHead(200 rdquoContent-TyperdquordquoapplicationjsonrdquordquoContent-Lengthrdquomsglength)

resend(msg)

)

else

fsreadFile(filePath encode function(err file)

reswriteHead(200 rdquoContent-Typerdquordquotexthtml charset=utf-8rdquo)

reswrite(file)

resend()

)

)

serverlisten(3000)

66 6 Express介紹

Nodejs中文電子書

consolelog(rsquoServer 跑起來了現在時間是rsquo + new Date())

69 原始資料提供

bull [NodeJS初學者筆記 (1)-用 GET傳送資料] (httpithelpithomecomtwquestion10087402)

bull [NodeJS初學者筆記 (2)-用 POST傳送資料] (httpithelpithomecomtwquestion10087489)

bull [NodeJS 初學者筆記 (3)-用 Ajax 傳送資料] (httpithelpithomecomtwquestion10087627)

69 原始資料提供 67

Nodejs中文電子書

68 6 Express介紹

7

CoffeeScript

bull Spine

bull Hem

bull Spineapp

Letrsquos Have a Cup of CoffeeScript

bull es5-shim ECMAScript 5 compatibility shims for legacy JavaScript engines

ESnext

node-iform - A middleware for node-validator httpsgithubcomguileennode-iform

69

Nodejs中文電子書

70 7 CoffeeScript

8

製作一個 Hubot的 Plurk Adapter

81 應用事項提醒

此應用範例以CoffeeScript編寫主要程式架構採用Github釋放的Hubot專案以下有幾個主要注意事項請讀者注意

bull Hubot 一開始的架構除了內建的兩個 Adapter 之外其餘都要以Nodejs的Module方式才能運作

bull Hubot的 binhubot裡面寫著 npm install所以不管你怎麼改原始碼也不能改變第一點的狀況

其實上述都在討論同一件事情NodeJS的模組而且模組不能設定相對路徑之類的來安裝一定要包裝成 npm相符和格式才有辦法正確執行

82 建立 Adapter

首先需要準備兩個檔案

bull packagejson

bull plurkcoffee

71

Nodejs中文電子書

有這兩個就足以變成 NodeJS的模組了在開始 Coding之前先來設定一下 packagejson相依性

rdquonamerdquo rdquohubot-plurkrdquo

rdquoversionrdquo rdquo011rdquo

rdquomainrdquo rdquoplurkrdquo

rdquodependenciesrdquo

rdquohubotrdquo rdquogt=205rdquo

rdquooauthrdquo rdquordquo

rdquocronrdquordquordquo

這邊會使用到NodeJS的 cron模組(Module)而登入 Plurk需要OAuth標準授權才行所以也需要加載 OAuth模組

注意Hubot在讀取非內建模組時會自動在前面加上 hubot-的前置

83 建立 Robot跟 API

本篇應用基本上程式碼大部分參考 Hubot - Twitter Adapter來製作主要差異只有在使用 OAuth這個模組Twitter有 Streaming可用而 Plurk則得用 Comet方式來達到即時讀取

plurkcoffee程式碼

Robot = require(rdquohubotrdquo)robot()

Adapter = require(rdquohubtordquo)adapter()

EventEmitter = require(rdquoeventsrdquo)EventEmitter

oauth = require(rdquooauthrdquo)

cronJob = require(rdquocronrdquo)CronJob

class Plurk exntends Adapter

class PlurkStreaming exnteds EventEmitter

72 8 製作一個 Hubot的 Plurk Adapter

Nodejs中文電子書

先弄個基本架構主要 Class皆參考 Twitter Adapter命名方式接著再將 Plurk這個 Class增加幾個Method基本上只要有 run send reply就夠了而 run用來做初始化的部分

class Plurk entends Adapter

send (plurk id strings⋯) -gt

reply (plurk id strings⋯) -gt

run -gt

看起來有點東西接著來處理主要的 Plurk API結合部分

class PlurkStreaming extends EventEmitter

consuctor (options) -gt

plurk (callback) -gt

觀察河道

getChannel -gt

取得 Comet 網址

reply (plurk id message) -gt

回噗

acceptFriends -gt

接受好友

get (path callback) -gt

GET 請求

post (path body callback)-gt

POST 請求(其實是裝飾)

request (method path body callback)-gt

主要的 OAuth 請求

comet (server callback)-gt

噗浪的 Comet 傳回是 JavaScript Callback 要另外處理後才會變成 JSON

然後把注意力集中到 constructor上先把建構子弄好

constructor (options) -gt

super()

if optionskey and optionssecret and optionstoken and optionstoken secret

key = optionskey

secret = optionssecret

token = optionstoken

token secret = optionstoken secret

83 建立 Robot跟 API 73

Nodejs中文電子書

建立 OAuth 連接

consumer = new oauthOAuth(

rdquohttpwwwplurkcomOAuthrequest tokenrdquo

rdquohttpwwwplurkcomOAuthaccess tokenrdquo

key

secret

rdquo10rdquo

rdquohttpwwwplurkcomOAuthauthorizerdquo

rdquoHMAC-SHA1rdquo

)

domain = rdquowwwplurkcomrdquo

初始化取得 Comet 網址

do getChannel

else

throw new Error(rdquo 參數不足需要 Key Secret Token Token Secretrdquo)

接著來處理 request這個 method

request (method path body callback) -gt

記錄一下這次的 Request

consolelog(rdquohttpdomainpathrdquo)

Callback 這邊先不丟進去要用另一種方式處理

request = consumerget(rdquohttpdomainpathrdquo token token secret null)

requeston rdquoresponserdquo (res) -gt

reson rdquodatardquo (chunk) -gt

parseResponse(chunk+rsquorsquo callback)

reson rdquoendrdquo (data) -gt

consolelog rdquoEnd Request pathrdquo

reson rdquoerrorrdquo (data) -gt

consolelog rdquoError rdquo + data

requestend()

處理資料

parseResponse = (data callback) -gt

if datalength gt 0

用 TryCatch 避免處理 JSON 出錯導致整個中斷

try

callback null JSONparse(data)

catch err

74 8 製作一個 Hubot的 Plurk Adapter

Nodejs中文電子書

consolelog(rdquoError Parse JSONrdquo + data err)

繼續執行

callback null data ||

大致上就是這樣上面程式的架構已經將整個 Hubot Plurk Adapter完成因為在測試時竟然因為噗浪 Lag而沒讀到完整的 Comet資料然後造成程式異常為了避免這個問題發生因此需要加上為了完美呈現需要再

加上 Comet的處理所以要使用到 EventEmitter的功能

comet (server callback) -gt

在 Callback 裡面會找不到自身所以設定區域變數

self =

記錄一下這次的 Request

consolelog(rdquo[Comet] serverrdquo)

Callback 這邊先不丟進去要用另一種方式處理

request = consumerget(rdquohttpdomainpathrdquo token token secret null)

requeston rdquoresponserdquo (res) -gt

reson rdquodatardquo (chunk) -gt

parseResponse(chunk+rsquorsquo callback)

reson rdquoendrdquo (data) -gt

consolelog rdquoEnd Request pathrdquo

請求結束發出事件通知可以進行下一次請求

selfemit rdquonextPlurkrdquo

reson rdquoerrorrdquo (data) -gt

consolelog rdquoError rdquo + data

requestend()

處理資料

parseResponse = (data callback) -gt

if datalength gt 0

用 trycatch 避免失敗中斷

try

去掉 JavaScript 的 Callback

data = datamatch(CometChannelscriptCallback((+))s)

jsonData = rdquordquo

if data

83 建立 Robot跟 API 75

Nodejs中文電子書

jsonData = JSONparse(data[1])

else

如果沒有任何 Match 嘗試直接 parse

jsonData = JSONparse(data)

catch err

consolelog(rdquo[Comet] Errorrdquo data err)

用 TryCatch 避免處理 JSON 出錯導致整個中斷

try

只傳入 json 的 data 部分

callback null jsonDatadata

catch err

consolelog(rdquo[Comet]Error Parse JSONrdquo + data err)

繼續執行

callback null data ||

後面的 get跟 post就簡單多了

get (path callback) -gt

request(rdquoGETrdquo path null callback)

post (path body callback) -gt

request(rdquoPOSTrdquo path body callback)

接著處理取的 Comet網址的 getChannel

getChannel -gt

self =

get rdquoAPPRealtimegetUserChannelrdquo (error data) -gt

if error

檢查是否有 comet server

if datacomet server

selfchannel = datacomet server

如果沒有 Channel Ready 就嘗試連接會失敗

selfemit(rsquochannel readyrsquo)

那麼先來處理 Plurk Adaper好處理的部份

send (plruk id strings⋯)-gt

跟 Reply 一樣直接交給 reply 做

reply plurk id strings⋯

76 8 製作一個 Hubot的 Plurk Adapter

Nodejs中文電子書

reply (plurk id strings⋯) -gt

stringsforEach (message) =gt

botreply(plruk id message)

接著把 run處理好就可以上線運作摟

run -gt

self =

options =

key processenvHUBOT PLURK KEY

secret processenvHUBOT PLURK SECRET

token processenvHUBOT PLURK TOKEN

token secret processenvHUBOT PLURK TOKEN SECRET

創建剛剛的 API

bot = new PlurkStreaming(options)

依照 Twitter 的 new RobotTextMessage 會沒有反應所以參考 hubot-minecraft 的方式

r = robotconstructor

處理噗浪河道訊息

doPlurk = (data)-gt

檢查是否為回噗

if dataresponse

datacontent raw = dataresponsecontent raw

datauser id = dataresponseuser id

確定有噗浪 ID 跟訊息

if dataplurk id and datacontent raw

selfreceive new rTextMessage(dataplurk id datacontent raw)

取得 Comet Server 完成開始第一次 Comet 連接

boton rdquochannel readyrdquo () -gt

botplurk selfdoPlurk

上一次 Comet 完成繼續 Polling

boton rdquonextPlurkrdquo ()-gt

botplurk selfdoPlurk

定時接受好友邀請

do botacceptFriends

bot = bot

83 建立 Robot跟 API 77

Nodejs中文電子書

終於完成 AdapterHubot專案裡面的 scripts資料夾內是互動部分不需要像 Adapter如此大費周章處理只新增檔案並且設計好對白之後就會回噗了我開發用的機器人在此大家可以去跟他玩玩[httpplurkcomelct9620 bot](httpplurkcomelct9620 bot)

84 原始資料提供

bull [製 作 一 個 Hubot 的 噗 浪 Adapter](http revo-skillfrosttw blog20120318create-a-hubot-plurk-adapter)

78 8 製作一個 Hubot的 Plurk Adapter

9

Nodejs好用工具介紹

91 logio

Real-time log monitoring in your browser

httplogioorg

nodejs (gt=0412 lt=0611)

npm config set unsafe-perm true

npm install -g --prefix=usrlocal logio

logio server start

httplocalhost8998

79

Nodejs中文電子書

80 9 Nodejs好用工具介紹

10

精選文章

101 Express

bull 使用 NodeJS + Express從 GETPOST Request取值 [邀稿中]

bull link2 [邀稿中]

bull link3 [邀稿中]

81

Nodejs中文電子書

82 10 精選文章

11

參考資源

111 Nodejs書籍

bull Programming Nodejs April 2012 (est) Travis Swicegood Pragmatic Bookshelf

bull e Little Book on CoffeeScript January 2012 Alex MacCaw OrsquoReilly Media

bull Node for Front-End Developers January 2012 Garann Means OrsquoReilly Media

bull Building Hypermedia APIs with HTML5 and Node November 2011 MikeAmundsen OrsquoReilly Media

bull Node Web Development August 2011 David Herron PacktPub

bull CoffeeScript Accelerated JavaScript Development July 2011 Trevor BurnhamPragmatic Bookshelf

bull Getting Started with GEO CouchDB and Nodejs July 2011 Mick omp-son OrsquoReilly Media

bull What Is Node July 2011 Brett McLaughlin OrsquoReilly Media

bull Node Up and Running May 2011 Tom Hughes-Croucher Mike WilsonOrsquoReilly Media

83

Nodejs中文電子書

bull Hands-on Nodejs e Nodejs introduction and API reference May 2011 Pe-dro Teixeira LeanPub

bull e Node Beginner Book Manuel Kiessling

bull Mastering Nodejs visionmedia

112 Nodejs影音教學

bull e Node Sessions e Best of OSCON 2011 August 2011 OrsquoReilly Media(Video)

bull Tom Hughes-Croucher on Node March 2011 OrsquoReilly Media (Video)

113 Nodejs教學網站

bull Node Tuts

114 Nodejs課程

bull Introduction To Nodejs Van Nguyen CodeLesson

84 11 參考資源

Page 61: Nodejs Wiki

Nodejs中文電子書

)

consolelog(rsquostart express servernrsquo)

64 Express middleware

Express 裡面有一個十分好用的應用概念稱為 middleware可以透過middleware做出複雜的效果同時上面也有介紹 next方法參數傳遞就是靠 middleware的概念來傳遞參數讓開發者可以明確的控制程式邏輯

create http server

appuse(expressbodyParser())

appuse(expressmethodOverride())

appuse(expresssession())

上面都是一種 middleware的使用方式透過 appuse方式裡面載入函式執行方法回應函式會包含三個基本參數responserequestnext其中next表示下一個 middleware執行函式同時會自動將預設三個參數繼續帶往下個函式執行底下有個實驗

上面的片段程式執行後開啟瀏覽器連結上 localhost1337會發現伺服器回應結果順序如下

first middle ware

second middle ware

execute middle ware

end middleware function

從上面的結果可以得知剛才設定的 middleware都生效了在 appuse設定的 middleware是所有 url皆會執行方法如果有指定特定方法就可以使用 appget的 middleware設定在 appget函式的第二個參數就可以帶入函式或者是匿名函式只要函式裡面最後會接受 request response next這三個參數同時也有正確指定 next函式的執行時機最後都會執行到最後一個方法當然開發者也可以評估程式邏輯要執行到哪一個階段讓邏輯

可以更為分明

64 Express middleware 55

Nodejs中文電子書

65 Express路由應用

在實際開發上可能會遇到需要使用參數等方式混和變數一起使用

express裡面提供了一個很棒的處理方法 appall這個方式可以先採用基本路由配對再將設定為每個不同的處理方式開發者可以透過這個方式簡

化自己的程式邏輯

overview

author

version 20120226

create server

var app = require(rsquoexpressrsquo)createServer()

port = 1337

users = [

name rsquoClonnrsquo

name rsquoChirsquo

]

applisten(port)

appall(rsquouseridoprsquo function(req res next)

requser = users[reqparamsid]

if (requser)

next()

else

next(new Error(rsquocannot find user rsquo + reqparamsid))

)

appget(rsquouseridrsquo function(req res)

ressend(rsquoviewing rsquo + requsername)

)

appget(rsquouserideditrsquo function(req res)

ressend(rsquoediting rsquo + requsername)

)

56 6 Express介紹

Nodejs中文電子書

appget(rsquouseriddeletersquo function(req res)

ressend(rsquodeleting rsquo + requsername)

)

appget(rsquorsquo function(req res)

ressend(rsquoPage not foundrsquo 404)

)

consolelog(rsquostart express servernrsquo)

內部宣告一組預設的使用者分別給予名稱設定藉由 appall這個方法可以先將路由雛形建立再接下來設定 appget的路徑格式只要符合格式就會分配進入對應的方法中像上面的程式當中如果使用者輸入路徑為user0除了執行 appall程式之後執行 next方法就會對應到路徑設定為userid的這個方法當中如果使用者輸入路徑為user0edit就會執行到useridedit的對應方法

66 Express GET應用範例

我們準備一個使用 GET方法傳送資料的表單

ltDOCTYPE htmlgt

lthtmlgt

ltheadgt

ltmeta http-equiv=rdquoContent-Typerdquo content=rdquotexthtml charset=utf8rdquogt

lttitlegtNodejs 菜鳥筆記 (1)lttitlegt

ltlink rel=rdquostylesheetrdquo href=rdquocssstylecssrdquo type=rdquotextcssrdquo media=rdquoallrdquo gt

ltheadgt

ltbodygt

lth1gtNodejs 菜鳥筆記-註冊lth1gt

ltform id=rdquosignuprdquo method=rdquoGETrdquo action=rdquohttplocalhost3000Signuprdquogt

ltlabelgt 使用者名稱ltlabelgtltinput type=rdquotextrdquo id=rdquousernamerdquo name=rdquousernamerdquo gtltbrgt

ltlabelgt 電子郵件ltlabelgtltinput type=rdquotextrdquo id=rdquoemailrdquo name=rdquoemailrdquo gtltbrgt

ltinput type=rdquosubmitrdquo value=rdquo 註冊我的帳號rdquo gtltbrgt

ltformgt

ltbodygt

lthtmlgt

66 Express GET應用範例 57

Nodejs中文電子書

這個表單沒有什麼特別的地方我們只需要看第 9 行form 使用的method是 GET然後 action是rdquohttplocalhost3000Signupldquo等一下我們要來撰寫Signup這個 URL Path的處理程式

處理 Signup行為

我們知道所謂的 GET方法會透過 URL來把表單的值給帶過去以上面的表單來說到時候 URL會以這樣的形式傳遞

httplocalhost3000Signupusername=xxxampemail=xxx

所以要能處理這樣的資料必須有以下功能

bull 解析 URL

bull 辨別動作是 Signup

bull 解析出 username和 email

一旦能取得 username和 email的值程式就能加以應用了

處理 Signup的程式碼雛形

load module

var url = require(rsquourlrsquo)

urlData = urlparse(requrltrue)

action = urlDatapathname

reswriteHead(200 rdquoContent-Typerdquordquotexthtml charset=utf-8rdquo)

if (action === rdquoSignuprdquo)

user = urlDataquery

resend(rdquolth1gtrdquo + userusername + rdquo 歡迎您的加入 lth1gtltpgt 我們已經將會員啟用信寄至rdquo + useremail + rdquoltpgtrdquo)

首先需要加載 url module它是用來協助我們解析 URL的模組接著使用 urlparse方法第一個傳入 url字串變數也就是 requrl另外第二個參數的用意是設為 ture則引進 querystring模組來協助處理預設是 false它影響到的是 urlDataquery設為 true會傳回物件不然就只是一般的字串urlparse會將字串內容整理成一個物件我們把它指定給 urlData

action變數作為記錄 pathname這是我們稍後要來判斷目前網頁的動作是什麼接著先將 html表頭資訊 (Header)準備好再來判斷路徑邏輯如

58 6 Express介紹

Nodejs中文電子書

果是 Signup這個動作就把 urlDataquery裡的資料指定給 user然後輸出userusername和 useremail把使用者從表單註冊的資料顯示於頁面中

最後進行程式測試啟動 nodejs主程式之後開啟瀏覽器就會看到表單填寫完畢按下送出就可以看到結果了

完整 nodejs程式碼如下

var http = require(rsquohttprsquo)

url = require(rsquourlrsquo)

fs = require(rdquofsrdquo)

server

server = httpcreateServer(function (reqres)

var urlData

encode = rdquoutf8rdquo

filePath = rdquoviewexpress_get_example_formhtmlrdquo

action

urlData = urlparse(requrltrue)

action = urlDatapathname

reswriteHead(200 rdquoContent-Typerdquordquotexthtml charset=utf-8rdquo)

if (action === rdquoSignuprdquo)

user = urlDataquery

resend(rdquolth1gtrdquo + userusername + rdquo 歡迎您的加入 lth1gtltpgt 我們已經將會員啟用信寄至rdquo + useremail + rdquoltpgtrdquo)

else

fsreadFile(filePath encode function(err file)

reswrite(file)

resend()

)

)

serverlisten(3000)

consolelog(rsquoServer 跑起來了現在時間是rsquo + new Date())

66 Express GET應用範例 59

Nodejs中文電子書

67 Express POST應用範例

一開始準備基本的 html表單傳送內容以 POST方式form的 action屬性設定為 POST其餘 html內容與前一個範例應用相同

ltDOCTYPE htmlgt

lthtmlgt

ltheadgt

ltmeta http-equiv=rdquoContent-Typerdquo content=rdquotexthtml charset=utf8rdquogt

lttitlegtNodejs 菜鳥筆記 (1)lttitlegt

ltlink rel=rdquostylesheetrdquo href=rdquocssstylecssrdquo type=rdquotextcssrdquo media=rdquoallrdquo gt

ltheadgt

ltbodygt

lth1gtNodejs 菜鳥筆記-註冊lth1gt

ltform id=rdquosignuprdquo method=rdquoPOSTrdquo action=rdquohttplocalhost3000Signuprdquogt

ltlabelgt 使用者名稱ltlabelgtltinput type=rdquotextrdquo id=rdquousernamerdquo name=rdquousernamerdquo gtltbrgt

ltlabelgt 電子郵件ltlabelgtltinput type=rdquotextrdquo id=rdquoemailrdquo name=rdquoemailrdquo gtltbrgt

ltinput type=rdquosubmitrdquo value=rdquo 註冊我的帳號rdquo gtltbrgt

ltformgt

ltbodygt

lthtmlgt

nodejs的程式處理邏輯與前面 GET範例類似部分程式碼如下

qs = require(rsquoquerystringrsquo)

if (action === rdquoSignuprdquo)

formData = rsquorsquo

reqon(rdquodatardquo function (data)

formData += data

)

reqon(rdquoendrdquo function ()

user = qsparse(formData)

resend(rdquolth1gtrdquo + userusername + rdquo 歡迎您的加入 lth1gtltpgt 我們已經將會員啟用信寄至rdquo + useremail + rdquoltpgtrdquo)

)

60 6 Express介紹

Nodejs中文電子書

主要加入了rsquoquerystringrsquo這個moduel方便我們等一下解析由表單 POST回來的資料另外加入一個 formData的變數用來搜集待等一下表單回傳的資料前面的 GET範例我們只從 req拿出 url的資料這次要在利用req身上的事件處理

JavaScript 在訂閱事件時使用 addEventListener而 nodejs 使用的則是on這邊加上了監聽 data的事件會在瀏覽器傳送資料到Web Server時被執行參數是它所接收到的資料型態是字串

接著再增加 end的事件當瀏覽器的請求事件結束時它就會動作

由於瀏覽器使用 POST在上傳資料時會將資料一塊塊地上傳因為我們在監聽 data事件時透過 formData變數將它累加起來lt不過由於我們

上傳的資料很少一次就結束不過如果日後需要傳的是資料比較大的檔

案這個累加動作就很重要

當資料傳完就進到 end 事件中會用到 qsparse 來解析 formDataformData的內容是字串內容是

username=wordsmithampemail=wordsmith40somewhere

而 qsparse可以幫我們把這個 querystring轉成物件的格式也就是

username=wordsmithampemail=wordsmith40somewhere

一旦轉成物件並指定給 user之後其他的事情就和 GET方法時操作的一樣寫 response的表頭將內容回傳並將 userusername和 useremail代入到內容中

修改完成後接著執行 nodejs程式啟動 web server開啟瀏覽器進入表單測試看看POST的方式能否順利運作

完整程式碼如下

var http = require(rsquohttprsquo)

url = require(rsquourlrsquo)

fs = require(rdquofsrdquo)

qs = require(rsquoquerystringrsquo)

server

server = httpcreateServer(function (reqres)

var urlData

67 Express POST應用範例 61

Nodejs中文電子書

encode = rdquoutf8rdquo

filePath = rdquoviewexpress_post_example_formhtmlrdquo

formData

action

urlData = urlparse(requrltrue)

action = urlDatapathname

reswriteHead(200 rdquoContent-Typerdquordquotexthtml charset=utf-8rdquo)

if (action === rdquoSignuprdquo)

formData = rsquorsquo

reqon(rdquodatardquo function (data)

formData += data

)

reqon(rdquoendrdquo function ()

user = qsparse(formData)

resend(rdquolth1gtrdquo + userusername + rdquo 歡迎您的加入 lth1gtltpgt 我們已經將會員啟用信寄至rdquo + useremail + rdquoltpgtrdquo)

)

else

fsreadFile(filePath encode function(err file)

reswrite(file)

resend()

)

)

serverlisten(3000)

consolelog(rsquoServer 跑起來了現在時間是rsquo + new Date())

62 6 Express介紹

Nodejs中文電子書

68 Express AJAX應用範例

在 Nodejs要使用 Ajax傳送資料並且與之互動在接受資料的部份沒有太大的差別client端不是用 GET就是用 POST來傳資料重點在處理完後用 JSON格式回傳當然 Ajax不見得只傳 JSON格式有時是回傳一段 HTML碼不過後者對伺服器來說基本上就和前兩篇沒有差別了所以我們還是以回傳 JSON做為這一回的主題

這一回其實大多數的工作都會落在前端 Ajax上面前端要負責發送與接收資料並在接收資料後撤掉原先發送資料的表單並將取得的資料

改成 HTML格式之後放上頁面

首先先準備 HTML靜態頁面資料

ltDOCTYPE htmlgt

lthtmlgt

ltheadgt

lttitlegtNodejs 菜鳥筆記 (3)lttitlegt

ltlink rel=rdquostylesheetrdquo href=rdquocssstylecssrdquo type=rdquotextcssrdquo media=rdquoallrdquo gt

ltscript src=rdquohttpsajaxgoogleapiscomajaxlibsjquery171jqueryminjsrdquogtltscriptgt

ltheadgt

ltbodygt

lth1gtNodejs 菜鳥筆記-註冊 (用 Ajax)lth1gt

ltform id=rdquosignuprdquo action=rdquoSignuprdquo method=rdquoPOSTrdquogt

ltlabelgt 使用者名稱ltlabelgtltinput type=rdquotextrdquo id=rdquousernamerdquo name=rdquousernamerdquo gtltbrgt

ltlabelgt 電子郵件ltlabelgtltinput type=rdquotextrdquo id=rdquoemailrdquo name=rdquoemailrdquo gtltbrgt

ltinput type=rdquosubmitrdquo value=rdquo 註冊我的帳號rdquo gtltbrgt

ltformgt

ltdiv id=rdquoresultrdquogt

ltdivgt

ltscript type=rdquotextjavascriptrdquogt

$(function()$(rsquosignup input[type=rdquosubmitrdquo]rsquo)click(function(e)

var user =

userusername = $(rdquousernamerdquo)val()useremail = $(rdquoemailrdquo)val()

$post(rdquoSignuprdquouserfunction(data)greet(data)

)

68 Express AJAX應用範例 63

Nodejs中文電子書

epreventDefault()

)

var greet = function(msg)

var resultNode = $(rsquoresultrsquo)greeting = $(rsquolth2gtHirsquo+msgusername+rsquo 你的會員 id 是rsquo+ msgid +rsquo 我們會將會員啟動信寄至rsquo+msgemail+rsquolth2gtrsquo)

$(rsquosignuprsquo)hide()resultNodehtml(rdquordquo)

resultNodeappend(greeting)

)

ltscriptgt

ltbodygt

lthtmlgt

HTML頁面上準備了一個表單用來傳送註冊資料接著直接引用了Google CDN來載入 jQuery用來幫我們處理 Ajax的工作這次要傳送和接收的工作很大的變動都在 HTML頁面上的 JavaScript當中我們要做的事有 (相關 jQuery處理這邊不多做贅述指提起主要功能解說)

bull 用 jQUery取得 submit按鈕綁定它的 click動作

bull 取得表單 username和 email的值存放在 user這個物件中

bull 用 jQuery的 $post方法將 user的資料傳到 Server

bull 一旦成功取得資料後透過 greet這個 function組成回報給 user的訊息

bull 清空原本給使用者填資料的表單

bull 將 Server回傳的 usernameemail和 id這 3個資料組成回應的訊息

bull 將訊息放到原本表單的位置

經過以上的處理後一個 Ajax的表單的基本功能已經完成

接著進行 nodejs主要程式的編輯部分程式碼如下

var fs = require(rdquofsrdquo)

64 6 Express介紹

Nodejs中文電子書

qs = require(rsquoquerystringrsquo)

if (action === rdquoSignuprdquo)

formData = rsquorsquo

reqon(rdquodatardquo function (data)

formData += data

)

reqon(rdquoendrdquo function ()

var msg

user = qsparse(formData)

userid = rdquo123456rdquo

msg = JSONstringify(user)

reswriteHead(200 rdquoContent-Typerdquordquoapplicationjson charset=utf-8rdquordquoContent-Lengthrdquomsglength)

resend(msg)

)

這裡的程式和前面 POST範例基本上大同小異差別在

bull 幫 user的資料加上 id隨意存放一些文字進去讓 Server回傳的資料多於 Client端傳上來的不然會覺得 Server都沒做事

bull 增加了 msg 這個變數存放將 user 物件 JSON 文字化的結果JSONstringify這個轉換函式是 V8引擎所提供的如果你好奇的話

bull 大重點來了我們要告訴 Client端這次回傳的資料格式是 JSON所在 Content-type和 Content-Length要提供給 Client

Server很輕鬆就完成任務了最後進行程式測試啟動 nodejs主程式之後開啟瀏覽器就會看到表單填寫完畢按下送出就可以看到結果了

最後 nodejs本篇範例程式碼如下

var http = require(rsquohttprsquo)

url = require(rsquourlrsquo)

fs = require(rdquofsrdquo)

qs = require(rsquoquerystringrsquo)

server

68 Express AJAX應用範例 65

Nodejs中文電子書

server = httpcreateServer(function (reqres)

var urlData

encode = rdquoutf8rdquo

filePath = rdquoviewexpress_ajax_example_formhtmlrdquo

formData

action

urlData = urlparse(requrltrue)

action = urlDatapathname

if (action === rdquoSignuprdquo)

formData = rsquorsquo

reqon(rdquodatardquo function (data)

formData += data

)

reqon(rdquoendrdquo function ()

var msg

user = qsparse(formData)

userid = rdquo123456rdquo

msg = JSONstringify(user)

reswriteHead(200 rdquoContent-TyperdquordquoapplicationjsonrdquordquoContent-Lengthrdquomsglength)

resend(msg)

)

else

fsreadFile(filePath encode function(err file)

reswriteHead(200 rdquoContent-Typerdquordquotexthtml charset=utf-8rdquo)

reswrite(file)

resend()

)

)

serverlisten(3000)

66 6 Express介紹

Nodejs中文電子書

consolelog(rsquoServer 跑起來了現在時間是rsquo + new Date())

69 原始資料提供

bull [NodeJS初學者筆記 (1)-用 GET傳送資料] (httpithelpithomecomtwquestion10087402)

bull [NodeJS初學者筆記 (2)-用 POST傳送資料] (httpithelpithomecomtwquestion10087489)

bull [NodeJS 初學者筆記 (3)-用 Ajax 傳送資料] (httpithelpithomecomtwquestion10087627)

69 原始資料提供 67

Nodejs中文電子書

68 6 Express介紹

7

CoffeeScript

bull Spine

bull Hem

bull Spineapp

Letrsquos Have a Cup of CoffeeScript

bull es5-shim ECMAScript 5 compatibility shims for legacy JavaScript engines

ESnext

node-iform - A middleware for node-validator httpsgithubcomguileennode-iform

69

Nodejs中文電子書

70 7 CoffeeScript

8

製作一個 Hubot的 Plurk Adapter

81 應用事項提醒

此應用範例以CoffeeScript編寫主要程式架構採用Github釋放的Hubot專案以下有幾個主要注意事項請讀者注意

bull Hubot 一開始的架構除了內建的兩個 Adapter 之外其餘都要以Nodejs的Module方式才能運作

bull Hubot的 binhubot裡面寫著 npm install所以不管你怎麼改原始碼也不能改變第一點的狀況

其實上述都在討論同一件事情NodeJS的模組而且模組不能設定相對路徑之類的來安裝一定要包裝成 npm相符和格式才有辦法正確執行

82 建立 Adapter

首先需要準備兩個檔案

bull packagejson

bull plurkcoffee

71

Nodejs中文電子書

有這兩個就足以變成 NodeJS的模組了在開始 Coding之前先來設定一下 packagejson相依性

rdquonamerdquo rdquohubot-plurkrdquo

rdquoversionrdquo rdquo011rdquo

rdquomainrdquo rdquoplurkrdquo

rdquodependenciesrdquo

rdquohubotrdquo rdquogt=205rdquo

rdquooauthrdquo rdquordquo

rdquocronrdquordquordquo

這邊會使用到NodeJS的 cron模組(Module)而登入 Plurk需要OAuth標準授權才行所以也需要加載 OAuth模組

注意Hubot在讀取非內建模組時會自動在前面加上 hubot-的前置

83 建立 Robot跟 API

本篇應用基本上程式碼大部分參考 Hubot - Twitter Adapter來製作主要差異只有在使用 OAuth這個模組Twitter有 Streaming可用而 Plurk則得用 Comet方式來達到即時讀取

plurkcoffee程式碼

Robot = require(rdquohubotrdquo)robot()

Adapter = require(rdquohubtordquo)adapter()

EventEmitter = require(rdquoeventsrdquo)EventEmitter

oauth = require(rdquooauthrdquo)

cronJob = require(rdquocronrdquo)CronJob

class Plurk exntends Adapter

class PlurkStreaming exnteds EventEmitter

72 8 製作一個 Hubot的 Plurk Adapter

Nodejs中文電子書

先弄個基本架構主要 Class皆參考 Twitter Adapter命名方式接著再將 Plurk這個 Class增加幾個Method基本上只要有 run send reply就夠了而 run用來做初始化的部分

class Plurk entends Adapter

send (plurk id strings⋯) -gt

reply (plurk id strings⋯) -gt

run -gt

看起來有點東西接著來處理主要的 Plurk API結合部分

class PlurkStreaming extends EventEmitter

consuctor (options) -gt

plurk (callback) -gt

觀察河道

getChannel -gt

取得 Comet 網址

reply (plurk id message) -gt

回噗

acceptFriends -gt

接受好友

get (path callback) -gt

GET 請求

post (path body callback)-gt

POST 請求(其實是裝飾)

request (method path body callback)-gt

主要的 OAuth 請求

comet (server callback)-gt

噗浪的 Comet 傳回是 JavaScript Callback 要另外處理後才會變成 JSON

然後把注意力集中到 constructor上先把建構子弄好

constructor (options) -gt

super()

if optionskey and optionssecret and optionstoken and optionstoken secret

key = optionskey

secret = optionssecret

token = optionstoken

token secret = optionstoken secret

83 建立 Robot跟 API 73

Nodejs中文電子書

建立 OAuth 連接

consumer = new oauthOAuth(

rdquohttpwwwplurkcomOAuthrequest tokenrdquo

rdquohttpwwwplurkcomOAuthaccess tokenrdquo

key

secret

rdquo10rdquo

rdquohttpwwwplurkcomOAuthauthorizerdquo

rdquoHMAC-SHA1rdquo

)

domain = rdquowwwplurkcomrdquo

初始化取得 Comet 網址

do getChannel

else

throw new Error(rdquo 參數不足需要 Key Secret Token Token Secretrdquo)

接著來處理 request這個 method

request (method path body callback) -gt

記錄一下這次的 Request

consolelog(rdquohttpdomainpathrdquo)

Callback 這邊先不丟進去要用另一種方式處理

request = consumerget(rdquohttpdomainpathrdquo token token secret null)

requeston rdquoresponserdquo (res) -gt

reson rdquodatardquo (chunk) -gt

parseResponse(chunk+rsquorsquo callback)

reson rdquoendrdquo (data) -gt

consolelog rdquoEnd Request pathrdquo

reson rdquoerrorrdquo (data) -gt

consolelog rdquoError rdquo + data

requestend()

處理資料

parseResponse = (data callback) -gt

if datalength gt 0

用 TryCatch 避免處理 JSON 出錯導致整個中斷

try

callback null JSONparse(data)

catch err

74 8 製作一個 Hubot的 Plurk Adapter

Nodejs中文電子書

consolelog(rdquoError Parse JSONrdquo + data err)

繼續執行

callback null data ||

大致上就是這樣上面程式的架構已經將整個 Hubot Plurk Adapter完成因為在測試時竟然因為噗浪 Lag而沒讀到完整的 Comet資料然後造成程式異常為了避免這個問題發生因此需要加上為了完美呈現需要再

加上 Comet的處理所以要使用到 EventEmitter的功能

comet (server callback) -gt

在 Callback 裡面會找不到自身所以設定區域變數

self =

記錄一下這次的 Request

consolelog(rdquo[Comet] serverrdquo)

Callback 這邊先不丟進去要用另一種方式處理

request = consumerget(rdquohttpdomainpathrdquo token token secret null)

requeston rdquoresponserdquo (res) -gt

reson rdquodatardquo (chunk) -gt

parseResponse(chunk+rsquorsquo callback)

reson rdquoendrdquo (data) -gt

consolelog rdquoEnd Request pathrdquo

請求結束發出事件通知可以進行下一次請求

selfemit rdquonextPlurkrdquo

reson rdquoerrorrdquo (data) -gt

consolelog rdquoError rdquo + data

requestend()

處理資料

parseResponse = (data callback) -gt

if datalength gt 0

用 trycatch 避免失敗中斷

try

去掉 JavaScript 的 Callback

data = datamatch(CometChannelscriptCallback((+))s)

jsonData = rdquordquo

if data

83 建立 Robot跟 API 75

Nodejs中文電子書

jsonData = JSONparse(data[1])

else

如果沒有任何 Match 嘗試直接 parse

jsonData = JSONparse(data)

catch err

consolelog(rdquo[Comet] Errorrdquo data err)

用 TryCatch 避免處理 JSON 出錯導致整個中斷

try

只傳入 json 的 data 部分

callback null jsonDatadata

catch err

consolelog(rdquo[Comet]Error Parse JSONrdquo + data err)

繼續執行

callback null data ||

後面的 get跟 post就簡單多了

get (path callback) -gt

request(rdquoGETrdquo path null callback)

post (path body callback) -gt

request(rdquoPOSTrdquo path body callback)

接著處理取的 Comet網址的 getChannel

getChannel -gt

self =

get rdquoAPPRealtimegetUserChannelrdquo (error data) -gt

if error

檢查是否有 comet server

if datacomet server

selfchannel = datacomet server

如果沒有 Channel Ready 就嘗試連接會失敗

selfemit(rsquochannel readyrsquo)

那麼先來處理 Plurk Adaper好處理的部份

send (plruk id strings⋯)-gt

跟 Reply 一樣直接交給 reply 做

reply plurk id strings⋯

76 8 製作一個 Hubot的 Plurk Adapter

Nodejs中文電子書

reply (plurk id strings⋯) -gt

stringsforEach (message) =gt

botreply(plruk id message)

接著把 run處理好就可以上線運作摟

run -gt

self =

options =

key processenvHUBOT PLURK KEY

secret processenvHUBOT PLURK SECRET

token processenvHUBOT PLURK TOKEN

token secret processenvHUBOT PLURK TOKEN SECRET

創建剛剛的 API

bot = new PlurkStreaming(options)

依照 Twitter 的 new RobotTextMessage 會沒有反應所以參考 hubot-minecraft 的方式

r = robotconstructor

處理噗浪河道訊息

doPlurk = (data)-gt

檢查是否為回噗

if dataresponse

datacontent raw = dataresponsecontent raw

datauser id = dataresponseuser id

確定有噗浪 ID 跟訊息

if dataplurk id and datacontent raw

selfreceive new rTextMessage(dataplurk id datacontent raw)

取得 Comet Server 完成開始第一次 Comet 連接

boton rdquochannel readyrdquo () -gt

botplurk selfdoPlurk

上一次 Comet 完成繼續 Polling

boton rdquonextPlurkrdquo ()-gt

botplurk selfdoPlurk

定時接受好友邀請

do botacceptFriends

bot = bot

83 建立 Robot跟 API 77

Nodejs中文電子書

終於完成 AdapterHubot專案裡面的 scripts資料夾內是互動部分不需要像 Adapter如此大費周章處理只新增檔案並且設計好對白之後就會回噗了我開發用的機器人在此大家可以去跟他玩玩[httpplurkcomelct9620 bot](httpplurkcomelct9620 bot)

84 原始資料提供

bull [製 作 一 個 Hubot 的 噗 浪 Adapter](http revo-skillfrosttw blog20120318create-a-hubot-plurk-adapter)

78 8 製作一個 Hubot的 Plurk Adapter

9

Nodejs好用工具介紹

91 logio

Real-time log monitoring in your browser

httplogioorg

nodejs (gt=0412 lt=0611)

npm config set unsafe-perm true

npm install -g --prefix=usrlocal logio

logio server start

httplocalhost8998

79

Nodejs中文電子書

80 9 Nodejs好用工具介紹

10

精選文章

101 Express

bull 使用 NodeJS + Express從 GETPOST Request取值 [邀稿中]

bull link2 [邀稿中]

bull link3 [邀稿中]

81

Nodejs中文電子書

82 10 精選文章

11

參考資源

111 Nodejs書籍

bull Programming Nodejs April 2012 (est) Travis Swicegood Pragmatic Bookshelf

bull e Little Book on CoffeeScript January 2012 Alex MacCaw OrsquoReilly Media

bull Node for Front-End Developers January 2012 Garann Means OrsquoReilly Media

bull Building Hypermedia APIs with HTML5 and Node November 2011 MikeAmundsen OrsquoReilly Media

bull Node Web Development August 2011 David Herron PacktPub

bull CoffeeScript Accelerated JavaScript Development July 2011 Trevor BurnhamPragmatic Bookshelf

bull Getting Started with GEO CouchDB and Nodejs July 2011 Mick omp-son OrsquoReilly Media

bull What Is Node July 2011 Brett McLaughlin OrsquoReilly Media

bull Node Up and Running May 2011 Tom Hughes-Croucher Mike WilsonOrsquoReilly Media

83

Nodejs中文電子書

bull Hands-on Nodejs e Nodejs introduction and API reference May 2011 Pe-dro Teixeira LeanPub

bull e Node Beginner Book Manuel Kiessling

bull Mastering Nodejs visionmedia

112 Nodejs影音教學

bull e Node Sessions e Best of OSCON 2011 August 2011 OrsquoReilly Media(Video)

bull Tom Hughes-Croucher on Node March 2011 OrsquoReilly Media (Video)

113 Nodejs教學網站

bull Node Tuts

114 Nodejs課程

bull Introduction To Nodejs Van Nguyen CodeLesson

84 11 參考資源

Page 62: Nodejs Wiki

Nodejs中文電子書

65 Express路由應用

在實際開發上可能會遇到需要使用參數等方式混和變數一起使用

express裡面提供了一個很棒的處理方法 appall這個方式可以先採用基本路由配對再將設定為每個不同的處理方式開發者可以透過這個方式簡

化自己的程式邏輯

overview

author

version 20120226

create server

var app = require(rsquoexpressrsquo)createServer()

port = 1337

users = [

name rsquoClonnrsquo

name rsquoChirsquo

]

applisten(port)

appall(rsquouseridoprsquo function(req res next)

requser = users[reqparamsid]

if (requser)

next()

else

next(new Error(rsquocannot find user rsquo + reqparamsid))

)

appget(rsquouseridrsquo function(req res)

ressend(rsquoviewing rsquo + requsername)

)

appget(rsquouserideditrsquo function(req res)

ressend(rsquoediting rsquo + requsername)

)

56 6 Express介紹

Nodejs中文電子書

appget(rsquouseriddeletersquo function(req res)

ressend(rsquodeleting rsquo + requsername)

)

appget(rsquorsquo function(req res)

ressend(rsquoPage not foundrsquo 404)

)

consolelog(rsquostart express servernrsquo)

內部宣告一組預設的使用者分別給予名稱設定藉由 appall這個方法可以先將路由雛形建立再接下來設定 appget的路徑格式只要符合格式就會分配進入對應的方法中像上面的程式當中如果使用者輸入路徑為user0除了執行 appall程式之後執行 next方法就會對應到路徑設定為userid的這個方法當中如果使用者輸入路徑為user0edit就會執行到useridedit的對應方法

66 Express GET應用範例

我們準備一個使用 GET方法傳送資料的表單

ltDOCTYPE htmlgt

lthtmlgt

ltheadgt

ltmeta http-equiv=rdquoContent-Typerdquo content=rdquotexthtml charset=utf8rdquogt

lttitlegtNodejs 菜鳥筆記 (1)lttitlegt

ltlink rel=rdquostylesheetrdquo href=rdquocssstylecssrdquo type=rdquotextcssrdquo media=rdquoallrdquo gt

ltheadgt

ltbodygt

lth1gtNodejs 菜鳥筆記-註冊lth1gt

ltform id=rdquosignuprdquo method=rdquoGETrdquo action=rdquohttplocalhost3000Signuprdquogt

ltlabelgt 使用者名稱ltlabelgtltinput type=rdquotextrdquo id=rdquousernamerdquo name=rdquousernamerdquo gtltbrgt

ltlabelgt 電子郵件ltlabelgtltinput type=rdquotextrdquo id=rdquoemailrdquo name=rdquoemailrdquo gtltbrgt

ltinput type=rdquosubmitrdquo value=rdquo 註冊我的帳號rdquo gtltbrgt

ltformgt

ltbodygt

lthtmlgt

66 Express GET應用範例 57

Nodejs中文電子書

這個表單沒有什麼特別的地方我們只需要看第 9 行form 使用的method是 GET然後 action是rdquohttplocalhost3000Signupldquo等一下我們要來撰寫Signup這個 URL Path的處理程式

處理 Signup行為

我們知道所謂的 GET方法會透過 URL來把表單的值給帶過去以上面的表單來說到時候 URL會以這樣的形式傳遞

httplocalhost3000Signupusername=xxxampemail=xxx

所以要能處理這樣的資料必須有以下功能

bull 解析 URL

bull 辨別動作是 Signup

bull 解析出 username和 email

一旦能取得 username和 email的值程式就能加以應用了

處理 Signup的程式碼雛形

load module

var url = require(rsquourlrsquo)

urlData = urlparse(requrltrue)

action = urlDatapathname

reswriteHead(200 rdquoContent-Typerdquordquotexthtml charset=utf-8rdquo)

if (action === rdquoSignuprdquo)

user = urlDataquery

resend(rdquolth1gtrdquo + userusername + rdquo 歡迎您的加入 lth1gtltpgt 我們已經將會員啟用信寄至rdquo + useremail + rdquoltpgtrdquo)

首先需要加載 url module它是用來協助我們解析 URL的模組接著使用 urlparse方法第一個傳入 url字串變數也就是 requrl另外第二個參數的用意是設為 ture則引進 querystring模組來協助處理預設是 false它影響到的是 urlDataquery設為 true會傳回物件不然就只是一般的字串urlparse會將字串內容整理成一個物件我們把它指定給 urlData

action變數作為記錄 pathname這是我們稍後要來判斷目前網頁的動作是什麼接著先將 html表頭資訊 (Header)準備好再來判斷路徑邏輯如

58 6 Express介紹

Nodejs中文電子書

果是 Signup這個動作就把 urlDataquery裡的資料指定給 user然後輸出userusername和 useremail把使用者從表單註冊的資料顯示於頁面中

最後進行程式測試啟動 nodejs主程式之後開啟瀏覽器就會看到表單填寫完畢按下送出就可以看到結果了

完整 nodejs程式碼如下

var http = require(rsquohttprsquo)

url = require(rsquourlrsquo)

fs = require(rdquofsrdquo)

server

server = httpcreateServer(function (reqres)

var urlData

encode = rdquoutf8rdquo

filePath = rdquoviewexpress_get_example_formhtmlrdquo

action

urlData = urlparse(requrltrue)

action = urlDatapathname

reswriteHead(200 rdquoContent-Typerdquordquotexthtml charset=utf-8rdquo)

if (action === rdquoSignuprdquo)

user = urlDataquery

resend(rdquolth1gtrdquo + userusername + rdquo 歡迎您的加入 lth1gtltpgt 我們已經將會員啟用信寄至rdquo + useremail + rdquoltpgtrdquo)

else

fsreadFile(filePath encode function(err file)

reswrite(file)

resend()

)

)

serverlisten(3000)

consolelog(rsquoServer 跑起來了現在時間是rsquo + new Date())

66 Express GET應用範例 59

Nodejs中文電子書

67 Express POST應用範例

一開始準備基本的 html表單傳送內容以 POST方式form的 action屬性設定為 POST其餘 html內容與前一個範例應用相同

ltDOCTYPE htmlgt

lthtmlgt

ltheadgt

ltmeta http-equiv=rdquoContent-Typerdquo content=rdquotexthtml charset=utf8rdquogt

lttitlegtNodejs 菜鳥筆記 (1)lttitlegt

ltlink rel=rdquostylesheetrdquo href=rdquocssstylecssrdquo type=rdquotextcssrdquo media=rdquoallrdquo gt

ltheadgt

ltbodygt

lth1gtNodejs 菜鳥筆記-註冊lth1gt

ltform id=rdquosignuprdquo method=rdquoPOSTrdquo action=rdquohttplocalhost3000Signuprdquogt

ltlabelgt 使用者名稱ltlabelgtltinput type=rdquotextrdquo id=rdquousernamerdquo name=rdquousernamerdquo gtltbrgt

ltlabelgt 電子郵件ltlabelgtltinput type=rdquotextrdquo id=rdquoemailrdquo name=rdquoemailrdquo gtltbrgt

ltinput type=rdquosubmitrdquo value=rdquo 註冊我的帳號rdquo gtltbrgt

ltformgt

ltbodygt

lthtmlgt

nodejs的程式處理邏輯與前面 GET範例類似部分程式碼如下

qs = require(rsquoquerystringrsquo)

if (action === rdquoSignuprdquo)

formData = rsquorsquo

reqon(rdquodatardquo function (data)

formData += data

)

reqon(rdquoendrdquo function ()

user = qsparse(formData)

resend(rdquolth1gtrdquo + userusername + rdquo 歡迎您的加入 lth1gtltpgt 我們已經將會員啟用信寄至rdquo + useremail + rdquoltpgtrdquo)

)

60 6 Express介紹

Nodejs中文電子書

主要加入了rsquoquerystringrsquo這個moduel方便我們等一下解析由表單 POST回來的資料另外加入一個 formData的變數用來搜集待等一下表單回傳的資料前面的 GET範例我們只從 req拿出 url的資料這次要在利用req身上的事件處理

JavaScript 在訂閱事件時使用 addEventListener而 nodejs 使用的則是on這邊加上了監聽 data的事件會在瀏覽器傳送資料到Web Server時被執行參數是它所接收到的資料型態是字串

接著再增加 end的事件當瀏覽器的請求事件結束時它就會動作

由於瀏覽器使用 POST在上傳資料時會將資料一塊塊地上傳因為我們在監聽 data事件時透過 formData變數將它累加起來lt不過由於我們

上傳的資料很少一次就結束不過如果日後需要傳的是資料比較大的檔

案這個累加動作就很重要

當資料傳完就進到 end 事件中會用到 qsparse 來解析 formDataformData的內容是字串內容是

username=wordsmithampemail=wordsmith40somewhere

而 qsparse可以幫我們把這個 querystring轉成物件的格式也就是

username=wordsmithampemail=wordsmith40somewhere

一旦轉成物件並指定給 user之後其他的事情就和 GET方法時操作的一樣寫 response的表頭將內容回傳並將 userusername和 useremail代入到內容中

修改完成後接著執行 nodejs程式啟動 web server開啟瀏覽器進入表單測試看看POST的方式能否順利運作

完整程式碼如下

var http = require(rsquohttprsquo)

url = require(rsquourlrsquo)

fs = require(rdquofsrdquo)

qs = require(rsquoquerystringrsquo)

server

server = httpcreateServer(function (reqres)

var urlData

67 Express POST應用範例 61

Nodejs中文電子書

encode = rdquoutf8rdquo

filePath = rdquoviewexpress_post_example_formhtmlrdquo

formData

action

urlData = urlparse(requrltrue)

action = urlDatapathname

reswriteHead(200 rdquoContent-Typerdquordquotexthtml charset=utf-8rdquo)

if (action === rdquoSignuprdquo)

formData = rsquorsquo

reqon(rdquodatardquo function (data)

formData += data

)

reqon(rdquoendrdquo function ()

user = qsparse(formData)

resend(rdquolth1gtrdquo + userusername + rdquo 歡迎您的加入 lth1gtltpgt 我們已經將會員啟用信寄至rdquo + useremail + rdquoltpgtrdquo)

)

else

fsreadFile(filePath encode function(err file)

reswrite(file)

resend()

)

)

serverlisten(3000)

consolelog(rsquoServer 跑起來了現在時間是rsquo + new Date())

62 6 Express介紹

Nodejs中文電子書

68 Express AJAX應用範例

在 Nodejs要使用 Ajax傳送資料並且與之互動在接受資料的部份沒有太大的差別client端不是用 GET就是用 POST來傳資料重點在處理完後用 JSON格式回傳當然 Ajax不見得只傳 JSON格式有時是回傳一段 HTML碼不過後者對伺服器來說基本上就和前兩篇沒有差別了所以我們還是以回傳 JSON做為這一回的主題

這一回其實大多數的工作都會落在前端 Ajax上面前端要負責發送與接收資料並在接收資料後撤掉原先發送資料的表單並將取得的資料

改成 HTML格式之後放上頁面

首先先準備 HTML靜態頁面資料

ltDOCTYPE htmlgt

lthtmlgt

ltheadgt

lttitlegtNodejs 菜鳥筆記 (3)lttitlegt

ltlink rel=rdquostylesheetrdquo href=rdquocssstylecssrdquo type=rdquotextcssrdquo media=rdquoallrdquo gt

ltscript src=rdquohttpsajaxgoogleapiscomajaxlibsjquery171jqueryminjsrdquogtltscriptgt

ltheadgt

ltbodygt

lth1gtNodejs 菜鳥筆記-註冊 (用 Ajax)lth1gt

ltform id=rdquosignuprdquo action=rdquoSignuprdquo method=rdquoPOSTrdquogt

ltlabelgt 使用者名稱ltlabelgtltinput type=rdquotextrdquo id=rdquousernamerdquo name=rdquousernamerdquo gtltbrgt

ltlabelgt 電子郵件ltlabelgtltinput type=rdquotextrdquo id=rdquoemailrdquo name=rdquoemailrdquo gtltbrgt

ltinput type=rdquosubmitrdquo value=rdquo 註冊我的帳號rdquo gtltbrgt

ltformgt

ltdiv id=rdquoresultrdquogt

ltdivgt

ltscript type=rdquotextjavascriptrdquogt

$(function()$(rsquosignup input[type=rdquosubmitrdquo]rsquo)click(function(e)

var user =

userusername = $(rdquousernamerdquo)val()useremail = $(rdquoemailrdquo)val()

$post(rdquoSignuprdquouserfunction(data)greet(data)

)

68 Express AJAX應用範例 63

Nodejs中文電子書

epreventDefault()

)

var greet = function(msg)

var resultNode = $(rsquoresultrsquo)greeting = $(rsquolth2gtHirsquo+msgusername+rsquo 你的會員 id 是rsquo+ msgid +rsquo 我們會將會員啟動信寄至rsquo+msgemail+rsquolth2gtrsquo)

$(rsquosignuprsquo)hide()resultNodehtml(rdquordquo)

resultNodeappend(greeting)

)

ltscriptgt

ltbodygt

lthtmlgt

HTML頁面上準備了一個表單用來傳送註冊資料接著直接引用了Google CDN來載入 jQuery用來幫我們處理 Ajax的工作這次要傳送和接收的工作很大的變動都在 HTML頁面上的 JavaScript當中我們要做的事有 (相關 jQuery處理這邊不多做贅述指提起主要功能解說)

bull 用 jQUery取得 submit按鈕綁定它的 click動作

bull 取得表單 username和 email的值存放在 user這個物件中

bull 用 jQuery的 $post方法將 user的資料傳到 Server

bull 一旦成功取得資料後透過 greet這個 function組成回報給 user的訊息

bull 清空原本給使用者填資料的表單

bull 將 Server回傳的 usernameemail和 id這 3個資料組成回應的訊息

bull 將訊息放到原本表單的位置

經過以上的處理後一個 Ajax的表單的基本功能已經完成

接著進行 nodejs主要程式的編輯部分程式碼如下

var fs = require(rdquofsrdquo)

64 6 Express介紹

Nodejs中文電子書

qs = require(rsquoquerystringrsquo)

if (action === rdquoSignuprdquo)

formData = rsquorsquo

reqon(rdquodatardquo function (data)

formData += data

)

reqon(rdquoendrdquo function ()

var msg

user = qsparse(formData)

userid = rdquo123456rdquo

msg = JSONstringify(user)

reswriteHead(200 rdquoContent-Typerdquordquoapplicationjson charset=utf-8rdquordquoContent-Lengthrdquomsglength)

resend(msg)

)

這裡的程式和前面 POST範例基本上大同小異差別在

bull 幫 user的資料加上 id隨意存放一些文字進去讓 Server回傳的資料多於 Client端傳上來的不然會覺得 Server都沒做事

bull 增加了 msg 這個變數存放將 user 物件 JSON 文字化的結果JSONstringify這個轉換函式是 V8引擎所提供的如果你好奇的話

bull 大重點來了我們要告訴 Client端這次回傳的資料格式是 JSON所在 Content-type和 Content-Length要提供給 Client

Server很輕鬆就完成任務了最後進行程式測試啟動 nodejs主程式之後開啟瀏覽器就會看到表單填寫完畢按下送出就可以看到結果了

最後 nodejs本篇範例程式碼如下

var http = require(rsquohttprsquo)

url = require(rsquourlrsquo)

fs = require(rdquofsrdquo)

qs = require(rsquoquerystringrsquo)

server

68 Express AJAX應用範例 65

Nodejs中文電子書

server = httpcreateServer(function (reqres)

var urlData

encode = rdquoutf8rdquo

filePath = rdquoviewexpress_ajax_example_formhtmlrdquo

formData

action

urlData = urlparse(requrltrue)

action = urlDatapathname

if (action === rdquoSignuprdquo)

formData = rsquorsquo

reqon(rdquodatardquo function (data)

formData += data

)

reqon(rdquoendrdquo function ()

var msg

user = qsparse(formData)

userid = rdquo123456rdquo

msg = JSONstringify(user)

reswriteHead(200 rdquoContent-TyperdquordquoapplicationjsonrdquordquoContent-Lengthrdquomsglength)

resend(msg)

)

else

fsreadFile(filePath encode function(err file)

reswriteHead(200 rdquoContent-Typerdquordquotexthtml charset=utf-8rdquo)

reswrite(file)

resend()

)

)

serverlisten(3000)

66 6 Express介紹

Nodejs中文電子書

consolelog(rsquoServer 跑起來了現在時間是rsquo + new Date())

69 原始資料提供

bull [NodeJS初學者筆記 (1)-用 GET傳送資料] (httpithelpithomecomtwquestion10087402)

bull [NodeJS初學者筆記 (2)-用 POST傳送資料] (httpithelpithomecomtwquestion10087489)

bull [NodeJS 初學者筆記 (3)-用 Ajax 傳送資料] (httpithelpithomecomtwquestion10087627)

69 原始資料提供 67

Nodejs中文電子書

68 6 Express介紹

7

CoffeeScript

bull Spine

bull Hem

bull Spineapp

Letrsquos Have a Cup of CoffeeScript

bull es5-shim ECMAScript 5 compatibility shims for legacy JavaScript engines

ESnext

node-iform - A middleware for node-validator httpsgithubcomguileennode-iform

69

Nodejs中文電子書

70 7 CoffeeScript

8

製作一個 Hubot的 Plurk Adapter

81 應用事項提醒

此應用範例以CoffeeScript編寫主要程式架構採用Github釋放的Hubot專案以下有幾個主要注意事項請讀者注意

bull Hubot 一開始的架構除了內建的兩個 Adapter 之外其餘都要以Nodejs的Module方式才能運作

bull Hubot的 binhubot裡面寫著 npm install所以不管你怎麼改原始碼也不能改變第一點的狀況

其實上述都在討論同一件事情NodeJS的模組而且模組不能設定相對路徑之類的來安裝一定要包裝成 npm相符和格式才有辦法正確執行

82 建立 Adapter

首先需要準備兩個檔案

bull packagejson

bull plurkcoffee

71

Nodejs中文電子書

有這兩個就足以變成 NodeJS的模組了在開始 Coding之前先來設定一下 packagejson相依性

rdquonamerdquo rdquohubot-plurkrdquo

rdquoversionrdquo rdquo011rdquo

rdquomainrdquo rdquoplurkrdquo

rdquodependenciesrdquo

rdquohubotrdquo rdquogt=205rdquo

rdquooauthrdquo rdquordquo

rdquocronrdquordquordquo

這邊會使用到NodeJS的 cron模組(Module)而登入 Plurk需要OAuth標準授權才行所以也需要加載 OAuth模組

注意Hubot在讀取非內建模組時會自動在前面加上 hubot-的前置

83 建立 Robot跟 API

本篇應用基本上程式碼大部分參考 Hubot - Twitter Adapter來製作主要差異只有在使用 OAuth這個模組Twitter有 Streaming可用而 Plurk則得用 Comet方式來達到即時讀取

plurkcoffee程式碼

Robot = require(rdquohubotrdquo)robot()

Adapter = require(rdquohubtordquo)adapter()

EventEmitter = require(rdquoeventsrdquo)EventEmitter

oauth = require(rdquooauthrdquo)

cronJob = require(rdquocronrdquo)CronJob

class Plurk exntends Adapter

class PlurkStreaming exnteds EventEmitter

72 8 製作一個 Hubot的 Plurk Adapter

Nodejs中文電子書

先弄個基本架構主要 Class皆參考 Twitter Adapter命名方式接著再將 Plurk這個 Class增加幾個Method基本上只要有 run send reply就夠了而 run用來做初始化的部分

class Plurk entends Adapter

send (plurk id strings⋯) -gt

reply (plurk id strings⋯) -gt

run -gt

看起來有點東西接著來處理主要的 Plurk API結合部分

class PlurkStreaming extends EventEmitter

consuctor (options) -gt

plurk (callback) -gt

觀察河道

getChannel -gt

取得 Comet 網址

reply (plurk id message) -gt

回噗

acceptFriends -gt

接受好友

get (path callback) -gt

GET 請求

post (path body callback)-gt

POST 請求(其實是裝飾)

request (method path body callback)-gt

主要的 OAuth 請求

comet (server callback)-gt

噗浪的 Comet 傳回是 JavaScript Callback 要另外處理後才會變成 JSON

然後把注意力集中到 constructor上先把建構子弄好

constructor (options) -gt

super()

if optionskey and optionssecret and optionstoken and optionstoken secret

key = optionskey

secret = optionssecret

token = optionstoken

token secret = optionstoken secret

83 建立 Robot跟 API 73

Nodejs中文電子書

建立 OAuth 連接

consumer = new oauthOAuth(

rdquohttpwwwplurkcomOAuthrequest tokenrdquo

rdquohttpwwwplurkcomOAuthaccess tokenrdquo

key

secret

rdquo10rdquo

rdquohttpwwwplurkcomOAuthauthorizerdquo

rdquoHMAC-SHA1rdquo

)

domain = rdquowwwplurkcomrdquo

初始化取得 Comet 網址

do getChannel

else

throw new Error(rdquo 參數不足需要 Key Secret Token Token Secretrdquo)

接著來處理 request這個 method

request (method path body callback) -gt

記錄一下這次的 Request

consolelog(rdquohttpdomainpathrdquo)

Callback 這邊先不丟進去要用另一種方式處理

request = consumerget(rdquohttpdomainpathrdquo token token secret null)

requeston rdquoresponserdquo (res) -gt

reson rdquodatardquo (chunk) -gt

parseResponse(chunk+rsquorsquo callback)

reson rdquoendrdquo (data) -gt

consolelog rdquoEnd Request pathrdquo

reson rdquoerrorrdquo (data) -gt

consolelog rdquoError rdquo + data

requestend()

處理資料

parseResponse = (data callback) -gt

if datalength gt 0

用 TryCatch 避免處理 JSON 出錯導致整個中斷

try

callback null JSONparse(data)

catch err

74 8 製作一個 Hubot的 Plurk Adapter

Nodejs中文電子書

consolelog(rdquoError Parse JSONrdquo + data err)

繼續執行

callback null data ||

大致上就是這樣上面程式的架構已經將整個 Hubot Plurk Adapter完成因為在測試時竟然因為噗浪 Lag而沒讀到完整的 Comet資料然後造成程式異常為了避免這個問題發生因此需要加上為了完美呈現需要再

加上 Comet的處理所以要使用到 EventEmitter的功能

comet (server callback) -gt

在 Callback 裡面會找不到自身所以設定區域變數

self =

記錄一下這次的 Request

consolelog(rdquo[Comet] serverrdquo)

Callback 這邊先不丟進去要用另一種方式處理

request = consumerget(rdquohttpdomainpathrdquo token token secret null)

requeston rdquoresponserdquo (res) -gt

reson rdquodatardquo (chunk) -gt

parseResponse(chunk+rsquorsquo callback)

reson rdquoendrdquo (data) -gt

consolelog rdquoEnd Request pathrdquo

請求結束發出事件通知可以進行下一次請求

selfemit rdquonextPlurkrdquo

reson rdquoerrorrdquo (data) -gt

consolelog rdquoError rdquo + data

requestend()

處理資料

parseResponse = (data callback) -gt

if datalength gt 0

用 trycatch 避免失敗中斷

try

去掉 JavaScript 的 Callback

data = datamatch(CometChannelscriptCallback((+))s)

jsonData = rdquordquo

if data

83 建立 Robot跟 API 75

Nodejs中文電子書

jsonData = JSONparse(data[1])

else

如果沒有任何 Match 嘗試直接 parse

jsonData = JSONparse(data)

catch err

consolelog(rdquo[Comet] Errorrdquo data err)

用 TryCatch 避免處理 JSON 出錯導致整個中斷

try

只傳入 json 的 data 部分

callback null jsonDatadata

catch err

consolelog(rdquo[Comet]Error Parse JSONrdquo + data err)

繼續執行

callback null data ||

後面的 get跟 post就簡單多了

get (path callback) -gt

request(rdquoGETrdquo path null callback)

post (path body callback) -gt

request(rdquoPOSTrdquo path body callback)

接著處理取的 Comet網址的 getChannel

getChannel -gt

self =

get rdquoAPPRealtimegetUserChannelrdquo (error data) -gt

if error

檢查是否有 comet server

if datacomet server

selfchannel = datacomet server

如果沒有 Channel Ready 就嘗試連接會失敗

selfemit(rsquochannel readyrsquo)

那麼先來處理 Plurk Adaper好處理的部份

send (plruk id strings⋯)-gt

跟 Reply 一樣直接交給 reply 做

reply plurk id strings⋯

76 8 製作一個 Hubot的 Plurk Adapter

Nodejs中文電子書

reply (plurk id strings⋯) -gt

stringsforEach (message) =gt

botreply(plruk id message)

接著把 run處理好就可以上線運作摟

run -gt

self =

options =

key processenvHUBOT PLURK KEY

secret processenvHUBOT PLURK SECRET

token processenvHUBOT PLURK TOKEN

token secret processenvHUBOT PLURK TOKEN SECRET

創建剛剛的 API

bot = new PlurkStreaming(options)

依照 Twitter 的 new RobotTextMessage 會沒有反應所以參考 hubot-minecraft 的方式

r = robotconstructor

處理噗浪河道訊息

doPlurk = (data)-gt

檢查是否為回噗

if dataresponse

datacontent raw = dataresponsecontent raw

datauser id = dataresponseuser id

確定有噗浪 ID 跟訊息

if dataplurk id and datacontent raw

selfreceive new rTextMessage(dataplurk id datacontent raw)

取得 Comet Server 完成開始第一次 Comet 連接

boton rdquochannel readyrdquo () -gt

botplurk selfdoPlurk

上一次 Comet 完成繼續 Polling

boton rdquonextPlurkrdquo ()-gt

botplurk selfdoPlurk

定時接受好友邀請

do botacceptFriends

bot = bot

83 建立 Robot跟 API 77

Nodejs中文電子書

終於完成 AdapterHubot專案裡面的 scripts資料夾內是互動部分不需要像 Adapter如此大費周章處理只新增檔案並且設計好對白之後就會回噗了我開發用的機器人在此大家可以去跟他玩玩[httpplurkcomelct9620 bot](httpplurkcomelct9620 bot)

84 原始資料提供

bull [製 作 一 個 Hubot 的 噗 浪 Adapter](http revo-skillfrosttw blog20120318create-a-hubot-plurk-adapter)

78 8 製作一個 Hubot的 Plurk Adapter

9

Nodejs好用工具介紹

91 logio

Real-time log monitoring in your browser

httplogioorg

nodejs (gt=0412 lt=0611)

npm config set unsafe-perm true

npm install -g --prefix=usrlocal logio

logio server start

httplocalhost8998

79

Nodejs中文電子書

80 9 Nodejs好用工具介紹

10

精選文章

101 Express

bull 使用 NodeJS + Express從 GETPOST Request取值 [邀稿中]

bull link2 [邀稿中]

bull link3 [邀稿中]

81

Nodejs中文電子書

82 10 精選文章

11

參考資源

111 Nodejs書籍

bull Programming Nodejs April 2012 (est) Travis Swicegood Pragmatic Bookshelf

bull e Little Book on CoffeeScript January 2012 Alex MacCaw OrsquoReilly Media

bull Node for Front-End Developers January 2012 Garann Means OrsquoReilly Media

bull Building Hypermedia APIs with HTML5 and Node November 2011 MikeAmundsen OrsquoReilly Media

bull Node Web Development August 2011 David Herron PacktPub

bull CoffeeScript Accelerated JavaScript Development July 2011 Trevor BurnhamPragmatic Bookshelf

bull Getting Started with GEO CouchDB and Nodejs July 2011 Mick omp-son OrsquoReilly Media

bull What Is Node July 2011 Brett McLaughlin OrsquoReilly Media

bull Node Up and Running May 2011 Tom Hughes-Croucher Mike WilsonOrsquoReilly Media

83

Nodejs中文電子書

bull Hands-on Nodejs e Nodejs introduction and API reference May 2011 Pe-dro Teixeira LeanPub

bull e Node Beginner Book Manuel Kiessling

bull Mastering Nodejs visionmedia

112 Nodejs影音教學

bull e Node Sessions e Best of OSCON 2011 August 2011 OrsquoReilly Media(Video)

bull Tom Hughes-Croucher on Node March 2011 OrsquoReilly Media (Video)

113 Nodejs教學網站

bull Node Tuts

114 Nodejs課程

bull Introduction To Nodejs Van Nguyen CodeLesson

84 11 參考資源

Page 63: Nodejs Wiki

Nodejs中文電子書

appget(rsquouseriddeletersquo function(req res)

ressend(rsquodeleting rsquo + requsername)

)

appget(rsquorsquo function(req res)

ressend(rsquoPage not foundrsquo 404)

)

consolelog(rsquostart express servernrsquo)

內部宣告一組預設的使用者分別給予名稱設定藉由 appall這個方法可以先將路由雛形建立再接下來設定 appget的路徑格式只要符合格式就會分配進入對應的方法中像上面的程式當中如果使用者輸入路徑為user0除了執行 appall程式之後執行 next方法就會對應到路徑設定為userid的這個方法當中如果使用者輸入路徑為user0edit就會執行到useridedit的對應方法

66 Express GET應用範例

我們準備一個使用 GET方法傳送資料的表單

ltDOCTYPE htmlgt

lthtmlgt

ltheadgt

ltmeta http-equiv=rdquoContent-Typerdquo content=rdquotexthtml charset=utf8rdquogt

lttitlegtNodejs 菜鳥筆記 (1)lttitlegt

ltlink rel=rdquostylesheetrdquo href=rdquocssstylecssrdquo type=rdquotextcssrdquo media=rdquoallrdquo gt

ltheadgt

ltbodygt

lth1gtNodejs 菜鳥筆記-註冊lth1gt

ltform id=rdquosignuprdquo method=rdquoGETrdquo action=rdquohttplocalhost3000Signuprdquogt

ltlabelgt 使用者名稱ltlabelgtltinput type=rdquotextrdquo id=rdquousernamerdquo name=rdquousernamerdquo gtltbrgt

ltlabelgt 電子郵件ltlabelgtltinput type=rdquotextrdquo id=rdquoemailrdquo name=rdquoemailrdquo gtltbrgt

ltinput type=rdquosubmitrdquo value=rdquo 註冊我的帳號rdquo gtltbrgt

ltformgt

ltbodygt

lthtmlgt

66 Express GET應用範例 57

Nodejs中文電子書

這個表單沒有什麼特別的地方我們只需要看第 9 行form 使用的method是 GET然後 action是rdquohttplocalhost3000Signupldquo等一下我們要來撰寫Signup這個 URL Path的處理程式

處理 Signup行為

我們知道所謂的 GET方法會透過 URL來把表單的值給帶過去以上面的表單來說到時候 URL會以這樣的形式傳遞

httplocalhost3000Signupusername=xxxampemail=xxx

所以要能處理這樣的資料必須有以下功能

bull 解析 URL

bull 辨別動作是 Signup

bull 解析出 username和 email

一旦能取得 username和 email的值程式就能加以應用了

處理 Signup的程式碼雛形

load module

var url = require(rsquourlrsquo)

urlData = urlparse(requrltrue)

action = urlDatapathname

reswriteHead(200 rdquoContent-Typerdquordquotexthtml charset=utf-8rdquo)

if (action === rdquoSignuprdquo)

user = urlDataquery

resend(rdquolth1gtrdquo + userusername + rdquo 歡迎您的加入 lth1gtltpgt 我們已經將會員啟用信寄至rdquo + useremail + rdquoltpgtrdquo)

首先需要加載 url module它是用來協助我們解析 URL的模組接著使用 urlparse方法第一個傳入 url字串變數也就是 requrl另外第二個參數的用意是設為 ture則引進 querystring模組來協助處理預設是 false它影響到的是 urlDataquery設為 true會傳回物件不然就只是一般的字串urlparse會將字串內容整理成一個物件我們把它指定給 urlData

action變數作為記錄 pathname這是我們稍後要來判斷目前網頁的動作是什麼接著先將 html表頭資訊 (Header)準備好再來判斷路徑邏輯如

58 6 Express介紹

Nodejs中文電子書

果是 Signup這個動作就把 urlDataquery裡的資料指定給 user然後輸出userusername和 useremail把使用者從表單註冊的資料顯示於頁面中

最後進行程式測試啟動 nodejs主程式之後開啟瀏覽器就會看到表單填寫完畢按下送出就可以看到結果了

完整 nodejs程式碼如下

var http = require(rsquohttprsquo)

url = require(rsquourlrsquo)

fs = require(rdquofsrdquo)

server

server = httpcreateServer(function (reqres)

var urlData

encode = rdquoutf8rdquo

filePath = rdquoviewexpress_get_example_formhtmlrdquo

action

urlData = urlparse(requrltrue)

action = urlDatapathname

reswriteHead(200 rdquoContent-Typerdquordquotexthtml charset=utf-8rdquo)

if (action === rdquoSignuprdquo)

user = urlDataquery

resend(rdquolth1gtrdquo + userusername + rdquo 歡迎您的加入 lth1gtltpgt 我們已經將會員啟用信寄至rdquo + useremail + rdquoltpgtrdquo)

else

fsreadFile(filePath encode function(err file)

reswrite(file)

resend()

)

)

serverlisten(3000)

consolelog(rsquoServer 跑起來了現在時間是rsquo + new Date())

66 Express GET應用範例 59

Nodejs中文電子書

67 Express POST應用範例

一開始準備基本的 html表單傳送內容以 POST方式form的 action屬性設定為 POST其餘 html內容與前一個範例應用相同

ltDOCTYPE htmlgt

lthtmlgt

ltheadgt

ltmeta http-equiv=rdquoContent-Typerdquo content=rdquotexthtml charset=utf8rdquogt

lttitlegtNodejs 菜鳥筆記 (1)lttitlegt

ltlink rel=rdquostylesheetrdquo href=rdquocssstylecssrdquo type=rdquotextcssrdquo media=rdquoallrdquo gt

ltheadgt

ltbodygt

lth1gtNodejs 菜鳥筆記-註冊lth1gt

ltform id=rdquosignuprdquo method=rdquoPOSTrdquo action=rdquohttplocalhost3000Signuprdquogt

ltlabelgt 使用者名稱ltlabelgtltinput type=rdquotextrdquo id=rdquousernamerdquo name=rdquousernamerdquo gtltbrgt

ltlabelgt 電子郵件ltlabelgtltinput type=rdquotextrdquo id=rdquoemailrdquo name=rdquoemailrdquo gtltbrgt

ltinput type=rdquosubmitrdquo value=rdquo 註冊我的帳號rdquo gtltbrgt

ltformgt

ltbodygt

lthtmlgt

nodejs的程式處理邏輯與前面 GET範例類似部分程式碼如下

qs = require(rsquoquerystringrsquo)

if (action === rdquoSignuprdquo)

formData = rsquorsquo

reqon(rdquodatardquo function (data)

formData += data

)

reqon(rdquoendrdquo function ()

user = qsparse(formData)

resend(rdquolth1gtrdquo + userusername + rdquo 歡迎您的加入 lth1gtltpgt 我們已經將會員啟用信寄至rdquo + useremail + rdquoltpgtrdquo)

)

60 6 Express介紹

Nodejs中文電子書

主要加入了rsquoquerystringrsquo這個moduel方便我們等一下解析由表單 POST回來的資料另外加入一個 formData的變數用來搜集待等一下表單回傳的資料前面的 GET範例我們只從 req拿出 url的資料這次要在利用req身上的事件處理

JavaScript 在訂閱事件時使用 addEventListener而 nodejs 使用的則是on這邊加上了監聽 data的事件會在瀏覽器傳送資料到Web Server時被執行參數是它所接收到的資料型態是字串

接著再增加 end的事件當瀏覽器的請求事件結束時它就會動作

由於瀏覽器使用 POST在上傳資料時會將資料一塊塊地上傳因為我們在監聽 data事件時透過 formData變數將它累加起來lt不過由於我們

上傳的資料很少一次就結束不過如果日後需要傳的是資料比較大的檔

案這個累加動作就很重要

當資料傳完就進到 end 事件中會用到 qsparse 來解析 formDataformData的內容是字串內容是

username=wordsmithampemail=wordsmith40somewhere

而 qsparse可以幫我們把這個 querystring轉成物件的格式也就是

username=wordsmithampemail=wordsmith40somewhere

一旦轉成物件並指定給 user之後其他的事情就和 GET方法時操作的一樣寫 response的表頭將內容回傳並將 userusername和 useremail代入到內容中

修改完成後接著執行 nodejs程式啟動 web server開啟瀏覽器進入表單測試看看POST的方式能否順利運作

完整程式碼如下

var http = require(rsquohttprsquo)

url = require(rsquourlrsquo)

fs = require(rdquofsrdquo)

qs = require(rsquoquerystringrsquo)

server

server = httpcreateServer(function (reqres)

var urlData

67 Express POST應用範例 61

Nodejs中文電子書

encode = rdquoutf8rdquo

filePath = rdquoviewexpress_post_example_formhtmlrdquo

formData

action

urlData = urlparse(requrltrue)

action = urlDatapathname

reswriteHead(200 rdquoContent-Typerdquordquotexthtml charset=utf-8rdquo)

if (action === rdquoSignuprdquo)

formData = rsquorsquo

reqon(rdquodatardquo function (data)

formData += data

)

reqon(rdquoendrdquo function ()

user = qsparse(formData)

resend(rdquolth1gtrdquo + userusername + rdquo 歡迎您的加入 lth1gtltpgt 我們已經將會員啟用信寄至rdquo + useremail + rdquoltpgtrdquo)

)

else

fsreadFile(filePath encode function(err file)

reswrite(file)

resend()

)

)

serverlisten(3000)

consolelog(rsquoServer 跑起來了現在時間是rsquo + new Date())

62 6 Express介紹

Nodejs中文電子書

68 Express AJAX應用範例

在 Nodejs要使用 Ajax傳送資料並且與之互動在接受資料的部份沒有太大的差別client端不是用 GET就是用 POST來傳資料重點在處理完後用 JSON格式回傳當然 Ajax不見得只傳 JSON格式有時是回傳一段 HTML碼不過後者對伺服器來說基本上就和前兩篇沒有差別了所以我們還是以回傳 JSON做為這一回的主題

這一回其實大多數的工作都會落在前端 Ajax上面前端要負責發送與接收資料並在接收資料後撤掉原先發送資料的表單並將取得的資料

改成 HTML格式之後放上頁面

首先先準備 HTML靜態頁面資料

ltDOCTYPE htmlgt

lthtmlgt

ltheadgt

lttitlegtNodejs 菜鳥筆記 (3)lttitlegt

ltlink rel=rdquostylesheetrdquo href=rdquocssstylecssrdquo type=rdquotextcssrdquo media=rdquoallrdquo gt

ltscript src=rdquohttpsajaxgoogleapiscomajaxlibsjquery171jqueryminjsrdquogtltscriptgt

ltheadgt

ltbodygt

lth1gtNodejs 菜鳥筆記-註冊 (用 Ajax)lth1gt

ltform id=rdquosignuprdquo action=rdquoSignuprdquo method=rdquoPOSTrdquogt

ltlabelgt 使用者名稱ltlabelgtltinput type=rdquotextrdquo id=rdquousernamerdquo name=rdquousernamerdquo gtltbrgt

ltlabelgt 電子郵件ltlabelgtltinput type=rdquotextrdquo id=rdquoemailrdquo name=rdquoemailrdquo gtltbrgt

ltinput type=rdquosubmitrdquo value=rdquo 註冊我的帳號rdquo gtltbrgt

ltformgt

ltdiv id=rdquoresultrdquogt

ltdivgt

ltscript type=rdquotextjavascriptrdquogt

$(function()$(rsquosignup input[type=rdquosubmitrdquo]rsquo)click(function(e)

var user =

userusername = $(rdquousernamerdquo)val()useremail = $(rdquoemailrdquo)val()

$post(rdquoSignuprdquouserfunction(data)greet(data)

)

68 Express AJAX應用範例 63

Nodejs中文電子書

epreventDefault()

)

var greet = function(msg)

var resultNode = $(rsquoresultrsquo)greeting = $(rsquolth2gtHirsquo+msgusername+rsquo 你的會員 id 是rsquo+ msgid +rsquo 我們會將會員啟動信寄至rsquo+msgemail+rsquolth2gtrsquo)

$(rsquosignuprsquo)hide()resultNodehtml(rdquordquo)

resultNodeappend(greeting)

)

ltscriptgt

ltbodygt

lthtmlgt

HTML頁面上準備了一個表單用來傳送註冊資料接著直接引用了Google CDN來載入 jQuery用來幫我們處理 Ajax的工作這次要傳送和接收的工作很大的變動都在 HTML頁面上的 JavaScript當中我們要做的事有 (相關 jQuery處理這邊不多做贅述指提起主要功能解說)

bull 用 jQUery取得 submit按鈕綁定它的 click動作

bull 取得表單 username和 email的值存放在 user這個物件中

bull 用 jQuery的 $post方法將 user的資料傳到 Server

bull 一旦成功取得資料後透過 greet這個 function組成回報給 user的訊息

bull 清空原本給使用者填資料的表單

bull 將 Server回傳的 usernameemail和 id這 3個資料組成回應的訊息

bull 將訊息放到原本表單的位置

經過以上的處理後一個 Ajax的表單的基本功能已經完成

接著進行 nodejs主要程式的編輯部分程式碼如下

var fs = require(rdquofsrdquo)

64 6 Express介紹

Nodejs中文電子書

qs = require(rsquoquerystringrsquo)

if (action === rdquoSignuprdquo)

formData = rsquorsquo

reqon(rdquodatardquo function (data)

formData += data

)

reqon(rdquoendrdquo function ()

var msg

user = qsparse(formData)

userid = rdquo123456rdquo

msg = JSONstringify(user)

reswriteHead(200 rdquoContent-Typerdquordquoapplicationjson charset=utf-8rdquordquoContent-Lengthrdquomsglength)

resend(msg)

)

這裡的程式和前面 POST範例基本上大同小異差別在

bull 幫 user的資料加上 id隨意存放一些文字進去讓 Server回傳的資料多於 Client端傳上來的不然會覺得 Server都沒做事

bull 增加了 msg 這個變數存放將 user 物件 JSON 文字化的結果JSONstringify這個轉換函式是 V8引擎所提供的如果你好奇的話

bull 大重點來了我們要告訴 Client端這次回傳的資料格式是 JSON所在 Content-type和 Content-Length要提供給 Client

Server很輕鬆就完成任務了最後進行程式測試啟動 nodejs主程式之後開啟瀏覽器就會看到表單填寫完畢按下送出就可以看到結果了

最後 nodejs本篇範例程式碼如下

var http = require(rsquohttprsquo)

url = require(rsquourlrsquo)

fs = require(rdquofsrdquo)

qs = require(rsquoquerystringrsquo)

server

68 Express AJAX應用範例 65

Nodejs中文電子書

server = httpcreateServer(function (reqres)

var urlData

encode = rdquoutf8rdquo

filePath = rdquoviewexpress_ajax_example_formhtmlrdquo

formData

action

urlData = urlparse(requrltrue)

action = urlDatapathname

if (action === rdquoSignuprdquo)

formData = rsquorsquo

reqon(rdquodatardquo function (data)

formData += data

)

reqon(rdquoendrdquo function ()

var msg

user = qsparse(formData)

userid = rdquo123456rdquo

msg = JSONstringify(user)

reswriteHead(200 rdquoContent-TyperdquordquoapplicationjsonrdquordquoContent-Lengthrdquomsglength)

resend(msg)

)

else

fsreadFile(filePath encode function(err file)

reswriteHead(200 rdquoContent-Typerdquordquotexthtml charset=utf-8rdquo)

reswrite(file)

resend()

)

)

serverlisten(3000)

66 6 Express介紹

Nodejs中文電子書

consolelog(rsquoServer 跑起來了現在時間是rsquo + new Date())

69 原始資料提供

bull [NodeJS初學者筆記 (1)-用 GET傳送資料] (httpithelpithomecomtwquestion10087402)

bull [NodeJS初學者筆記 (2)-用 POST傳送資料] (httpithelpithomecomtwquestion10087489)

bull [NodeJS 初學者筆記 (3)-用 Ajax 傳送資料] (httpithelpithomecomtwquestion10087627)

69 原始資料提供 67

Nodejs中文電子書

68 6 Express介紹

7

CoffeeScript

bull Spine

bull Hem

bull Spineapp

Letrsquos Have a Cup of CoffeeScript

bull es5-shim ECMAScript 5 compatibility shims for legacy JavaScript engines

ESnext

node-iform - A middleware for node-validator httpsgithubcomguileennode-iform

69

Nodejs中文電子書

70 7 CoffeeScript

8

製作一個 Hubot的 Plurk Adapter

81 應用事項提醒

此應用範例以CoffeeScript編寫主要程式架構採用Github釋放的Hubot專案以下有幾個主要注意事項請讀者注意

bull Hubot 一開始的架構除了內建的兩個 Adapter 之外其餘都要以Nodejs的Module方式才能運作

bull Hubot的 binhubot裡面寫著 npm install所以不管你怎麼改原始碼也不能改變第一點的狀況

其實上述都在討論同一件事情NodeJS的模組而且模組不能設定相對路徑之類的來安裝一定要包裝成 npm相符和格式才有辦法正確執行

82 建立 Adapter

首先需要準備兩個檔案

bull packagejson

bull plurkcoffee

71

Nodejs中文電子書

有這兩個就足以變成 NodeJS的模組了在開始 Coding之前先來設定一下 packagejson相依性

rdquonamerdquo rdquohubot-plurkrdquo

rdquoversionrdquo rdquo011rdquo

rdquomainrdquo rdquoplurkrdquo

rdquodependenciesrdquo

rdquohubotrdquo rdquogt=205rdquo

rdquooauthrdquo rdquordquo

rdquocronrdquordquordquo

這邊會使用到NodeJS的 cron模組(Module)而登入 Plurk需要OAuth標準授權才行所以也需要加載 OAuth模組

注意Hubot在讀取非內建模組時會自動在前面加上 hubot-的前置

83 建立 Robot跟 API

本篇應用基本上程式碼大部分參考 Hubot - Twitter Adapter來製作主要差異只有在使用 OAuth這個模組Twitter有 Streaming可用而 Plurk則得用 Comet方式來達到即時讀取

plurkcoffee程式碼

Robot = require(rdquohubotrdquo)robot()

Adapter = require(rdquohubtordquo)adapter()

EventEmitter = require(rdquoeventsrdquo)EventEmitter

oauth = require(rdquooauthrdquo)

cronJob = require(rdquocronrdquo)CronJob

class Plurk exntends Adapter

class PlurkStreaming exnteds EventEmitter

72 8 製作一個 Hubot的 Plurk Adapter

Nodejs中文電子書

先弄個基本架構主要 Class皆參考 Twitter Adapter命名方式接著再將 Plurk這個 Class增加幾個Method基本上只要有 run send reply就夠了而 run用來做初始化的部分

class Plurk entends Adapter

send (plurk id strings⋯) -gt

reply (plurk id strings⋯) -gt

run -gt

看起來有點東西接著來處理主要的 Plurk API結合部分

class PlurkStreaming extends EventEmitter

consuctor (options) -gt

plurk (callback) -gt

觀察河道

getChannel -gt

取得 Comet 網址

reply (plurk id message) -gt

回噗

acceptFriends -gt

接受好友

get (path callback) -gt

GET 請求

post (path body callback)-gt

POST 請求(其實是裝飾)

request (method path body callback)-gt

主要的 OAuth 請求

comet (server callback)-gt

噗浪的 Comet 傳回是 JavaScript Callback 要另外處理後才會變成 JSON

然後把注意力集中到 constructor上先把建構子弄好

constructor (options) -gt

super()

if optionskey and optionssecret and optionstoken and optionstoken secret

key = optionskey

secret = optionssecret

token = optionstoken

token secret = optionstoken secret

83 建立 Robot跟 API 73

Nodejs中文電子書

建立 OAuth 連接

consumer = new oauthOAuth(

rdquohttpwwwplurkcomOAuthrequest tokenrdquo

rdquohttpwwwplurkcomOAuthaccess tokenrdquo

key

secret

rdquo10rdquo

rdquohttpwwwplurkcomOAuthauthorizerdquo

rdquoHMAC-SHA1rdquo

)

domain = rdquowwwplurkcomrdquo

初始化取得 Comet 網址

do getChannel

else

throw new Error(rdquo 參數不足需要 Key Secret Token Token Secretrdquo)

接著來處理 request這個 method

request (method path body callback) -gt

記錄一下這次的 Request

consolelog(rdquohttpdomainpathrdquo)

Callback 這邊先不丟進去要用另一種方式處理

request = consumerget(rdquohttpdomainpathrdquo token token secret null)

requeston rdquoresponserdquo (res) -gt

reson rdquodatardquo (chunk) -gt

parseResponse(chunk+rsquorsquo callback)

reson rdquoendrdquo (data) -gt

consolelog rdquoEnd Request pathrdquo

reson rdquoerrorrdquo (data) -gt

consolelog rdquoError rdquo + data

requestend()

處理資料

parseResponse = (data callback) -gt

if datalength gt 0

用 TryCatch 避免處理 JSON 出錯導致整個中斷

try

callback null JSONparse(data)

catch err

74 8 製作一個 Hubot的 Plurk Adapter

Nodejs中文電子書

consolelog(rdquoError Parse JSONrdquo + data err)

繼續執行

callback null data ||

大致上就是這樣上面程式的架構已經將整個 Hubot Plurk Adapter完成因為在測試時竟然因為噗浪 Lag而沒讀到完整的 Comet資料然後造成程式異常為了避免這個問題發生因此需要加上為了完美呈現需要再

加上 Comet的處理所以要使用到 EventEmitter的功能

comet (server callback) -gt

在 Callback 裡面會找不到自身所以設定區域變數

self =

記錄一下這次的 Request

consolelog(rdquo[Comet] serverrdquo)

Callback 這邊先不丟進去要用另一種方式處理

request = consumerget(rdquohttpdomainpathrdquo token token secret null)

requeston rdquoresponserdquo (res) -gt

reson rdquodatardquo (chunk) -gt

parseResponse(chunk+rsquorsquo callback)

reson rdquoendrdquo (data) -gt

consolelog rdquoEnd Request pathrdquo

請求結束發出事件通知可以進行下一次請求

selfemit rdquonextPlurkrdquo

reson rdquoerrorrdquo (data) -gt

consolelog rdquoError rdquo + data

requestend()

處理資料

parseResponse = (data callback) -gt

if datalength gt 0

用 trycatch 避免失敗中斷

try

去掉 JavaScript 的 Callback

data = datamatch(CometChannelscriptCallback((+))s)

jsonData = rdquordquo

if data

83 建立 Robot跟 API 75

Nodejs中文電子書

jsonData = JSONparse(data[1])

else

如果沒有任何 Match 嘗試直接 parse

jsonData = JSONparse(data)

catch err

consolelog(rdquo[Comet] Errorrdquo data err)

用 TryCatch 避免處理 JSON 出錯導致整個中斷

try

只傳入 json 的 data 部分

callback null jsonDatadata

catch err

consolelog(rdquo[Comet]Error Parse JSONrdquo + data err)

繼續執行

callback null data ||

後面的 get跟 post就簡單多了

get (path callback) -gt

request(rdquoGETrdquo path null callback)

post (path body callback) -gt

request(rdquoPOSTrdquo path body callback)

接著處理取的 Comet網址的 getChannel

getChannel -gt

self =

get rdquoAPPRealtimegetUserChannelrdquo (error data) -gt

if error

檢查是否有 comet server

if datacomet server

selfchannel = datacomet server

如果沒有 Channel Ready 就嘗試連接會失敗

selfemit(rsquochannel readyrsquo)

那麼先來處理 Plurk Adaper好處理的部份

send (plruk id strings⋯)-gt

跟 Reply 一樣直接交給 reply 做

reply plurk id strings⋯

76 8 製作一個 Hubot的 Plurk Adapter

Nodejs中文電子書

reply (plurk id strings⋯) -gt

stringsforEach (message) =gt

botreply(plruk id message)

接著把 run處理好就可以上線運作摟

run -gt

self =

options =

key processenvHUBOT PLURK KEY

secret processenvHUBOT PLURK SECRET

token processenvHUBOT PLURK TOKEN

token secret processenvHUBOT PLURK TOKEN SECRET

創建剛剛的 API

bot = new PlurkStreaming(options)

依照 Twitter 的 new RobotTextMessage 會沒有反應所以參考 hubot-minecraft 的方式

r = robotconstructor

處理噗浪河道訊息

doPlurk = (data)-gt

檢查是否為回噗

if dataresponse

datacontent raw = dataresponsecontent raw

datauser id = dataresponseuser id

確定有噗浪 ID 跟訊息

if dataplurk id and datacontent raw

selfreceive new rTextMessage(dataplurk id datacontent raw)

取得 Comet Server 完成開始第一次 Comet 連接

boton rdquochannel readyrdquo () -gt

botplurk selfdoPlurk

上一次 Comet 完成繼續 Polling

boton rdquonextPlurkrdquo ()-gt

botplurk selfdoPlurk

定時接受好友邀請

do botacceptFriends

bot = bot

83 建立 Robot跟 API 77

Nodejs中文電子書

終於完成 AdapterHubot專案裡面的 scripts資料夾內是互動部分不需要像 Adapter如此大費周章處理只新增檔案並且設計好對白之後就會回噗了我開發用的機器人在此大家可以去跟他玩玩[httpplurkcomelct9620 bot](httpplurkcomelct9620 bot)

84 原始資料提供

bull [製 作 一 個 Hubot 的 噗 浪 Adapter](http revo-skillfrosttw blog20120318create-a-hubot-plurk-adapter)

78 8 製作一個 Hubot的 Plurk Adapter

9

Nodejs好用工具介紹

91 logio

Real-time log monitoring in your browser

httplogioorg

nodejs (gt=0412 lt=0611)

npm config set unsafe-perm true

npm install -g --prefix=usrlocal logio

logio server start

httplocalhost8998

79

Nodejs中文電子書

80 9 Nodejs好用工具介紹

10

精選文章

101 Express

bull 使用 NodeJS + Express從 GETPOST Request取值 [邀稿中]

bull link2 [邀稿中]

bull link3 [邀稿中]

81

Nodejs中文電子書

82 10 精選文章

11

參考資源

111 Nodejs書籍

bull Programming Nodejs April 2012 (est) Travis Swicegood Pragmatic Bookshelf

bull e Little Book on CoffeeScript January 2012 Alex MacCaw OrsquoReilly Media

bull Node for Front-End Developers January 2012 Garann Means OrsquoReilly Media

bull Building Hypermedia APIs with HTML5 and Node November 2011 MikeAmundsen OrsquoReilly Media

bull Node Web Development August 2011 David Herron PacktPub

bull CoffeeScript Accelerated JavaScript Development July 2011 Trevor BurnhamPragmatic Bookshelf

bull Getting Started with GEO CouchDB and Nodejs July 2011 Mick omp-son OrsquoReilly Media

bull What Is Node July 2011 Brett McLaughlin OrsquoReilly Media

bull Node Up and Running May 2011 Tom Hughes-Croucher Mike WilsonOrsquoReilly Media

83

Nodejs中文電子書

bull Hands-on Nodejs e Nodejs introduction and API reference May 2011 Pe-dro Teixeira LeanPub

bull e Node Beginner Book Manuel Kiessling

bull Mastering Nodejs visionmedia

112 Nodejs影音教學

bull e Node Sessions e Best of OSCON 2011 August 2011 OrsquoReilly Media(Video)

bull Tom Hughes-Croucher on Node March 2011 OrsquoReilly Media (Video)

113 Nodejs教學網站

bull Node Tuts

114 Nodejs課程

bull Introduction To Nodejs Van Nguyen CodeLesson

84 11 參考資源

Page 64: Nodejs Wiki

Nodejs中文電子書

這個表單沒有什麼特別的地方我們只需要看第 9 行form 使用的method是 GET然後 action是rdquohttplocalhost3000Signupldquo等一下我們要來撰寫Signup這個 URL Path的處理程式

處理 Signup行為

我們知道所謂的 GET方法會透過 URL來把表單的值給帶過去以上面的表單來說到時候 URL會以這樣的形式傳遞

httplocalhost3000Signupusername=xxxampemail=xxx

所以要能處理這樣的資料必須有以下功能

bull 解析 URL

bull 辨別動作是 Signup

bull 解析出 username和 email

一旦能取得 username和 email的值程式就能加以應用了

處理 Signup的程式碼雛形

load module

var url = require(rsquourlrsquo)

urlData = urlparse(requrltrue)

action = urlDatapathname

reswriteHead(200 rdquoContent-Typerdquordquotexthtml charset=utf-8rdquo)

if (action === rdquoSignuprdquo)

user = urlDataquery

resend(rdquolth1gtrdquo + userusername + rdquo 歡迎您的加入 lth1gtltpgt 我們已經將會員啟用信寄至rdquo + useremail + rdquoltpgtrdquo)

首先需要加載 url module它是用來協助我們解析 URL的模組接著使用 urlparse方法第一個傳入 url字串變數也就是 requrl另外第二個參數的用意是設為 ture則引進 querystring模組來協助處理預設是 false它影響到的是 urlDataquery設為 true會傳回物件不然就只是一般的字串urlparse會將字串內容整理成一個物件我們把它指定給 urlData

action變數作為記錄 pathname這是我們稍後要來判斷目前網頁的動作是什麼接著先將 html表頭資訊 (Header)準備好再來判斷路徑邏輯如

58 6 Express介紹

Nodejs中文電子書

果是 Signup這個動作就把 urlDataquery裡的資料指定給 user然後輸出userusername和 useremail把使用者從表單註冊的資料顯示於頁面中

最後進行程式測試啟動 nodejs主程式之後開啟瀏覽器就會看到表單填寫完畢按下送出就可以看到結果了

完整 nodejs程式碼如下

var http = require(rsquohttprsquo)

url = require(rsquourlrsquo)

fs = require(rdquofsrdquo)

server

server = httpcreateServer(function (reqres)

var urlData

encode = rdquoutf8rdquo

filePath = rdquoviewexpress_get_example_formhtmlrdquo

action

urlData = urlparse(requrltrue)

action = urlDatapathname

reswriteHead(200 rdquoContent-Typerdquordquotexthtml charset=utf-8rdquo)

if (action === rdquoSignuprdquo)

user = urlDataquery

resend(rdquolth1gtrdquo + userusername + rdquo 歡迎您的加入 lth1gtltpgt 我們已經將會員啟用信寄至rdquo + useremail + rdquoltpgtrdquo)

else

fsreadFile(filePath encode function(err file)

reswrite(file)

resend()

)

)

serverlisten(3000)

consolelog(rsquoServer 跑起來了現在時間是rsquo + new Date())

66 Express GET應用範例 59

Nodejs中文電子書

67 Express POST應用範例

一開始準備基本的 html表單傳送內容以 POST方式form的 action屬性設定為 POST其餘 html內容與前一個範例應用相同

ltDOCTYPE htmlgt

lthtmlgt

ltheadgt

ltmeta http-equiv=rdquoContent-Typerdquo content=rdquotexthtml charset=utf8rdquogt

lttitlegtNodejs 菜鳥筆記 (1)lttitlegt

ltlink rel=rdquostylesheetrdquo href=rdquocssstylecssrdquo type=rdquotextcssrdquo media=rdquoallrdquo gt

ltheadgt

ltbodygt

lth1gtNodejs 菜鳥筆記-註冊lth1gt

ltform id=rdquosignuprdquo method=rdquoPOSTrdquo action=rdquohttplocalhost3000Signuprdquogt

ltlabelgt 使用者名稱ltlabelgtltinput type=rdquotextrdquo id=rdquousernamerdquo name=rdquousernamerdquo gtltbrgt

ltlabelgt 電子郵件ltlabelgtltinput type=rdquotextrdquo id=rdquoemailrdquo name=rdquoemailrdquo gtltbrgt

ltinput type=rdquosubmitrdquo value=rdquo 註冊我的帳號rdquo gtltbrgt

ltformgt

ltbodygt

lthtmlgt

nodejs的程式處理邏輯與前面 GET範例類似部分程式碼如下

qs = require(rsquoquerystringrsquo)

if (action === rdquoSignuprdquo)

formData = rsquorsquo

reqon(rdquodatardquo function (data)

formData += data

)

reqon(rdquoendrdquo function ()

user = qsparse(formData)

resend(rdquolth1gtrdquo + userusername + rdquo 歡迎您的加入 lth1gtltpgt 我們已經將會員啟用信寄至rdquo + useremail + rdquoltpgtrdquo)

)

60 6 Express介紹

Nodejs中文電子書

主要加入了rsquoquerystringrsquo這個moduel方便我們等一下解析由表單 POST回來的資料另外加入一個 formData的變數用來搜集待等一下表單回傳的資料前面的 GET範例我們只從 req拿出 url的資料這次要在利用req身上的事件處理

JavaScript 在訂閱事件時使用 addEventListener而 nodejs 使用的則是on這邊加上了監聽 data的事件會在瀏覽器傳送資料到Web Server時被執行參數是它所接收到的資料型態是字串

接著再增加 end的事件當瀏覽器的請求事件結束時它就會動作

由於瀏覽器使用 POST在上傳資料時會將資料一塊塊地上傳因為我們在監聽 data事件時透過 formData變數將它累加起來lt不過由於我們

上傳的資料很少一次就結束不過如果日後需要傳的是資料比較大的檔

案這個累加動作就很重要

當資料傳完就進到 end 事件中會用到 qsparse 來解析 formDataformData的內容是字串內容是

username=wordsmithampemail=wordsmith40somewhere

而 qsparse可以幫我們把這個 querystring轉成物件的格式也就是

username=wordsmithampemail=wordsmith40somewhere

一旦轉成物件並指定給 user之後其他的事情就和 GET方法時操作的一樣寫 response的表頭將內容回傳並將 userusername和 useremail代入到內容中

修改完成後接著執行 nodejs程式啟動 web server開啟瀏覽器進入表單測試看看POST的方式能否順利運作

完整程式碼如下

var http = require(rsquohttprsquo)

url = require(rsquourlrsquo)

fs = require(rdquofsrdquo)

qs = require(rsquoquerystringrsquo)

server

server = httpcreateServer(function (reqres)

var urlData

67 Express POST應用範例 61

Nodejs中文電子書

encode = rdquoutf8rdquo

filePath = rdquoviewexpress_post_example_formhtmlrdquo

formData

action

urlData = urlparse(requrltrue)

action = urlDatapathname

reswriteHead(200 rdquoContent-Typerdquordquotexthtml charset=utf-8rdquo)

if (action === rdquoSignuprdquo)

formData = rsquorsquo

reqon(rdquodatardquo function (data)

formData += data

)

reqon(rdquoendrdquo function ()

user = qsparse(formData)

resend(rdquolth1gtrdquo + userusername + rdquo 歡迎您的加入 lth1gtltpgt 我們已經將會員啟用信寄至rdquo + useremail + rdquoltpgtrdquo)

)

else

fsreadFile(filePath encode function(err file)

reswrite(file)

resend()

)

)

serverlisten(3000)

consolelog(rsquoServer 跑起來了現在時間是rsquo + new Date())

62 6 Express介紹

Nodejs中文電子書

68 Express AJAX應用範例

在 Nodejs要使用 Ajax傳送資料並且與之互動在接受資料的部份沒有太大的差別client端不是用 GET就是用 POST來傳資料重點在處理完後用 JSON格式回傳當然 Ajax不見得只傳 JSON格式有時是回傳一段 HTML碼不過後者對伺服器來說基本上就和前兩篇沒有差別了所以我們還是以回傳 JSON做為這一回的主題

這一回其實大多數的工作都會落在前端 Ajax上面前端要負責發送與接收資料並在接收資料後撤掉原先發送資料的表單並將取得的資料

改成 HTML格式之後放上頁面

首先先準備 HTML靜態頁面資料

ltDOCTYPE htmlgt

lthtmlgt

ltheadgt

lttitlegtNodejs 菜鳥筆記 (3)lttitlegt

ltlink rel=rdquostylesheetrdquo href=rdquocssstylecssrdquo type=rdquotextcssrdquo media=rdquoallrdquo gt

ltscript src=rdquohttpsajaxgoogleapiscomajaxlibsjquery171jqueryminjsrdquogtltscriptgt

ltheadgt

ltbodygt

lth1gtNodejs 菜鳥筆記-註冊 (用 Ajax)lth1gt

ltform id=rdquosignuprdquo action=rdquoSignuprdquo method=rdquoPOSTrdquogt

ltlabelgt 使用者名稱ltlabelgtltinput type=rdquotextrdquo id=rdquousernamerdquo name=rdquousernamerdquo gtltbrgt

ltlabelgt 電子郵件ltlabelgtltinput type=rdquotextrdquo id=rdquoemailrdquo name=rdquoemailrdquo gtltbrgt

ltinput type=rdquosubmitrdquo value=rdquo 註冊我的帳號rdquo gtltbrgt

ltformgt

ltdiv id=rdquoresultrdquogt

ltdivgt

ltscript type=rdquotextjavascriptrdquogt

$(function()$(rsquosignup input[type=rdquosubmitrdquo]rsquo)click(function(e)

var user =

userusername = $(rdquousernamerdquo)val()useremail = $(rdquoemailrdquo)val()

$post(rdquoSignuprdquouserfunction(data)greet(data)

)

68 Express AJAX應用範例 63

Nodejs中文電子書

epreventDefault()

)

var greet = function(msg)

var resultNode = $(rsquoresultrsquo)greeting = $(rsquolth2gtHirsquo+msgusername+rsquo 你的會員 id 是rsquo+ msgid +rsquo 我們會將會員啟動信寄至rsquo+msgemail+rsquolth2gtrsquo)

$(rsquosignuprsquo)hide()resultNodehtml(rdquordquo)

resultNodeappend(greeting)

)

ltscriptgt

ltbodygt

lthtmlgt

HTML頁面上準備了一個表單用來傳送註冊資料接著直接引用了Google CDN來載入 jQuery用來幫我們處理 Ajax的工作這次要傳送和接收的工作很大的變動都在 HTML頁面上的 JavaScript當中我們要做的事有 (相關 jQuery處理這邊不多做贅述指提起主要功能解說)

bull 用 jQUery取得 submit按鈕綁定它的 click動作

bull 取得表單 username和 email的值存放在 user這個物件中

bull 用 jQuery的 $post方法將 user的資料傳到 Server

bull 一旦成功取得資料後透過 greet這個 function組成回報給 user的訊息

bull 清空原本給使用者填資料的表單

bull 將 Server回傳的 usernameemail和 id這 3個資料組成回應的訊息

bull 將訊息放到原本表單的位置

經過以上的處理後一個 Ajax的表單的基本功能已經完成

接著進行 nodejs主要程式的編輯部分程式碼如下

var fs = require(rdquofsrdquo)

64 6 Express介紹

Nodejs中文電子書

qs = require(rsquoquerystringrsquo)

if (action === rdquoSignuprdquo)

formData = rsquorsquo

reqon(rdquodatardquo function (data)

formData += data

)

reqon(rdquoendrdquo function ()

var msg

user = qsparse(formData)

userid = rdquo123456rdquo

msg = JSONstringify(user)

reswriteHead(200 rdquoContent-Typerdquordquoapplicationjson charset=utf-8rdquordquoContent-Lengthrdquomsglength)

resend(msg)

)

這裡的程式和前面 POST範例基本上大同小異差別在

bull 幫 user的資料加上 id隨意存放一些文字進去讓 Server回傳的資料多於 Client端傳上來的不然會覺得 Server都沒做事

bull 增加了 msg 這個變數存放將 user 物件 JSON 文字化的結果JSONstringify這個轉換函式是 V8引擎所提供的如果你好奇的話

bull 大重點來了我們要告訴 Client端這次回傳的資料格式是 JSON所在 Content-type和 Content-Length要提供給 Client

Server很輕鬆就完成任務了最後進行程式測試啟動 nodejs主程式之後開啟瀏覽器就會看到表單填寫完畢按下送出就可以看到結果了

最後 nodejs本篇範例程式碼如下

var http = require(rsquohttprsquo)

url = require(rsquourlrsquo)

fs = require(rdquofsrdquo)

qs = require(rsquoquerystringrsquo)

server

68 Express AJAX應用範例 65

Nodejs中文電子書

server = httpcreateServer(function (reqres)

var urlData

encode = rdquoutf8rdquo

filePath = rdquoviewexpress_ajax_example_formhtmlrdquo

formData

action

urlData = urlparse(requrltrue)

action = urlDatapathname

if (action === rdquoSignuprdquo)

formData = rsquorsquo

reqon(rdquodatardquo function (data)

formData += data

)

reqon(rdquoendrdquo function ()

var msg

user = qsparse(formData)

userid = rdquo123456rdquo

msg = JSONstringify(user)

reswriteHead(200 rdquoContent-TyperdquordquoapplicationjsonrdquordquoContent-Lengthrdquomsglength)

resend(msg)

)

else

fsreadFile(filePath encode function(err file)

reswriteHead(200 rdquoContent-Typerdquordquotexthtml charset=utf-8rdquo)

reswrite(file)

resend()

)

)

serverlisten(3000)

66 6 Express介紹

Nodejs中文電子書

consolelog(rsquoServer 跑起來了現在時間是rsquo + new Date())

69 原始資料提供

bull [NodeJS初學者筆記 (1)-用 GET傳送資料] (httpithelpithomecomtwquestion10087402)

bull [NodeJS初學者筆記 (2)-用 POST傳送資料] (httpithelpithomecomtwquestion10087489)

bull [NodeJS 初學者筆記 (3)-用 Ajax 傳送資料] (httpithelpithomecomtwquestion10087627)

69 原始資料提供 67

Nodejs中文電子書

68 6 Express介紹

7

CoffeeScript

bull Spine

bull Hem

bull Spineapp

Letrsquos Have a Cup of CoffeeScript

bull es5-shim ECMAScript 5 compatibility shims for legacy JavaScript engines

ESnext

node-iform - A middleware for node-validator httpsgithubcomguileennode-iform

69

Nodejs中文電子書

70 7 CoffeeScript

8

製作一個 Hubot的 Plurk Adapter

81 應用事項提醒

此應用範例以CoffeeScript編寫主要程式架構採用Github釋放的Hubot專案以下有幾個主要注意事項請讀者注意

bull Hubot 一開始的架構除了內建的兩個 Adapter 之外其餘都要以Nodejs的Module方式才能運作

bull Hubot的 binhubot裡面寫著 npm install所以不管你怎麼改原始碼也不能改變第一點的狀況

其實上述都在討論同一件事情NodeJS的模組而且模組不能設定相對路徑之類的來安裝一定要包裝成 npm相符和格式才有辦法正確執行

82 建立 Adapter

首先需要準備兩個檔案

bull packagejson

bull plurkcoffee

71

Nodejs中文電子書

有這兩個就足以變成 NodeJS的模組了在開始 Coding之前先來設定一下 packagejson相依性

rdquonamerdquo rdquohubot-plurkrdquo

rdquoversionrdquo rdquo011rdquo

rdquomainrdquo rdquoplurkrdquo

rdquodependenciesrdquo

rdquohubotrdquo rdquogt=205rdquo

rdquooauthrdquo rdquordquo

rdquocronrdquordquordquo

這邊會使用到NodeJS的 cron模組(Module)而登入 Plurk需要OAuth標準授權才行所以也需要加載 OAuth模組

注意Hubot在讀取非內建模組時會自動在前面加上 hubot-的前置

83 建立 Robot跟 API

本篇應用基本上程式碼大部分參考 Hubot - Twitter Adapter來製作主要差異只有在使用 OAuth這個模組Twitter有 Streaming可用而 Plurk則得用 Comet方式來達到即時讀取

plurkcoffee程式碼

Robot = require(rdquohubotrdquo)robot()

Adapter = require(rdquohubtordquo)adapter()

EventEmitter = require(rdquoeventsrdquo)EventEmitter

oauth = require(rdquooauthrdquo)

cronJob = require(rdquocronrdquo)CronJob

class Plurk exntends Adapter

class PlurkStreaming exnteds EventEmitter

72 8 製作一個 Hubot的 Plurk Adapter

Nodejs中文電子書

先弄個基本架構主要 Class皆參考 Twitter Adapter命名方式接著再將 Plurk這個 Class增加幾個Method基本上只要有 run send reply就夠了而 run用來做初始化的部分

class Plurk entends Adapter

send (plurk id strings⋯) -gt

reply (plurk id strings⋯) -gt

run -gt

看起來有點東西接著來處理主要的 Plurk API結合部分

class PlurkStreaming extends EventEmitter

consuctor (options) -gt

plurk (callback) -gt

觀察河道

getChannel -gt

取得 Comet 網址

reply (plurk id message) -gt

回噗

acceptFriends -gt

接受好友

get (path callback) -gt

GET 請求

post (path body callback)-gt

POST 請求(其實是裝飾)

request (method path body callback)-gt

主要的 OAuth 請求

comet (server callback)-gt

噗浪的 Comet 傳回是 JavaScript Callback 要另外處理後才會變成 JSON

然後把注意力集中到 constructor上先把建構子弄好

constructor (options) -gt

super()

if optionskey and optionssecret and optionstoken and optionstoken secret

key = optionskey

secret = optionssecret

token = optionstoken

token secret = optionstoken secret

83 建立 Robot跟 API 73

Nodejs中文電子書

建立 OAuth 連接

consumer = new oauthOAuth(

rdquohttpwwwplurkcomOAuthrequest tokenrdquo

rdquohttpwwwplurkcomOAuthaccess tokenrdquo

key

secret

rdquo10rdquo

rdquohttpwwwplurkcomOAuthauthorizerdquo

rdquoHMAC-SHA1rdquo

)

domain = rdquowwwplurkcomrdquo

初始化取得 Comet 網址

do getChannel

else

throw new Error(rdquo 參數不足需要 Key Secret Token Token Secretrdquo)

接著來處理 request這個 method

request (method path body callback) -gt

記錄一下這次的 Request

consolelog(rdquohttpdomainpathrdquo)

Callback 這邊先不丟進去要用另一種方式處理

request = consumerget(rdquohttpdomainpathrdquo token token secret null)

requeston rdquoresponserdquo (res) -gt

reson rdquodatardquo (chunk) -gt

parseResponse(chunk+rsquorsquo callback)

reson rdquoendrdquo (data) -gt

consolelog rdquoEnd Request pathrdquo

reson rdquoerrorrdquo (data) -gt

consolelog rdquoError rdquo + data

requestend()

處理資料

parseResponse = (data callback) -gt

if datalength gt 0

用 TryCatch 避免處理 JSON 出錯導致整個中斷

try

callback null JSONparse(data)

catch err

74 8 製作一個 Hubot的 Plurk Adapter

Nodejs中文電子書

consolelog(rdquoError Parse JSONrdquo + data err)

繼續執行

callback null data ||

大致上就是這樣上面程式的架構已經將整個 Hubot Plurk Adapter完成因為在測試時竟然因為噗浪 Lag而沒讀到完整的 Comet資料然後造成程式異常為了避免這個問題發生因此需要加上為了完美呈現需要再

加上 Comet的處理所以要使用到 EventEmitter的功能

comet (server callback) -gt

在 Callback 裡面會找不到自身所以設定區域變數

self =

記錄一下這次的 Request

consolelog(rdquo[Comet] serverrdquo)

Callback 這邊先不丟進去要用另一種方式處理

request = consumerget(rdquohttpdomainpathrdquo token token secret null)

requeston rdquoresponserdquo (res) -gt

reson rdquodatardquo (chunk) -gt

parseResponse(chunk+rsquorsquo callback)

reson rdquoendrdquo (data) -gt

consolelog rdquoEnd Request pathrdquo

請求結束發出事件通知可以進行下一次請求

selfemit rdquonextPlurkrdquo

reson rdquoerrorrdquo (data) -gt

consolelog rdquoError rdquo + data

requestend()

處理資料

parseResponse = (data callback) -gt

if datalength gt 0

用 trycatch 避免失敗中斷

try

去掉 JavaScript 的 Callback

data = datamatch(CometChannelscriptCallback((+))s)

jsonData = rdquordquo

if data

83 建立 Robot跟 API 75

Nodejs中文電子書

jsonData = JSONparse(data[1])

else

如果沒有任何 Match 嘗試直接 parse

jsonData = JSONparse(data)

catch err

consolelog(rdquo[Comet] Errorrdquo data err)

用 TryCatch 避免處理 JSON 出錯導致整個中斷

try

只傳入 json 的 data 部分

callback null jsonDatadata

catch err

consolelog(rdquo[Comet]Error Parse JSONrdquo + data err)

繼續執行

callback null data ||

後面的 get跟 post就簡單多了

get (path callback) -gt

request(rdquoGETrdquo path null callback)

post (path body callback) -gt

request(rdquoPOSTrdquo path body callback)

接著處理取的 Comet網址的 getChannel

getChannel -gt

self =

get rdquoAPPRealtimegetUserChannelrdquo (error data) -gt

if error

檢查是否有 comet server

if datacomet server

selfchannel = datacomet server

如果沒有 Channel Ready 就嘗試連接會失敗

selfemit(rsquochannel readyrsquo)

那麼先來處理 Plurk Adaper好處理的部份

send (plruk id strings⋯)-gt

跟 Reply 一樣直接交給 reply 做

reply plurk id strings⋯

76 8 製作一個 Hubot的 Plurk Adapter

Nodejs中文電子書

reply (plurk id strings⋯) -gt

stringsforEach (message) =gt

botreply(plruk id message)

接著把 run處理好就可以上線運作摟

run -gt

self =

options =

key processenvHUBOT PLURK KEY

secret processenvHUBOT PLURK SECRET

token processenvHUBOT PLURK TOKEN

token secret processenvHUBOT PLURK TOKEN SECRET

創建剛剛的 API

bot = new PlurkStreaming(options)

依照 Twitter 的 new RobotTextMessage 會沒有反應所以參考 hubot-minecraft 的方式

r = robotconstructor

處理噗浪河道訊息

doPlurk = (data)-gt

檢查是否為回噗

if dataresponse

datacontent raw = dataresponsecontent raw

datauser id = dataresponseuser id

確定有噗浪 ID 跟訊息

if dataplurk id and datacontent raw

selfreceive new rTextMessage(dataplurk id datacontent raw)

取得 Comet Server 完成開始第一次 Comet 連接

boton rdquochannel readyrdquo () -gt

botplurk selfdoPlurk

上一次 Comet 完成繼續 Polling

boton rdquonextPlurkrdquo ()-gt

botplurk selfdoPlurk

定時接受好友邀請

do botacceptFriends

bot = bot

83 建立 Robot跟 API 77

Nodejs中文電子書

終於完成 AdapterHubot專案裡面的 scripts資料夾內是互動部分不需要像 Adapter如此大費周章處理只新增檔案並且設計好對白之後就會回噗了我開發用的機器人在此大家可以去跟他玩玩[httpplurkcomelct9620 bot](httpplurkcomelct9620 bot)

84 原始資料提供

bull [製 作 一 個 Hubot 的 噗 浪 Adapter](http revo-skillfrosttw blog20120318create-a-hubot-plurk-adapter)

78 8 製作一個 Hubot的 Plurk Adapter

9

Nodejs好用工具介紹

91 logio

Real-time log monitoring in your browser

httplogioorg

nodejs (gt=0412 lt=0611)

npm config set unsafe-perm true

npm install -g --prefix=usrlocal logio

logio server start

httplocalhost8998

79

Nodejs中文電子書

80 9 Nodejs好用工具介紹

10

精選文章

101 Express

bull 使用 NodeJS + Express從 GETPOST Request取值 [邀稿中]

bull link2 [邀稿中]

bull link3 [邀稿中]

81

Nodejs中文電子書

82 10 精選文章

11

參考資源

111 Nodejs書籍

bull Programming Nodejs April 2012 (est) Travis Swicegood Pragmatic Bookshelf

bull e Little Book on CoffeeScript January 2012 Alex MacCaw OrsquoReilly Media

bull Node for Front-End Developers January 2012 Garann Means OrsquoReilly Media

bull Building Hypermedia APIs with HTML5 and Node November 2011 MikeAmundsen OrsquoReilly Media

bull Node Web Development August 2011 David Herron PacktPub

bull CoffeeScript Accelerated JavaScript Development July 2011 Trevor BurnhamPragmatic Bookshelf

bull Getting Started with GEO CouchDB and Nodejs July 2011 Mick omp-son OrsquoReilly Media

bull What Is Node July 2011 Brett McLaughlin OrsquoReilly Media

bull Node Up and Running May 2011 Tom Hughes-Croucher Mike WilsonOrsquoReilly Media

83

Nodejs中文電子書

bull Hands-on Nodejs e Nodejs introduction and API reference May 2011 Pe-dro Teixeira LeanPub

bull e Node Beginner Book Manuel Kiessling

bull Mastering Nodejs visionmedia

112 Nodejs影音教學

bull e Node Sessions e Best of OSCON 2011 August 2011 OrsquoReilly Media(Video)

bull Tom Hughes-Croucher on Node March 2011 OrsquoReilly Media (Video)

113 Nodejs教學網站

bull Node Tuts

114 Nodejs課程

bull Introduction To Nodejs Van Nguyen CodeLesson

84 11 參考資源

Page 65: Nodejs Wiki

Nodejs中文電子書

果是 Signup這個動作就把 urlDataquery裡的資料指定給 user然後輸出userusername和 useremail把使用者從表單註冊的資料顯示於頁面中

最後進行程式測試啟動 nodejs主程式之後開啟瀏覽器就會看到表單填寫完畢按下送出就可以看到結果了

完整 nodejs程式碼如下

var http = require(rsquohttprsquo)

url = require(rsquourlrsquo)

fs = require(rdquofsrdquo)

server

server = httpcreateServer(function (reqres)

var urlData

encode = rdquoutf8rdquo

filePath = rdquoviewexpress_get_example_formhtmlrdquo

action

urlData = urlparse(requrltrue)

action = urlDatapathname

reswriteHead(200 rdquoContent-Typerdquordquotexthtml charset=utf-8rdquo)

if (action === rdquoSignuprdquo)

user = urlDataquery

resend(rdquolth1gtrdquo + userusername + rdquo 歡迎您的加入 lth1gtltpgt 我們已經將會員啟用信寄至rdquo + useremail + rdquoltpgtrdquo)

else

fsreadFile(filePath encode function(err file)

reswrite(file)

resend()

)

)

serverlisten(3000)

consolelog(rsquoServer 跑起來了現在時間是rsquo + new Date())

66 Express GET應用範例 59

Nodejs中文電子書

67 Express POST應用範例

一開始準備基本的 html表單傳送內容以 POST方式form的 action屬性設定為 POST其餘 html內容與前一個範例應用相同

ltDOCTYPE htmlgt

lthtmlgt

ltheadgt

ltmeta http-equiv=rdquoContent-Typerdquo content=rdquotexthtml charset=utf8rdquogt

lttitlegtNodejs 菜鳥筆記 (1)lttitlegt

ltlink rel=rdquostylesheetrdquo href=rdquocssstylecssrdquo type=rdquotextcssrdquo media=rdquoallrdquo gt

ltheadgt

ltbodygt

lth1gtNodejs 菜鳥筆記-註冊lth1gt

ltform id=rdquosignuprdquo method=rdquoPOSTrdquo action=rdquohttplocalhost3000Signuprdquogt

ltlabelgt 使用者名稱ltlabelgtltinput type=rdquotextrdquo id=rdquousernamerdquo name=rdquousernamerdquo gtltbrgt

ltlabelgt 電子郵件ltlabelgtltinput type=rdquotextrdquo id=rdquoemailrdquo name=rdquoemailrdquo gtltbrgt

ltinput type=rdquosubmitrdquo value=rdquo 註冊我的帳號rdquo gtltbrgt

ltformgt

ltbodygt

lthtmlgt

nodejs的程式處理邏輯與前面 GET範例類似部分程式碼如下

qs = require(rsquoquerystringrsquo)

if (action === rdquoSignuprdquo)

formData = rsquorsquo

reqon(rdquodatardquo function (data)

formData += data

)

reqon(rdquoendrdquo function ()

user = qsparse(formData)

resend(rdquolth1gtrdquo + userusername + rdquo 歡迎您的加入 lth1gtltpgt 我們已經將會員啟用信寄至rdquo + useremail + rdquoltpgtrdquo)

)

60 6 Express介紹

Nodejs中文電子書

主要加入了rsquoquerystringrsquo這個moduel方便我們等一下解析由表單 POST回來的資料另外加入一個 formData的變數用來搜集待等一下表單回傳的資料前面的 GET範例我們只從 req拿出 url的資料這次要在利用req身上的事件處理

JavaScript 在訂閱事件時使用 addEventListener而 nodejs 使用的則是on這邊加上了監聽 data的事件會在瀏覽器傳送資料到Web Server時被執行參數是它所接收到的資料型態是字串

接著再增加 end的事件當瀏覽器的請求事件結束時它就會動作

由於瀏覽器使用 POST在上傳資料時會將資料一塊塊地上傳因為我們在監聽 data事件時透過 formData變數將它累加起來lt不過由於我們

上傳的資料很少一次就結束不過如果日後需要傳的是資料比較大的檔

案這個累加動作就很重要

當資料傳完就進到 end 事件中會用到 qsparse 來解析 formDataformData的內容是字串內容是

username=wordsmithampemail=wordsmith40somewhere

而 qsparse可以幫我們把這個 querystring轉成物件的格式也就是

username=wordsmithampemail=wordsmith40somewhere

一旦轉成物件並指定給 user之後其他的事情就和 GET方法時操作的一樣寫 response的表頭將內容回傳並將 userusername和 useremail代入到內容中

修改完成後接著執行 nodejs程式啟動 web server開啟瀏覽器進入表單測試看看POST的方式能否順利運作

完整程式碼如下

var http = require(rsquohttprsquo)

url = require(rsquourlrsquo)

fs = require(rdquofsrdquo)

qs = require(rsquoquerystringrsquo)

server

server = httpcreateServer(function (reqres)

var urlData

67 Express POST應用範例 61

Nodejs中文電子書

encode = rdquoutf8rdquo

filePath = rdquoviewexpress_post_example_formhtmlrdquo

formData

action

urlData = urlparse(requrltrue)

action = urlDatapathname

reswriteHead(200 rdquoContent-Typerdquordquotexthtml charset=utf-8rdquo)

if (action === rdquoSignuprdquo)

formData = rsquorsquo

reqon(rdquodatardquo function (data)

formData += data

)

reqon(rdquoendrdquo function ()

user = qsparse(formData)

resend(rdquolth1gtrdquo + userusername + rdquo 歡迎您的加入 lth1gtltpgt 我們已經將會員啟用信寄至rdquo + useremail + rdquoltpgtrdquo)

)

else

fsreadFile(filePath encode function(err file)

reswrite(file)

resend()

)

)

serverlisten(3000)

consolelog(rsquoServer 跑起來了現在時間是rsquo + new Date())

62 6 Express介紹

Nodejs中文電子書

68 Express AJAX應用範例

在 Nodejs要使用 Ajax傳送資料並且與之互動在接受資料的部份沒有太大的差別client端不是用 GET就是用 POST來傳資料重點在處理完後用 JSON格式回傳當然 Ajax不見得只傳 JSON格式有時是回傳一段 HTML碼不過後者對伺服器來說基本上就和前兩篇沒有差別了所以我們還是以回傳 JSON做為這一回的主題

這一回其實大多數的工作都會落在前端 Ajax上面前端要負責發送與接收資料並在接收資料後撤掉原先發送資料的表單並將取得的資料

改成 HTML格式之後放上頁面

首先先準備 HTML靜態頁面資料

ltDOCTYPE htmlgt

lthtmlgt

ltheadgt

lttitlegtNodejs 菜鳥筆記 (3)lttitlegt

ltlink rel=rdquostylesheetrdquo href=rdquocssstylecssrdquo type=rdquotextcssrdquo media=rdquoallrdquo gt

ltscript src=rdquohttpsajaxgoogleapiscomajaxlibsjquery171jqueryminjsrdquogtltscriptgt

ltheadgt

ltbodygt

lth1gtNodejs 菜鳥筆記-註冊 (用 Ajax)lth1gt

ltform id=rdquosignuprdquo action=rdquoSignuprdquo method=rdquoPOSTrdquogt

ltlabelgt 使用者名稱ltlabelgtltinput type=rdquotextrdquo id=rdquousernamerdquo name=rdquousernamerdquo gtltbrgt

ltlabelgt 電子郵件ltlabelgtltinput type=rdquotextrdquo id=rdquoemailrdquo name=rdquoemailrdquo gtltbrgt

ltinput type=rdquosubmitrdquo value=rdquo 註冊我的帳號rdquo gtltbrgt

ltformgt

ltdiv id=rdquoresultrdquogt

ltdivgt

ltscript type=rdquotextjavascriptrdquogt

$(function()$(rsquosignup input[type=rdquosubmitrdquo]rsquo)click(function(e)

var user =

userusername = $(rdquousernamerdquo)val()useremail = $(rdquoemailrdquo)val()

$post(rdquoSignuprdquouserfunction(data)greet(data)

)

68 Express AJAX應用範例 63

Nodejs中文電子書

epreventDefault()

)

var greet = function(msg)

var resultNode = $(rsquoresultrsquo)greeting = $(rsquolth2gtHirsquo+msgusername+rsquo 你的會員 id 是rsquo+ msgid +rsquo 我們會將會員啟動信寄至rsquo+msgemail+rsquolth2gtrsquo)

$(rsquosignuprsquo)hide()resultNodehtml(rdquordquo)

resultNodeappend(greeting)

)

ltscriptgt

ltbodygt

lthtmlgt

HTML頁面上準備了一個表單用來傳送註冊資料接著直接引用了Google CDN來載入 jQuery用來幫我們處理 Ajax的工作這次要傳送和接收的工作很大的變動都在 HTML頁面上的 JavaScript當中我們要做的事有 (相關 jQuery處理這邊不多做贅述指提起主要功能解說)

bull 用 jQUery取得 submit按鈕綁定它的 click動作

bull 取得表單 username和 email的值存放在 user這個物件中

bull 用 jQuery的 $post方法將 user的資料傳到 Server

bull 一旦成功取得資料後透過 greet這個 function組成回報給 user的訊息

bull 清空原本給使用者填資料的表單

bull 將 Server回傳的 usernameemail和 id這 3個資料組成回應的訊息

bull 將訊息放到原本表單的位置

經過以上的處理後一個 Ajax的表單的基本功能已經完成

接著進行 nodejs主要程式的編輯部分程式碼如下

var fs = require(rdquofsrdquo)

64 6 Express介紹

Nodejs中文電子書

qs = require(rsquoquerystringrsquo)

if (action === rdquoSignuprdquo)

formData = rsquorsquo

reqon(rdquodatardquo function (data)

formData += data

)

reqon(rdquoendrdquo function ()

var msg

user = qsparse(formData)

userid = rdquo123456rdquo

msg = JSONstringify(user)

reswriteHead(200 rdquoContent-Typerdquordquoapplicationjson charset=utf-8rdquordquoContent-Lengthrdquomsglength)

resend(msg)

)

這裡的程式和前面 POST範例基本上大同小異差別在

bull 幫 user的資料加上 id隨意存放一些文字進去讓 Server回傳的資料多於 Client端傳上來的不然會覺得 Server都沒做事

bull 增加了 msg 這個變數存放將 user 物件 JSON 文字化的結果JSONstringify這個轉換函式是 V8引擎所提供的如果你好奇的話

bull 大重點來了我們要告訴 Client端這次回傳的資料格式是 JSON所在 Content-type和 Content-Length要提供給 Client

Server很輕鬆就完成任務了最後進行程式測試啟動 nodejs主程式之後開啟瀏覽器就會看到表單填寫完畢按下送出就可以看到結果了

最後 nodejs本篇範例程式碼如下

var http = require(rsquohttprsquo)

url = require(rsquourlrsquo)

fs = require(rdquofsrdquo)

qs = require(rsquoquerystringrsquo)

server

68 Express AJAX應用範例 65

Nodejs中文電子書

server = httpcreateServer(function (reqres)

var urlData

encode = rdquoutf8rdquo

filePath = rdquoviewexpress_ajax_example_formhtmlrdquo

formData

action

urlData = urlparse(requrltrue)

action = urlDatapathname

if (action === rdquoSignuprdquo)

formData = rsquorsquo

reqon(rdquodatardquo function (data)

formData += data

)

reqon(rdquoendrdquo function ()

var msg

user = qsparse(formData)

userid = rdquo123456rdquo

msg = JSONstringify(user)

reswriteHead(200 rdquoContent-TyperdquordquoapplicationjsonrdquordquoContent-Lengthrdquomsglength)

resend(msg)

)

else

fsreadFile(filePath encode function(err file)

reswriteHead(200 rdquoContent-Typerdquordquotexthtml charset=utf-8rdquo)

reswrite(file)

resend()

)

)

serverlisten(3000)

66 6 Express介紹

Nodejs中文電子書

consolelog(rsquoServer 跑起來了現在時間是rsquo + new Date())

69 原始資料提供

bull [NodeJS初學者筆記 (1)-用 GET傳送資料] (httpithelpithomecomtwquestion10087402)

bull [NodeJS初學者筆記 (2)-用 POST傳送資料] (httpithelpithomecomtwquestion10087489)

bull [NodeJS 初學者筆記 (3)-用 Ajax 傳送資料] (httpithelpithomecomtwquestion10087627)

69 原始資料提供 67

Nodejs中文電子書

68 6 Express介紹

7

CoffeeScript

bull Spine

bull Hem

bull Spineapp

Letrsquos Have a Cup of CoffeeScript

bull es5-shim ECMAScript 5 compatibility shims for legacy JavaScript engines

ESnext

node-iform - A middleware for node-validator httpsgithubcomguileennode-iform

69

Nodejs中文電子書

70 7 CoffeeScript

8

製作一個 Hubot的 Plurk Adapter

81 應用事項提醒

此應用範例以CoffeeScript編寫主要程式架構採用Github釋放的Hubot專案以下有幾個主要注意事項請讀者注意

bull Hubot 一開始的架構除了內建的兩個 Adapter 之外其餘都要以Nodejs的Module方式才能運作

bull Hubot的 binhubot裡面寫著 npm install所以不管你怎麼改原始碼也不能改變第一點的狀況

其實上述都在討論同一件事情NodeJS的模組而且模組不能設定相對路徑之類的來安裝一定要包裝成 npm相符和格式才有辦法正確執行

82 建立 Adapter

首先需要準備兩個檔案

bull packagejson

bull plurkcoffee

71

Nodejs中文電子書

有這兩個就足以變成 NodeJS的模組了在開始 Coding之前先來設定一下 packagejson相依性

rdquonamerdquo rdquohubot-plurkrdquo

rdquoversionrdquo rdquo011rdquo

rdquomainrdquo rdquoplurkrdquo

rdquodependenciesrdquo

rdquohubotrdquo rdquogt=205rdquo

rdquooauthrdquo rdquordquo

rdquocronrdquordquordquo

這邊會使用到NodeJS的 cron模組(Module)而登入 Plurk需要OAuth標準授權才行所以也需要加載 OAuth模組

注意Hubot在讀取非內建模組時會自動在前面加上 hubot-的前置

83 建立 Robot跟 API

本篇應用基本上程式碼大部分參考 Hubot - Twitter Adapter來製作主要差異只有在使用 OAuth這個模組Twitter有 Streaming可用而 Plurk則得用 Comet方式來達到即時讀取

plurkcoffee程式碼

Robot = require(rdquohubotrdquo)robot()

Adapter = require(rdquohubtordquo)adapter()

EventEmitter = require(rdquoeventsrdquo)EventEmitter

oauth = require(rdquooauthrdquo)

cronJob = require(rdquocronrdquo)CronJob

class Plurk exntends Adapter

class PlurkStreaming exnteds EventEmitter

72 8 製作一個 Hubot的 Plurk Adapter

Nodejs中文電子書

先弄個基本架構主要 Class皆參考 Twitter Adapter命名方式接著再將 Plurk這個 Class增加幾個Method基本上只要有 run send reply就夠了而 run用來做初始化的部分

class Plurk entends Adapter

send (plurk id strings⋯) -gt

reply (plurk id strings⋯) -gt

run -gt

看起來有點東西接著來處理主要的 Plurk API結合部分

class PlurkStreaming extends EventEmitter

consuctor (options) -gt

plurk (callback) -gt

觀察河道

getChannel -gt

取得 Comet 網址

reply (plurk id message) -gt

回噗

acceptFriends -gt

接受好友

get (path callback) -gt

GET 請求

post (path body callback)-gt

POST 請求(其實是裝飾)

request (method path body callback)-gt

主要的 OAuth 請求

comet (server callback)-gt

噗浪的 Comet 傳回是 JavaScript Callback 要另外處理後才會變成 JSON

然後把注意力集中到 constructor上先把建構子弄好

constructor (options) -gt

super()

if optionskey and optionssecret and optionstoken and optionstoken secret

key = optionskey

secret = optionssecret

token = optionstoken

token secret = optionstoken secret

83 建立 Robot跟 API 73

Nodejs中文電子書

建立 OAuth 連接

consumer = new oauthOAuth(

rdquohttpwwwplurkcomOAuthrequest tokenrdquo

rdquohttpwwwplurkcomOAuthaccess tokenrdquo

key

secret

rdquo10rdquo

rdquohttpwwwplurkcomOAuthauthorizerdquo

rdquoHMAC-SHA1rdquo

)

domain = rdquowwwplurkcomrdquo

初始化取得 Comet 網址

do getChannel

else

throw new Error(rdquo 參數不足需要 Key Secret Token Token Secretrdquo)

接著來處理 request這個 method

request (method path body callback) -gt

記錄一下這次的 Request

consolelog(rdquohttpdomainpathrdquo)

Callback 這邊先不丟進去要用另一種方式處理

request = consumerget(rdquohttpdomainpathrdquo token token secret null)

requeston rdquoresponserdquo (res) -gt

reson rdquodatardquo (chunk) -gt

parseResponse(chunk+rsquorsquo callback)

reson rdquoendrdquo (data) -gt

consolelog rdquoEnd Request pathrdquo

reson rdquoerrorrdquo (data) -gt

consolelog rdquoError rdquo + data

requestend()

處理資料

parseResponse = (data callback) -gt

if datalength gt 0

用 TryCatch 避免處理 JSON 出錯導致整個中斷

try

callback null JSONparse(data)

catch err

74 8 製作一個 Hubot的 Plurk Adapter

Nodejs中文電子書

consolelog(rdquoError Parse JSONrdquo + data err)

繼續執行

callback null data ||

大致上就是這樣上面程式的架構已經將整個 Hubot Plurk Adapter完成因為在測試時竟然因為噗浪 Lag而沒讀到完整的 Comet資料然後造成程式異常為了避免這個問題發生因此需要加上為了完美呈現需要再

加上 Comet的處理所以要使用到 EventEmitter的功能

comet (server callback) -gt

在 Callback 裡面會找不到自身所以設定區域變數

self =

記錄一下這次的 Request

consolelog(rdquo[Comet] serverrdquo)

Callback 這邊先不丟進去要用另一種方式處理

request = consumerget(rdquohttpdomainpathrdquo token token secret null)

requeston rdquoresponserdquo (res) -gt

reson rdquodatardquo (chunk) -gt

parseResponse(chunk+rsquorsquo callback)

reson rdquoendrdquo (data) -gt

consolelog rdquoEnd Request pathrdquo

請求結束發出事件通知可以進行下一次請求

selfemit rdquonextPlurkrdquo

reson rdquoerrorrdquo (data) -gt

consolelog rdquoError rdquo + data

requestend()

處理資料

parseResponse = (data callback) -gt

if datalength gt 0

用 trycatch 避免失敗中斷

try

去掉 JavaScript 的 Callback

data = datamatch(CometChannelscriptCallback((+))s)

jsonData = rdquordquo

if data

83 建立 Robot跟 API 75

Nodejs中文電子書

jsonData = JSONparse(data[1])

else

如果沒有任何 Match 嘗試直接 parse

jsonData = JSONparse(data)

catch err

consolelog(rdquo[Comet] Errorrdquo data err)

用 TryCatch 避免處理 JSON 出錯導致整個中斷

try

只傳入 json 的 data 部分

callback null jsonDatadata

catch err

consolelog(rdquo[Comet]Error Parse JSONrdquo + data err)

繼續執行

callback null data ||

後面的 get跟 post就簡單多了

get (path callback) -gt

request(rdquoGETrdquo path null callback)

post (path body callback) -gt

request(rdquoPOSTrdquo path body callback)

接著處理取的 Comet網址的 getChannel

getChannel -gt

self =

get rdquoAPPRealtimegetUserChannelrdquo (error data) -gt

if error

檢查是否有 comet server

if datacomet server

selfchannel = datacomet server

如果沒有 Channel Ready 就嘗試連接會失敗

selfemit(rsquochannel readyrsquo)

那麼先來處理 Plurk Adaper好處理的部份

send (plruk id strings⋯)-gt

跟 Reply 一樣直接交給 reply 做

reply plurk id strings⋯

76 8 製作一個 Hubot的 Plurk Adapter

Nodejs中文電子書

reply (plurk id strings⋯) -gt

stringsforEach (message) =gt

botreply(plruk id message)

接著把 run處理好就可以上線運作摟

run -gt

self =

options =

key processenvHUBOT PLURK KEY

secret processenvHUBOT PLURK SECRET

token processenvHUBOT PLURK TOKEN

token secret processenvHUBOT PLURK TOKEN SECRET

創建剛剛的 API

bot = new PlurkStreaming(options)

依照 Twitter 的 new RobotTextMessage 會沒有反應所以參考 hubot-minecraft 的方式

r = robotconstructor

處理噗浪河道訊息

doPlurk = (data)-gt

檢查是否為回噗

if dataresponse

datacontent raw = dataresponsecontent raw

datauser id = dataresponseuser id

確定有噗浪 ID 跟訊息

if dataplurk id and datacontent raw

selfreceive new rTextMessage(dataplurk id datacontent raw)

取得 Comet Server 完成開始第一次 Comet 連接

boton rdquochannel readyrdquo () -gt

botplurk selfdoPlurk

上一次 Comet 完成繼續 Polling

boton rdquonextPlurkrdquo ()-gt

botplurk selfdoPlurk

定時接受好友邀請

do botacceptFriends

bot = bot

83 建立 Robot跟 API 77

Nodejs中文電子書

終於完成 AdapterHubot專案裡面的 scripts資料夾內是互動部分不需要像 Adapter如此大費周章處理只新增檔案並且設計好對白之後就會回噗了我開發用的機器人在此大家可以去跟他玩玩[httpplurkcomelct9620 bot](httpplurkcomelct9620 bot)

84 原始資料提供

bull [製 作 一 個 Hubot 的 噗 浪 Adapter](http revo-skillfrosttw blog20120318create-a-hubot-plurk-adapter)

78 8 製作一個 Hubot的 Plurk Adapter

9

Nodejs好用工具介紹

91 logio

Real-time log monitoring in your browser

httplogioorg

nodejs (gt=0412 lt=0611)

npm config set unsafe-perm true

npm install -g --prefix=usrlocal logio

logio server start

httplocalhost8998

79

Nodejs中文電子書

80 9 Nodejs好用工具介紹

10

精選文章

101 Express

bull 使用 NodeJS + Express從 GETPOST Request取值 [邀稿中]

bull link2 [邀稿中]

bull link3 [邀稿中]

81

Nodejs中文電子書

82 10 精選文章

11

參考資源

111 Nodejs書籍

bull Programming Nodejs April 2012 (est) Travis Swicegood Pragmatic Bookshelf

bull e Little Book on CoffeeScript January 2012 Alex MacCaw OrsquoReilly Media

bull Node for Front-End Developers January 2012 Garann Means OrsquoReilly Media

bull Building Hypermedia APIs with HTML5 and Node November 2011 MikeAmundsen OrsquoReilly Media

bull Node Web Development August 2011 David Herron PacktPub

bull CoffeeScript Accelerated JavaScript Development July 2011 Trevor BurnhamPragmatic Bookshelf

bull Getting Started with GEO CouchDB and Nodejs July 2011 Mick omp-son OrsquoReilly Media

bull What Is Node July 2011 Brett McLaughlin OrsquoReilly Media

bull Node Up and Running May 2011 Tom Hughes-Croucher Mike WilsonOrsquoReilly Media

83

Nodejs中文電子書

bull Hands-on Nodejs e Nodejs introduction and API reference May 2011 Pe-dro Teixeira LeanPub

bull e Node Beginner Book Manuel Kiessling

bull Mastering Nodejs visionmedia

112 Nodejs影音教學

bull e Node Sessions e Best of OSCON 2011 August 2011 OrsquoReilly Media(Video)

bull Tom Hughes-Croucher on Node March 2011 OrsquoReilly Media (Video)

113 Nodejs教學網站

bull Node Tuts

114 Nodejs課程

bull Introduction To Nodejs Van Nguyen CodeLesson

84 11 參考資源

Page 66: Nodejs Wiki

Nodejs中文電子書

67 Express POST應用範例

一開始準備基本的 html表單傳送內容以 POST方式form的 action屬性設定為 POST其餘 html內容與前一個範例應用相同

ltDOCTYPE htmlgt

lthtmlgt

ltheadgt

ltmeta http-equiv=rdquoContent-Typerdquo content=rdquotexthtml charset=utf8rdquogt

lttitlegtNodejs 菜鳥筆記 (1)lttitlegt

ltlink rel=rdquostylesheetrdquo href=rdquocssstylecssrdquo type=rdquotextcssrdquo media=rdquoallrdquo gt

ltheadgt

ltbodygt

lth1gtNodejs 菜鳥筆記-註冊lth1gt

ltform id=rdquosignuprdquo method=rdquoPOSTrdquo action=rdquohttplocalhost3000Signuprdquogt

ltlabelgt 使用者名稱ltlabelgtltinput type=rdquotextrdquo id=rdquousernamerdquo name=rdquousernamerdquo gtltbrgt

ltlabelgt 電子郵件ltlabelgtltinput type=rdquotextrdquo id=rdquoemailrdquo name=rdquoemailrdquo gtltbrgt

ltinput type=rdquosubmitrdquo value=rdquo 註冊我的帳號rdquo gtltbrgt

ltformgt

ltbodygt

lthtmlgt

nodejs的程式處理邏輯與前面 GET範例類似部分程式碼如下

qs = require(rsquoquerystringrsquo)

if (action === rdquoSignuprdquo)

formData = rsquorsquo

reqon(rdquodatardquo function (data)

formData += data

)

reqon(rdquoendrdquo function ()

user = qsparse(formData)

resend(rdquolth1gtrdquo + userusername + rdquo 歡迎您的加入 lth1gtltpgt 我們已經將會員啟用信寄至rdquo + useremail + rdquoltpgtrdquo)

)

60 6 Express介紹

Nodejs中文電子書

主要加入了rsquoquerystringrsquo這個moduel方便我們等一下解析由表單 POST回來的資料另外加入一個 formData的變數用來搜集待等一下表單回傳的資料前面的 GET範例我們只從 req拿出 url的資料這次要在利用req身上的事件處理

JavaScript 在訂閱事件時使用 addEventListener而 nodejs 使用的則是on這邊加上了監聽 data的事件會在瀏覽器傳送資料到Web Server時被執行參數是它所接收到的資料型態是字串

接著再增加 end的事件當瀏覽器的請求事件結束時它就會動作

由於瀏覽器使用 POST在上傳資料時會將資料一塊塊地上傳因為我們在監聽 data事件時透過 formData變數將它累加起來lt不過由於我們

上傳的資料很少一次就結束不過如果日後需要傳的是資料比較大的檔

案這個累加動作就很重要

當資料傳完就進到 end 事件中會用到 qsparse 來解析 formDataformData的內容是字串內容是

username=wordsmithampemail=wordsmith40somewhere

而 qsparse可以幫我們把這個 querystring轉成物件的格式也就是

username=wordsmithampemail=wordsmith40somewhere

一旦轉成物件並指定給 user之後其他的事情就和 GET方法時操作的一樣寫 response的表頭將內容回傳並將 userusername和 useremail代入到內容中

修改完成後接著執行 nodejs程式啟動 web server開啟瀏覽器進入表單測試看看POST的方式能否順利運作

完整程式碼如下

var http = require(rsquohttprsquo)

url = require(rsquourlrsquo)

fs = require(rdquofsrdquo)

qs = require(rsquoquerystringrsquo)

server

server = httpcreateServer(function (reqres)

var urlData

67 Express POST應用範例 61

Nodejs中文電子書

encode = rdquoutf8rdquo

filePath = rdquoviewexpress_post_example_formhtmlrdquo

formData

action

urlData = urlparse(requrltrue)

action = urlDatapathname

reswriteHead(200 rdquoContent-Typerdquordquotexthtml charset=utf-8rdquo)

if (action === rdquoSignuprdquo)

formData = rsquorsquo

reqon(rdquodatardquo function (data)

formData += data

)

reqon(rdquoendrdquo function ()

user = qsparse(formData)

resend(rdquolth1gtrdquo + userusername + rdquo 歡迎您的加入 lth1gtltpgt 我們已經將會員啟用信寄至rdquo + useremail + rdquoltpgtrdquo)

)

else

fsreadFile(filePath encode function(err file)

reswrite(file)

resend()

)

)

serverlisten(3000)

consolelog(rsquoServer 跑起來了現在時間是rsquo + new Date())

62 6 Express介紹

Nodejs中文電子書

68 Express AJAX應用範例

在 Nodejs要使用 Ajax傳送資料並且與之互動在接受資料的部份沒有太大的差別client端不是用 GET就是用 POST來傳資料重點在處理完後用 JSON格式回傳當然 Ajax不見得只傳 JSON格式有時是回傳一段 HTML碼不過後者對伺服器來說基本上就和前兩篇沒有差別了所以我們還是以回傳 JSON做為這一回的主題

這一回其實大多數的工作都會落在前端 Ajax上面前端要負責發送與接收資料並在接收資料後撤掉原先發送資料的表單並將取得的資料

改成 HTML格式之後放上頁面

首先先準備 HTML靜態頁面資料

ltDOCTYPE htmlgt

lthtmlgt

ltheadgt

lttitlegtNodejs 菜鳥筆記 (3)lttitlegt

ltlink rel=rdquostylesheetrdquo href=rdquocssstylecssrdquo type=rdquotextcssrdquo media=rdquoallrdquo gt

ltscript src=rdquohttpsajaxgoogleapiscomajaxlibsjquery171jqueryminjsrdquogtltscriptgt

ltheadgt

ltbodygt

lth1gtNodejs 菜鳥筆記-註冊 (用 Ajax)lth1gt

ltform id=rdquosignuprdquo action=rdquoSignuprdquo method=rdquoPOSTrdquogt

ltlabelgt 使用者名稱ltlabelgtltinput type=rdquotextrdquo id=rdquousernamerdquo name=rdquousernamerdquo gtltbrgt

ltlabelgt 電子郵件ltlabelgtltinput type=rdquotextrdquo id=rdquoemailrdquo name=rdquoemailrdquo gtltbrgt

ltinput type=rdquosubmitrdquo value=rdquo 註冊我的帳號rdquo gtltbrgt

ltformgt

ltdiv id=rdquoresultrdquogt

ltdivgt

ltscript type=rdquotextjavascriptrdquogt

$(function()$(rsquosignup input[type=rdquosubmitrdquo]rsquo)click(function(e)

var user =

userusername = $(rdquousernamerdquo)val()useremail = $(rdquoemailrdquo)val()

$post(rdquoSignuprdquouserfunction(data)greet(data)

)

68 Express AJAX應用範例 63

Nodejs中文電子書

epreventDefault()

)

var greet = function(msg)

var resultNode = $(rsquoresultrsquo)greeting = $(rsquolth2gtHirsquo+msgusername+rsquo 你的會員 id 是rsquo+ msgid +rsquo 我們會將會員啟動信寄至rsquo+msgemail+rsquolth2gtrsquo)

$(rsquosignuprsquo)hide()resultNodehtml(rdquordquo)

resultNodeappend(greeting)

)

ltscriptgt

ltbodygt

lthtmlgt

HTML頁面上準備了一個表單用來傳送註冊資料接著直接引用了Google CDN來載入 jQuery用來幫我們處理 Ajax的工作這次要傳送和接收的工作很大的變動都在 HTML頁面上的 JavaScript當中我們要做的事有 (相關 jQuery處理這邊不多做贅述指提起主要功能解說)

bull 用 jQUery取得 submit按鈕綁定它的 click動作

bull 取得表單 username和 email的值存放在 user這個物件中

bull 用 jQuery的 $post方法將 user的資料傳到 Server

bull 一旦成功取得資料後透過 greet這個 function組成回報給 user的訊息

bull 清空原本給使用者填資料的表單

bull 將 Server回傳的 usernameemail和 id這 3個資料組成回應的訊息

bull 將訊息放到原本表單的位置

經過以上的處理後一個 Ajax的表單的基本功能已經完成

接著進行 nodejs主要程式的編輯部分程式碼如下

var fs = require(rdquofsrdquo)

64 6 Express介紹

Nodejs中文電子書

qs = require(rsquoquerystringrsquo)

if (action === rdquoSignuprdquo)

formData = rsquorsquo

reqon(rdquodatardquo function (data)

formData += data

)

reqon(rdquoendrdquo function ()

var msg

user = qsparse(formData)

userid = rdquo123456rdquo

msg = JSONstringify(user)

reswriteHead(200 rdquoContent-Typerdquordquoapplicationjson charset=utf-8rdquordquoContent-Lengthrdquomsglength)

resend(msg)

)

這裡的程式和前面 POST範例基本上大同小異差別在

bull 幫 user的資料加上 id隨意存放一些文字進去讓 Server回傳的資料多於 Client端傳上來的不然會覺得 Server都沒做事

bull 增加了 msg 這個變數存放將 user 物件 JSON 文字化的結果JSONstringify這個轉換函式是 V8引擎所提供的如果你好奇的話

bull 大重點來了我們要告訴 Client端這次回傳的資料格式是 JSON所在 Content-type和 Content-Length要提供給 Client

Server很輕鬆就完成任務了最後進行程式測試啟動 nodejs主程式之後開啟瀏覽器就會看到表單填寫完畢按下送出就可以看到結果了

最後 nodejs本篇範例程式碼如下

var http = require(rsquohttprsquo)

url = require(rsquourlrsquo)

fs = require(rdquofsrdquo)

qs = require(rsquoquerystringrsquo)

server

68 Express AJAX應用範例 65

Nodejs中文電子書

server = httpcreateServer(function (reqres)

var urlData

encode = rdquoutf8rdquo

filePath = rdquoviewexpress_ajax_example_formhtmlrdquo

formData

action

urlData = urlparse(requrltrue)

action = urlDatapathname

if (action === rdquoSignuprdquo)

formData = rsquorsquo

reqon(rdquodatardquo function (data)

formData += data

)

reqon(rdquoendrdquo function ()

var msg

user = qsparse(formData)

userid = rdquo123456rdquo

msg = JSONstringify(user)

reswriteHead(200 rdquoContent-TyperdquordquoapplicationjsonrdquordquoContent-Lengthrdquomsglength)

resend(msg)

)

else

fsreadFile(filePath encode function(err file)

reswriteHead(200 rdquoContent-Typerdquordquotexthtml charset=utf-8rdquo)

reswrite(file)

resend()

)

)

serverlisten(3000)

66 6 Express介紹

Nodejs中文電子書

consolelog(rsquoServer 跑起來了現在時間是rsquo + new Date())

69 原始資料提供

bull [NodeJS初學者筆記 (1)-用 GET傳送資料] (httpithelpithomecomtwquestion10087402)

bull [NodeJS初學者筆記 (2)-用 POST傳送資料] (httpithelpithomecomtwquestion10087489)

bull [NodeJS 初學者筆記 (3)-用 Ajax 傳送資料] (httpithelpithomecomtwquestion10087627)

69 原始資料提供 67

Nodejs中文電子書

68 6 Express介紹

7

CoffeeScript

bull Spine

bull Hem

bull Spineapp

Letrsquos Have a Cup of CoffeeScript

bull es5-shim ECMAScript 5 compatibility shims for legacy JavaScript engines

ESnext

node-iform - A middleware for node-validator httpsgithubcomguileennode-iform

69

Nodejs中文電子書

70 7 CoffeeScript

8

製作一個 Hubot的 Plurk Adapter

81 應用事項提醒

此應用範例以CoffeeScript編寫主要程式架構採用Github釋放的Hubot專案以下有幾個主要注意事項請讀者注意

bull Hubot 一開始的架構除了內建的兩個 Adapter 之外其餘都要以Nodejs的Module方式才能運作

bull Hubot的 binhubot裡面寫著 npm install所以不管你怎麼改原始碼也不能改變第一點的狀況

其實上述都在討論同一件事情NodeJS的模組而且模組不能設定相對路徑之類的來安裝一定要包裝成 npm相符和格式才有辦法正確執行

82 建立 Adapter

首先需要準備兩個檔案

bull packagejson

bull plurkcoffee

71

Nodejs中文電子書

有這兩個就足以變成 NodeJS的模組了在開始 Coding之前先來設定一下 packagejson相依性

rdquonamerdquo rdquohubot-plurkrdquo

rdquoversionrdquo rdquo011rdquo

rdquomainrdquo rdquoplurkrdquo

rdquodependenciesrdquo

rdquohubotrdquo rdquogt=205rdquo

rdquooauthrdquo rdquordquo

rdquocronrdquordquordquo

這邊會使用到NodeJS的 cron模組(Module)而登入 Plurk需要OAuth標準授權才行所以也需要加載 OAuth模組

注意Hubot在讀取非內建模組時會自動在前面加上 hubot-的前置

83 建立 Robot跟 API

本篇應用基本上程式碼大部分參考 Hubot - Twitter Adapter來製作主要差異只有在使用 OAuth這個模組Twitter有 Streaming可用而 Plurk則得用 Comet方式來達到即時讀取

plurkcoffee程式碼

Robot = require(rdquohubotrdquo)robot()

Adapter = require(rdquohubtordquo)adapter()

EventEmitter = require(rdquoeventsrdquo)EventEmitter

oauth = require(rdquooauthrdquo)

cronJob = require(rdquocronrdquo)CronJob

class Plurk exntends Adapter

class PlurkStreaming exnteds EventEmitter

72 8 製作一個 Hubot的 Plurk Adapter

Nodejs中文電子書

先弄個基本架構主要 Class皆參考 Twitter Adapter命名方式接著再將 Plurk這個 Class增加幾個Method基本上只要有 run send reply就夠了而 run用來做初始化的部分

class Plurk entends Adapter

send (plurk id strings⋯) -gt

reply (plurk id strings⋯) -gt

run -gt

看起來有點東西接著來處理主要的 Plurk API結合部分

class PlurkStreaming extends EventEmitter

consuctor (options) -gt

plurk (callback) -gt

觀察河道

getChannel -gt

取得 Comet 網址

reply (plurk id message) -gt

回噗

acceptFriends -gt

接受好友

get (path callback) -gt

GET 請求

post (path body callback)-gt

POST 請求(其實是裝飾)

request (method path body callback)-gt

主要的 OAuth 請求

comet (server callback)-gt

噗浪的 Comet 傳回是 JavaScript Callback 要另外處理後才會變成 JSON

然後把注意力集中到 constructor上先把建構子弄好

constructor (options) -gt

super()

if optionskey and optionssecret and optionstoken and optionstoken secret

key = optionskey

secret = optionssecret

token = optionstoken

token secret = optionstoken secret

83 建立 Robot跟 API 73

Nodejs中文電子書

建立 OAuth 連接

consumer = new oauthOAuth(

rdquohttpwwwplurkcomOAuthrequest tokenrdquo

rdquohttpwwwplurkcomOAuthaccess tokenrdquo

key

secret

rdquo10rdquo

rdquohttpwwwplurkcomOAuthauthorizerdquo

rdquoHMAC-SHA1rdquo

)

domain = rdquowwwplurkcomrdquo

初始化取得 Comet 網址

do getChannel

else

throw new Error(rdquo 參數不足需要 Key Secret Token Token Secretrdquo)

接著來處理 request這個 method

request (method path body callback) -gt

記錄一下這次的 Request

consolelog(rdquohttpdomainpathrdquo)

Callback 這邊先不丟進去要用另一種方式處理

request = consumerget(rdquohttpdomainpathrdquo token token secret null)

requeston rdquoresponserdquo (res) -gt

reson rdquodatardquo (chunk) -gt

parseResponse(chunk+rsquorsquo callback)

reson rdquoendrdquo (data) -gt

consolelog rdquoEnd Request pathrdquo

reson rdquoerrorrdquo (data) -gt

consolelog rdquoError rdquo + data

requestend()

處理資料

parseResponse = (data callback) -gt

if datalength gt 0

用 TryCatch 避免處理 JSON 出錯導致整個中斷

try

callback null JSONparse(data)

catch err

74 8 製作一個 Hubot的 Plurk Adapter

Nodejs中文電子書

consolelog(rdquoError Parse JSONrdquo + data err)

繼續執行

callback null data ||

大致上就是這樣上面程式的架構已經將整個 Hubot Plurk Adapter完成因為在測試時竟然因為噗浪 Lag而沒讀到完整的 Comet資料然後造成程式異常為了避免這個問題發生因此需要加上為了完美呈現需要再

加上 Comet的處理所以要使用到 EventEmitter的功能

comet (server callback) -gt

在 Callback 裡面會找不到自身所以設定區域變數

self =

記錄一下這次的 Request

consolelog(rdquo[Comet] serverrdquo)

Callback 這邊先不丟進去要用另一種方式處理

request = consumerget(rdquohttpdomainpathrdquo token token secret null)

requeston rdquoresponserdquo (res) -gt

reson rdquodatardquo (chunk) -gt

parseResponse(chunk+rsquorsquo callback)

reson rdquoendrdquo (data) -gt

consolelog rdquoEnd Request pathrdquo

請求結束發出事件通知可以進行下一次請求

selfemit rdquonextPlurkrdquo

reson rdquoerrorrdquo (data) -gt

consolelog rdquoError rdquo + data

requestend()

處理資料

parseResponse = (data callback) -gt

if datalength gt 0

用 trycatch 避免失敗中斷

try

去掉 JavaScript 的 Callback

data = datamatch(CometChannelscriptCallback((+))s)

jsonData = rdquordquo

if data

83 建立 Robot跟 API 75

Nodejs中文電子書

jsonData = JSONparse(data[1])

else

如果沒有任何 Match 嘗試直接 parse

jsonData = JSONparse(data)

catch err

consolelog(rdquo[Comet] Errorrdquo data err)

用 TryCatch 避免處理 JSON 出錯導致整個中斷

try

只傳入 json 的 data 部分

callback null jsonDatadata

catch err

consolelog(rdquo[Comet]Error Parse JSONrdquo + data err)

繼續執行

callback null data ||

後面的 get跟 post就簡單多了

get (path callback) -gt

request(rdquoGETrdquo path null callback)

post (path body callback) -gt

request(rdquoPOSTrdquo path body callback)

接著處理取的 Comet網址的 getChannel

getChannel -gt

self =

get rdquoAPPRealtimegetUserChannelrdquo (error data) -gt

if error

檢查是否有 comet server

if datacomet server

selfchannel = datacomet server

如果沒有 Channel Ready 就嘗試連接會失敗

selfemit(rsquochannel readyrsquo)

那麼先來處理 Plurk Adaper好處理的部份

send (plruk id strings⋯)-gt

跟 Reply 一樣直接交給 reply 做

reply plurk id strings⋯

76 8 製作一個 Hubot的 Plurk Adapter

Nodejs中文電子書

reply (plurk id strings⋯) -gt

stringsforEach (message) =gt

botreply(plruk id message)

接著把 run處理好就可以上線運作摟

run -gt

self =

options =

key processenvHUBOT PLURK KEY

secret processenvHUBOT PLURK SECRET

token processenvHUBOT PLURK TOKEN

token secret processenvHUBOT PLURK TOKEN SECRET

創建剛剛的 API

bot = new PlurkStreaming(options)

依照 Twitter 的 new RobotTextMessage 會沒有反應所以參考 hubot-minecraft 的方式

r = robotconstructor

處理噗浪河道訊息

doPlurk = (data)-gt

檢查是否為回噗

if dataresponse

datacontent raw = dataresponsecontent raw

datauser id = dataresponseuser id

確定有噗浪 ID 跟訊息

if dataplurk id and datacontent raw

selfreceive new rTextMessage(dataplurk id datacontent raw)

取得 Comet Server 完成開始第一次 Comet 連接

boton rdquochannel readyrdquo () -gt

botplurk selfdoPlurk

上一次 Comet 完成繼續 Polling

boton rdquonextPlurkrdquo ()-gt

botplurk selfdoPlurk

定時接受好友邀請

do botacceptFriends

bot = bot

83 建立 Robot跟 API 77

Nodejs中文電子書

終於完成 AdapterHubot專案裡面的 scripts資料夾內是互動部分不需要像 Adapter如此大費周章處理只新增檔案並且設計好對白之後就會回噗了我開發用的機器人在此大家可以去跟他玩玩[httpplurkcomelct9620 bot](httpplurkcomelct9620 bot)

84 原始資料提供

bull [製 作 一 個 Hubot 的 噗 浪 Adapter](http revo-skillfrosttw blog20120318create-a-hubot-plurk-adapter)

78 8 製作一個 Hubot的 Plurk Adapter

9

Nodejs好用工具介紹

91 logio

Real-time log monitoring in your browser

httplogioorg

nodejs (gt=0412 lt=0611)

npm config set unsafe-perm true

npm install -g --prefix=usrlocal logio

logio server start

httplocalhost8998

79

Nodejs中文電子書

80 9 Nodejs好用工具介紹

10

精選文章

101 Express

bull 使用 NodeJS + Express從 GETPOST Request取值 [邀稿中]

bull link2 [邀稿中]

bull link3 [邀稿中]

81

Nodejs中文電子書

82 10 精選文章

11

參考資源

111 Nodejs書籍

bull Programming Nodejs April 2012 (est) Travis Swicegood Pragmatic Bookshelf

bull e Little Book on CoffeeScript January 2012 Alex MacCaw OrsquoReilly Media

bull Node for Front-End Developers January 2012 Garann Means OrsquoReilly Media

bull Building Hypermedia APIs with HTML5 and Node November 2011 MikeAmundsen OrsquoReilly Media

bull Node Web Development August 2011 David Herron PacktPub

bull CoffeeScript Accelerated JavaScript Development July 2011 Trevor BurnhamPragmatic Bookshelf

bull Getting Started with GEO CouchDB and Nodejs July 2011 Mick omp-son OrsquoReilly Media

bull What Is Node July 2011 Brett McLaughlin OrsquoReilly Media

bull Node Up and Running May 2011 Tom Hughes-Croucher Mike WilsonOrsquoReilly Media

83

Nodejs中文電子書

bull Hands-on Nodejs e Nodejs introduction and API reference May 2011 Pe-dro Teixeira LeanPub

bull e Node Beginner Book Manuel Kiessling

bull Mastering Nodejs visionmedia

112 Nodejs影音教學

bull e Node Sessions e Best of OSCON 2011 August 2011 OrsquoReilly Media(Video)

bull Tom Hughes-Croucher on Node March 2011 OrsquoReilly Media (Video)

113 Nodejs教學網站

bull Node Tuts

114 Nodejs課程

bull Introduction To Nodejs Van Nguyen CodeLesson

84 11 參考資源

Page 67: Nodejs Wiki

Nodejs中文電子書

主要加入了rsquoquerystringrsquo這個moduel方便我們等一下解析由表單 POST回來的資料另外加入一個 formData的變數用來搜集待等一下表單回傳的資料前面的 GET範例我們只從 req拿出 url的資料這次要在利用req身上的事件處理

JavaScript 在訂閱事件時使用 addEventListener而 nodejs 使用的則是on這邊加上了監聽 data的事件會在瀏覽器傳送資料到Web Server時被執行參數是它所接收到的資料型態是字串

接著再增加 end的事件當瀏覽器的請求事件結束時它就會動作

由於瀏覽器使用 POST在上傳資料時會將資料一塊塊地上傳因為我們在監聽 data事件時透過 formData變數將它累加起來lt不過由於我們

上傳的資料很少一次就結束不過如果日後需要傳的是資料比較大的檔

案這個累加動作就很重要

當資料傳完就進到 end 事件中會用到 qsparse 來解析 formDataformData的內容是字串內容是

username=wordsmithampemail=wordsmith40somewhere

而 qsparse可以幫我們把這個 querystring轉成物件的格式也就是

username=wordsmithampemail=wordsmith40somewhere

一旦轉成物件並指定給 user之後其他的事情就和 GET方法時操作的一樣寫 response的表頭將內容回傳並將 userusername和 useremail代入到內容中

修改完成後接著執行 nodejs程式啟動 web server開啟瀏覽器進入表單測試看看POST的方式能否順利運作

完整程式碼如下

var http = require(rsquohttprsquo)

url = require(rsquourlrsquo)

fs = require(rdquofsrdquo)

qs = require(rsquoquerystringrsquo)

server

server = httpcreateServer(function (reqres)

var urlData

67 Express POST應用範例 61

Nodejs中文電子書

encode = rdquoutf8rdquo

filePath = rdquoviewexpress_post_example_formhtmlrdquo

formData

action

urlData = urlparse(requrltrue)

action = urlDatapathname

reswriteHead(200 rdquoContent-Typerdquordquotexthtml charset=utf-8rdquo)

if (action === rdquoSignuprdquo)

formData = rsquorsquo

reqon(rdquodatardquo function (data)

formData += data

)

reqon(rdquoendrdquo function ()

user = qsparse(formData)

resend(rdquolth1gtrdquo + userusername + rdquo 歡迎您的加入 lth1gtltpgt 我們已經將會員啟用信寄至rdquo + useremail + rdquoltpgtrdquo)

)

else

fsreadFile(filePath encode function(err file)

reswrite(file)

resend()

)

)

serverlisten(3000)

consolelog(rsquoServer 跑起來了現在時間是rsquo + new Date())

62 6 Express介紹

Nodejs中文電子書

68 Express AJAX應用範例

在 Nodejs要使用 Ajax傳送資料並且與之互動在接受資料的部份沒有太大的差別client端不是用 GET就是用 POST來傳資料重點在處理完後用 JSON格式回傳當然 Ajax不見得只傳 JSON格式有時是回傳一段 HTML碼不過後者對伺服器來說基本上就和前兩篇沒有差別了所以我們還是以回傳 JSON做為這一回的主題

這一回其實大多數的工作都會落在前端 Ajax上面前端要負責發送與接收資料並在接收資料後撤掉原先發送資料的表單並將取得的資料

改成 HTML格式之後放上頁面

首先先準備 HTML靜態頁面資料

ltDOCTYPE htmlgt

lthtmlgt

ltheadgt

lttitlegtNodejs 菜鳥筆記 (3)lttitlegt

ltlink rel=rdquostylesheetrdquo href=rdquocssstylecssrdquo type=rdquotextcssrdquo media=rdquoallrdquo gt

ltscript src=rdquohttpsajaxgoogleapiscomajaxlibsjquery171jqueryminjsrdquogtltscriptgt

ltheadgt

ltbodygt

lth1gtNodejs 菜鳥筆記-註冊 (用 Ajax)lth1gt

ltform id=rdquosignuprdquo action=rdquoSignuprdquo method=rdquoPOSTrdquogt

ltlabelgt 使用者名稱ltlabelgtltinput type=rdquotextrdquo id=rdquousernamerdquo name=rdquousernamerdquo gtltbrgt

ltlabelgt 電子郵件ltlabelgtltinput type=rdquotextrdquo id=rdquoemailrdquo name=rdquoemailrdquo gtltbrgt

ltinput type=rdquosubmitrdquo value=rdquo 註冊我的帳號rdquo gtltbrgt

ltformgt

ltdiv id=rdquoresultrdquogt

ltdivgt

ltscript type=rdquotextjavascriptrdquogt

$(function()$(rsquosignup input[type=rdquosubmitrdquo]rsquo)click(function(e)

var user =

userusername = $(rdquousernamerdquo)val()useremail = $(rdquoemailrdquo)val()

$post(rdquoSignuprdquouserfunction(data)greet(data)

)

68 Express AJAX應用範例 63

Nodejs中文電子書

epreventDefault()

)

var greet = function(msg)

var resultNode = $(rsquoresultrsquo)greeting = $(rsquolth2gtHirsquo+msgusername+rsquo 你的會員 id 是rsquo+ msgid +rsquo 我們會將會員啟動信寄至rsquo+msgemail+rsquolth2gtrsquo)

$(rsquosignuprsquo)hide()resultNodehtml(rdquordquo)

resultNodeappend(greeting)

)

ltscriptgt

ltbodygt

lthtmlgt

HTML頁面上準備了一個表單用來傳送註冊資料接著直接引用了Google CDN來載入 jQuery用來幫我們處理 Ajax的工作這次要傳送和接收的工作很大的變動都在 HTML頁面上的 JavaScript當中我們要做的事有 (相關 jQuery處理這邊不多做贅述指提起主要功能解說)

bull 用 jQUery取得 submit按鈕綁定它的 click動作

bull 取得表單 username和 email的值存放在 user這個物件中

bull 用 jQuery的 $post方法將 user的資料傳到 Server

bull 一旦成功取得資料後透過 greet這個 function組成回報給 user的訊息

bull 清空原本給使用者填資料的表單

bull 將 Server回傳的 usernameemail和 id這 3個資料組成回應的訊息

bull 將訊息放到原本表單的位置

經過以上的處理後一個 Ajax的表單的基本功能已經完成

接著進行 nodejs主要程式的編輯部分程式碼如下

var fs = require(rdquofsrdquo)

64 6 Express介紹

Nodejs中文電子書

qs = require(rsquoquerystringrsquo)

if (action === rdquoSignuprdquo)

formData = rsquorsquo

reqon(rdquodatardquo function (data)

formData += data

)

reqon(rdquoendrdquo function ()

var msg

user = qsparse(formData)

userid = rdquo123456rdquo

msg = JSONstringify(user)

reswriteHead(200 rdquoContent-Typerdquordquoapplicationjson charset=utf-8rdquordquoContent-Lengthrdquomsglength)

resend(msg)

)

這裡的程式和前面 POST範例基本上大同小異差別在

bull 幫 user的資料加上 id隨意存放一些文字進去讓 Server回傳的資料多於 Client端傳上來的不然會覺得 Server都沒做事

bull 增加了 msg 這個變數存放將 user 物件 JSON 文字化的結果JSONstringify這個轉換函式是 V8引擎所提供的如果你好奇的話

bull 大重點來了我們要告訴 Client端這次回傳的資料格式是 JSON所在 Content-type和 Content-Length要提供給 Client

Server很輕鬆就完成任務了最後進行程式測試啟動 nodejs主程式之後開啟瀏覽器就會看到表單填寫完畢按下送出就可以看到結果了

最後 nodejs本篇範例程式碼如下

var http = require(rsquohttprsquo)

url = require(rsquourlrsquo)

fs = require(rdquofsrdquo)

qs = require(rsquoquerystringrsquo)

server

68 Express AJAX應用範例 65

Nodejs中文電子書

server = httpcreateServer(function (reqres)

var urlData

encode = rdquoutf8rdquo

filePath = rdquoviewexpress_ajax_example_formhtmlrdquo

formData

action

urlData = urlparse(requrltrue)

action = urlDatapathname

if (action === rdquoSignuprdquo)

formData = rsquorsquo

reqon(rdquodatardquo function (data)

formData += data

)

reqon(rdquoendrdquo function ()

var msg

user = qsparse(formData)

userid = rdquo123456rdquo

msg = JSONstringify(user)

reswriteHead(200 rdquoContent-TyperdquordquoapplicationjsonrdquordquoContent-Lengthrdquomsglength)

resend(msg)

)

else

fsreadFile(filePath encode function(err file)

reswriteHead(200 rdquoContent-Typerdquordquotexthtml charset=utf-8rdquo)

reswrite(file)

resend()

)

)

serverlisten(3000)

66 6 Express介紹

Nodejs中文電子書

consolelog(rsquoServer 跑起來了現在時間是rsquo + new Date())

69 原始資料提供

bull [NodeJS初學者筆記 (1)-用 GET傳送資料] (httpithelpithomecomtwquestion10087402)

bull [NodeJS初學者筆記 (2)-用 POST傳送資料] (httpithelpithomecomtwquestion10087489)

bull [NodeJS 初學者筆記 (3)-用 Ajax 傳送資料] (httpithelpithomecomtwquestion10087627)

69 原始資料提供 67

Nodejs中文電子書

68 6 Express介紹

7

CoffeeScript

bull Spine

bull Hem

bull Spineapp

Letrsquos Have a Cup of CoffeeScript

bull es5-shim ECMAScript 5 compatibility shims for legacy JavaScript engines

ESnext

node-iform - A middleware for node-validator httpsgithubcomguileennode-iform

69

Nodejs中文電子書

70 7 CoffeeScript

8

製作一個 Hubot的 Plurk Adapter

81 應用事項提醒

此應用範例以CoffeeScript編寫主要程式架構採用Github釋放的Hubot專案以下有幾個主要注意事項請讀者注意

bull Hubot 一開始的架構除了內建的兩個 Adapter 之外其餘都要以Nodejs的Module方式才能運作

bull Hubot的 binhubot裡面寫著 npm install所以不管你怎麼改原始碼也不能改變第一點的狀況

其實上述都在討論同一件事情NodeJS的模組而且模組不能設定相對路徑之類的來安裝一定要包裝成 npm相符和格式才有辦法正確執行

82 建立 Adapter

首先需要準備兩個檔案

bull packagejson

bull plurkcoffee

71

Nodejs中文電子書

有這兩個就足以變成 NodeJS的模組了在開始 Coding之前先來設定一下 packagejson相依性

rdquonamerdquo rdquohubot-plurkrdquo

rdquoversionrdquo rdquo011rdquo

rdquomainrdquo rdquoplurkrdquo

rdquodependenciesrdquo

rdquohubotrdquo rdquogt=205rdquo

rdquooauthrdquo rdquordquo

rdquocronrdquordquordquo

這邊會使用到NodeJS的 cron模組(Module)而登入 Plurk需要OAuth標準授權才行所以也需要加載 OAuth模組

注意Hubot在讀取非內建模組時會自動在前面加上 hubot-的前置

83 建立 Robot跟 API

本篇應用基本上程式碼大部分參考 Hubot - Twitter Adapter來製作主要差異只有在使用 OAuth這個模組Twitter有 Streaming可用而 Plurk則得用 Comet方式來達到即時讀取

plurkcoffee程式碼

Robot = require(rdquohubotrdquo)robot()

Adapter = require(rdquohubtordquo)adapter()

EventEmitter = require(rdquoeventsrdquo)EventEmitter

oauth = require(rdquooauthrdquo)

cronJob = require(rdquocronrdquo)CronJob

class Plurk exntends Adapter

class PlurkStreaming exnteds EventEmitter

72 8 製作一個 Hubot的 Plurk Adapter

Nodejs中文電子書

先弄個基本架構主要 Class皆參考 Twitter Adapter命名方式接著再將 Plurk這個 Class增加幾個Method基本上只要有 run send reply就夠了而 run用來做初始化的部分

class Plurk entends Adapter

send (plurk id strings⋯) -gt

reply (plurk id strings⋯) -gt

run -gt

看起來有點東西接著來處理主要的 Plurk API結合部分

class PlurkStreaming extends EventEmitter

consuctor (options) -gt

plurk (callback) -gt

觀察河道

getChannel -gt

取得 Comet 網址

reply (plurk id message) -gt

回噗

acceptFriends -gt

接受好友

get (path callback) -gt

GET 請求

post (path body callback)-gt

POST 請求(其實是裝飾)

request (method path body callback)-gt

主要的 OAuth 請求

comet (server callback)-gt

噗浪的 Comet 傳回是 JavaScript Callback 要另外處理後才會變成 JSON

然後把注意力集中到 constructor上先把建構子弄好

constructor (options) -gt

super()

if optionskey and optionssecret and optionstoken and optionstoken secret

key = optionskey

secret = optionssecret

token = optionstoken

token secret = optionstoken secret

83 建立 Robot跟 API 73

Nodejs中文電子書

建立 OAuth 連接

consumer = new oauthOAuth(

rdquohttpwwwplurkcomOAuthrequest tokenrdquo

rdquohttpwwwplurkcomOAuthaccess tokenrdquo

key

secret

rdquo10rdquo

rdquohttpwwwplurkcomOAuthauthorizerdquo

rdquoHMAC-SHA1rdquo

)

domain = rdquowwwplurkcomrdquo

初始化取得 Comet 網址

do getChannel

else

throw new Error(rdquo 參數不足需要 Key Secret Token Token Secretrdquo)

接著來處理 request這個 method

request (method path body callback) -gt

記錄一下這次的 Request

consolelog(rdquohttpdomainpathrdquo)

Callback 這邊先不丟進去要用另一種方式處理

request = consumerget(rdquohttpdomainpathrdquo token token secret null)

requeston rdquoresponserdquo (res) -gt

reson rdquodatardquo (chunk) -gt

parseResponse(chunk+rsquorsquo callback)

reson rdquoendrdquo (data) -gt

consolelog rdquoEnd Request pathrdquo

reson rdquoerrorrdquo (data) -gt

consolelog rdquoError rdquo + data

requestend()

處理資料

parseResponse = (data callback) -gt

if datalength gt 0

用 TryCatch 避免處理 JSON 出錯導致整個中斷

try

callback null JSONparse(data)

catch err

74 8 製作一個 Hubot的 Plurk Adapter

Nodejs中文電子書

consolelog(rdquoError Parse JSONrdquo + data err)

繼續執行

callback null data ||

大致上就是這樣上面程式的架構已經將整個 Hubot Plurk Adapter完成因為在測試時竟然因為噗浪 Lag而沒讀到完整的 Comet資料然後造成程式異常為了避免這個問題發生因此需要加上為了完美呈現需要再

加上 Comet的處理所以要使用到 EventEmitter的功能

comet (server callback) -gt

在 Callback 裡面會找不到自身所以設定區域變數

self =

記錄一下這次的 Request

consolelog(rdquo[Comet] serverrdquo)

Callback 這邊先不丟進去要用另一種方式處理

request = consumerget(rdquohttpdomainpathrdquo token token secret null)

requeston rdquoresponserdquo (res) -gt

reson rdquodatardquo (chunk) -gt

parseResponse(chunk+rsquorsquo callback)

reson rdquoendrdquo (data) -gt

consolelog rdquoEnd Request pathrdquo

請求結束發出事件通知可以進行下一次請求

selfemit rdquonextPlurkrdquo

reson rdquoerrorrdquo (data) -gt

consolelog rdquoError rdquo + data

requestend()

處理資料

parseResponse = (data callback) -gt

if datalength gt 0

用 trycatch 避免失敗中斷

try

去掉 JavaScript 的 Callback

data = datamatch(CometChannelscriptCallback((+))s)

jsonData = rdquordquo

if data

83 建立 Robot跟 API 75

Nodejs中文電子書

jsonData = JSONparse(data[1])

else

如果沒有任何 Match 嘗試直接 parse

jsonData = JSONparse(data)

catch err

consolelog(rdquo[Comet] Errorrdquo data err)

用 TryCatch 避免處理 JSON 出錯導致整個中斷

try

只傳入 json 的 data 部分

callback null jsonDatadata

catch err

consolelog(rdquo[Comet]Error Parse JSONrdquo + data err)

繼續執行

callback null data ||

後面的 get跟 post就簡單多了

get (path callback) -gt

request(rdquoGETrdquo path null callback)

post (path body callback) -gt

request(rdquoPOSTrdquo path body callback)

接著處理取的 Comet網址的 getChannel

getChannel -gt

self =

get rdquoAPPRealtimegetUserChannelrdquo (error data) -gt

if error

檢查是否有 comet server

if datacomet server

selfchannel = datacomet server

如果沒有 Channel Ready 就嘗試連接會失敗

selfemit(rsquochannel readyrsquo)

那麼先來處理 Plurk Adaper好處理的部份

send (plruk id strings⋯)-gt

跟 Reply 一樣直接交給 reply 做

reply plurk id strings⋯

76 8 製作一個 Hubot的 Plurk Adapter

Nodejs中文電子書

reply (plurk id strings⋯) -gt

stringsforEach (message) =gt

botreply(plruk id message)

接著把 run處理好就可以上線運作摟

run -gt

self =

options =

key processenvHUBOT PLURK KEY

secret processenvHUBOT PLURK SECRET

token processenvHUBOT PLURK TOKEN

token secret processenvHUBOT PLURK TOKEN SECRET

創建剛剛的 API

bot = new PlurkStreaming(options)

依照 Twitter 的 new RobotTextMessage 會沒有反應所以參考 hubot-minecraft 的方式

r = robotconstructor

處理噗浪河道訊息

doPlurk = (data)-gt

檢查是否為回噗

if dataresponse

datacontent raw = dataresponsecontent raw

datauser id = dataresponseuser id

確定有噗浪 ID 跟訊息

if dataplurk id and datacontent raw

selfreceive new rTextMessage(dataplurk id datacontent raw)

取得 Comet Server 完成開始第一次 Comet 連接

boton rdquochannel readyrdquo () -gt

botplurk selfdoPlurk

上一次 Comet 完成繼續 Polling

boton rdquonextPlurkrdquo ()-gt

botplurk selfdoPlurk

定時接受好友邀請

do botacceptFriends

bot = bot

83 建立 Robot跟 API 77

Nodejs中文電子書

終於完成 AdapterHubot專案裡面的 scripts資料夾內是互動部分不需要像 Adapter如此大費周章處理只新增檔案並且設計好對白之後就會回噗了我開發用的機器人在此大家可以去跟他玩玩[httpplurkcomelct9620 bot](httpplurkcomelct9620 bot)

84 原始資料提供

bull [製 作 一 個 Hubot 的 噗 浪 Adapter](http revo-skillfrosttw blog20120318create-a-hubot-plurk-adapter)

78 8 製作一個 Hubot的 Plurk Adapter

9

Nodejs好用工具介紹

91 logio

Real-time log monitoring in your browser

httplogioorg

nodejs (gt=0412 lt=0611)

npm config set unsafe-perm true

npm install -g --prefix=usrlocal logio

logio server start

httplocalhost8998

79

Nodejs中文電子書

80 9 Nodejs好用工具介紹

10

精選文章

101 Express

bull 使用 NodeJS + Express從 GETPOST Request取值 [邀稿中]

bull link2 [邀稿中]

bull link3 [邀稿中]

81

Nodejs中文電子書

82 10 精選文章

11

參考資源

111 Nodejs書籍

bull Programming Nodejs April 2012 (est) Travis Swicegood Pragmatic Bookshelf

bull e Little Book on CoffeeScript January 2012 Alex MacCaw OrsquoReilly Media

bull Node for Front-End Developers January 2012 Garann Means OrsquoReilly Media

bull Building Hypermedia APIs with HTML5 and Node November 2011 MikeAmundsen OrsquoReilly Media

bull Node Web Development August 2011 David Herron PacktPub

bull CoffeeScript Accelerated JavaScript Development July 2011 Trevor BurnhamPragmatic Bookshelf

bull Getting Started with GEO CouchDB and Nodejs July 2011 Mick omp-son OrsquoReilly Media

bull What Is Node July 2011 Brett McLaughlin OrsquoReilly Media

bull Node Up and Running May 2011 Tom Hughes-Croucher Mike WilsonOrsquoReilly Media

83

Nodejs中文電子書

bull Hands-on Nodejs e Nodejs introduction and API reference May 2011 Pe-dro Teixeira LeanPub

bull e Node Beginner Book Manuel Kiessling

bull Mastering Nodejs visionmedia

112 Nodejs影音教學

bull e Node Sessions e Best of OSCON 2011 August 2011 OrsquoReilly Media(Video)

bull Tom Hughes-Croucher on Node March 2011 OrsquoReilly Media (Video)

113 Nodejs教學網站

bull Node Tuts

114 Nodejs課程

bull Introduction To Nodejs Van Nguyen CodeLesson

84 11 參考資源

Page 68: Nodejs Wiki

Nodejs中文電子書

encode = rdquoutf8rdquo

filePath = rdquoviewexpress_post_example_formhtmlrdquo

formData

action

urlData = urlparse(requrltrue)

action = urlDatapathname

reswriteHead(200 rdquoContent-Typerdquordquotexthtml charset=utf-8rdquo)

if (action === rdquoSignuprdquo)

formData = rsquorsquo

reqon(rdquodatardquo function (data)

formData += data

)

reqon(rdquoendrdquo function ()

user = qsparse(formData)

resend(rdquolth1gtrdquo + userusername + rdquo 歡迎您的加入 lth1gtltpgt 我們已經將會員啟用信寄至rdquo + useremail + rdquoltpgtrdquo)

)

else

fsreadFile(filePath encode function(err file)

reswrite(file)

resend()

)

)

serverlisten(3000)

consolelog(rsquoServer 跑起來了現在時間是rsquo + new Date())

62 6 Express介紹

Nodejs中文電子書

68 Express AJAX應用範例

在 Nodejs要使用 Ajax傳送資料並且與之互動在接受資料的部份沒有太大的差別client端不是用 GET就是用 POST來傳資料重點在處理完後用 JSON格式回傳當然 Ajax不見得只傳 JSON格式有時是回傳一段 HTML碼不過後者對伺服器來說基本上就和前兩篇沒有差別了所以我們還是以回傳 JSON做為這一回的主題

這一回其實大多數的工作都會落在前端 Ajax上面前端要負責發送與接收資料並在接收資料後撤掉原先發送資料的表單並將取得的資料

改成 HTML格式之後放上頁面

首先先準備 HTML靜態頁面資料

ltDOCTYPE htmlgt

lthtmlgt

ltheadgt

lttitlegtNodejs 菜鳥筆記 (3)lttitlegt

ltlink rel=rdquostylesheetrdquo href=rdquocssstylecssrdquo type=rdquotextcssrdquo media=rdquoallrdquo gt

ltscript src=rdquohttpsajaxgoogleapiscomajaxlibsjquery171jqueryminjsrdquogtltscriptgt

ltheadgt

ltbodygt

lth1gtNodejs 菜鳥筆記-註冊 (用 Ajax)lth1gt

ltform id=rdquosignuprdquo action=rdquoSignuprdquo method=rdquoPOSTrdquogt

ltlabelgt 使用者名稱ltlabelgtltinput type=rdquotextrdquo id=rdquousernamerdquo name=rdquousernamerdquo gtltbrgt

ltlabelgt 電子郵件ltlabelgtltinput type=rdquotextrdquo id=rdquoemailrdquo name=rdquoemailrdquo gtltbrgt

ltinput type=rdquosubmitrdquo value=rdquo 註冊我的帳號rdquo gtltbrgt

ltformgt

ltdiv id=rdquoresultrdquogt

ltdivgt

ltscript type=rdquotextjavascriptrdquogt

$(function()$(rsquosignup input[type=rdquosubmitrdquo]rsquo)click(function(e)

var user =

userusername = $(rdquousernamerdquo)val()useremail = $(rdquoemailrdquo)val()

$post(rdquoSignuprdquouserfunction(data)greet(data)

)

68 Express AJAX應用範例 63

Nodejs中文電子書

epreventDefault()

)

var greet = function(msg)

var resultNode = $(rsquoresultrsquo)greeting = $(rsquolth2gtHirsquo+msgusername+rsquo 你的會員 id 是rsquo+ msgid +rsquo 我們會將會員啟動信寄至rsquo+msgemail+rsquolth2gtrsquo)

$(rsquosignuprsquo)hide()resultNodehtml(rdquordquo)

resultNodeappend(greeting)

)

ltscriptgt

ltbodygt

lthtmlgt

HTML頁面上準備了一個表單用來傳送註冊資料接著直接引用了Google CDN來載入 jQuery用來幫我們處理 Ajax的工作這次要傳送和接收的工作很大的變動都在 HTML頁面上的 JavaScript當中我們要做的事有 (相關 jQuery處理這邊不多做贅述指提起主要功能解說)

bull 用 jQUery取得 submit按鈕綁定它的 click動作

bull 取得表單 username和 email的值存放在 user這個物件中

bull 用 jQuery的 $post方法將 user的資料傳到 Server

bull 一旦成功取得資料後透過 greet這個 function組成回報給 user的訊息

bull 清空原本給使用者填資料的表單

bull 將 Server回傳的 usernameemail和 id這 3個資料組成回應的訊息

bull 將訊息放到原本表單的位置

經過以上的處理後一個 Ajax的表單的基本功能已經完成

接著進行 nodejs主要程式的編輯部分程式碼如下

var fs = require(rdquofsrdquo)

64 6 Express介紹

Nodejs中文電子書

qs = require(rsquoquerystringrsquo)

if (action === rdquoSignuprdquo)

formData = rsquorsquo

reqon(rdquodatardquo function (data)

formData += data

)

reqon(rdquoendrdquo function ()

var msg

user = qsparse(formData)

userid = rdquo123456rdquo

msg = JSONstringify(user)

reswriteHead(200 rdquoContent-Typerdquordquoapplicationjson charset=utf-8rdquordquoContent-Lengthrdquomsglength)

resend(msg)

)

這裡的程式和前面 POST範例基本上大同小異差別在

bull 幫 user的資料加上 id隨意存放一些文字進去讓 Server回傳的資料多於 Client端傳上來的不然會覺得 Server都沒做事

bull 增加了 msg 這個變數存放將 user 物件 JSON 文字化的結果JSONstringify這個轉換函式是 V8引擎所提供的如果你好奇的話

bull 大重點來了我們要告訴 Client端這次回傳的資料格式是 JSON所在 Content-type和 Content-Length要提供給 Client

Server很輕鬆就完成任務了最後進行程式測試啟動 nodejs主程式之後開啟瀏覽器就會看到表單填寫完畢按下送出就可以看到結果了

最後 nodejs本篇範例程式碼如下

var http = require(rsquohttprsquo)

url = require(rsquourlrsquo)

fs = require(rdquofsrdquo)

qs = require(rsquoquerystringrsquo)

server

68 Express AJAX應用範例 65

Nodejs中文電子書

server = httpcreateServer(function (reqres)

var urlData

encode = rdquoutf8rdquo

filePath = rdquoviewexpress_ajax_example_formhtmlrdquo

formData

action

urlData = urlparse(requrltrue)

action = urlDatapathname

if (action === rdquoSignuprdquo)

formData = rsquorsquo

reqon(rdquodatardquo function (data)

formData += data

)

reqon(rdquoendrdquo function ()

var msg

user = qsparse(formData)

userid = rdquo123456rdquo

msg = JSONstringify(user)

reswriteHead(200 rdquoContent-TyperdquordquoapplicationjsonrdquordquoContent-Lengthrdquomsglength)

resend(msg)

)

else

fsreadFile(filePath encode function(err file)

reswriteHead(200 rdquoContent-Typerdquordquotexthtml charset=utf-8rdquo)

reswrite(file)

resend()

)

)

serverlisten(3000)

66 6 Express介紹

Nodejs中文電子書

consolelog(rsquoServer 跑起來了現在時間是rsquo + new Date())

69 原始資料提供

bull [NodeJS初學者筆記 (1)-用 GET傳送資料] (httpithelpithomecomtwquestion10087402)

bull [NodeJS初學者筆記 (2)-用 POST傳送資料] (httpithelpithomecomtwquestion10087489)

bull [NodeJS 初學者筆記 (3)-用 Ajax 傳送資料] (httpithelpithomecomtwquestion10087627)

69 原始資料提供 67

Nodejs中文電子書

68 6 Express介紹

7

CoffeeScript

bull Spine

bull Hem

bull Spineapp

Letrsquos Have a Cup of CoffeeScript

bull es5-shim ECMAScript 5 compatibility shims for legacy JavaScript engines

ESnext

node-iform - A middleware for node-validator httpsgithubcomguileennode-iform

69

Nodejs中文電子書

70 7 CoffeeScript

8

製作一個 Hubot的 Plurk Adapter

81 應用事項提醒

此應用範例以CoffeeScript編寫主要程式架構採用Github釋放的Hubot專案以下有幾個主要注意事項請讀者注意

bull Hubot 一開始的架構除了內建的兩個 Adapter 之外其餘都要以Nodejs的Module方式才能運作

bull Hubot的 binhubot裡面寫著 npm install所以不管你怎麼改原始碼也不能改變第一點的狀況

其實上述都在討論同一件事情NodeJS的模組而且模組不能設定相對路徑之類的來安裝一定要包裝成 npm相符和格式才有辦法正確執行

82 建立 Adapter

首先需要準備兩個檔案

bull packagejson

bull plurkcoffee

71

Nodejs中文電子書

有這兩個就足以變成 NodeJS的模組了在開始 Coding之前先來設定一下 packagejson相依性

rdquonamerdquo rdquohubot-plurkrdquo

rdquoversionrdquo rdquo011rdquo

rdquomainrdquo rdquoplurkrdquo

rdquodependenciesrdquo

rdquohubotrdquo rdquogt=205rdquo

rdquooauthrdquo rdquordquo

rdquocronrdquordquordquo

這邊會使用到NodeJS的 cron模組(Module)而登入 Plurk需要OAuth標準授權才行所以也需要加載 OAuth模組

注意Hubot在讀取非內建模組時會自動在前面加上 hubot-的前置

83 建立 Robot跟 API

本篇應用基本上程式碼大部分參考 Hubot - Twitter Adapter來製作主要差異只有在使用 OAuth這個模組Twitter有 Streaming可用而 Plurk則得用 Comet方式來達到即時讀取

plurkcoffee程式碼

Robot = require(rdquohubotrdquo)robot()

Adapter = require(rdquohubtordquo)adapter()

EventEmitter = require(rdquoeventsrdquo)EventEmitter

oauth = require(rdquooauthrdquo)

cronJob = require(rdquocronrdquo)CronJob

class Plurk exntends Adapter

class PlurkStreaming exnteds EventEmitter

72 8 製作一個 Hubot的 Plurk Adapter

Nodejs中文電子書

先弄個基本架構主要 Class皆參考 Twitter Adapter命名方式接著再將 Plurk這個 Class增加幾個Method基本上只要有 run send reply就夠了而 run用來做初始化的部分

class Plurk entends Adapter

send (plurk id strings⋯) -gt

reply (plurk id strings⋯) -gt

run -gt

看起來有點東西接著來處理主要的 Plurk API結合部分

class PlurkStreaming extends EventEmitter

consuctor (options) -gt

plurk (callback) -gt

觀察河道

getChannel -gt

取得 Comet 網址

reply (plurk id message) -gt

回噗

acceptFriends -gt

接受好友

get (path callback) -gt

GET 請求

post (path body callback)-gt

POST 請求(其實是裝飾)

request (method path body callback)-gt

主要的 OAuth 請求

comet (server callback)-gt

噗浪的 Comet 傳回是 JavaScript Callback 要另外處理後才會變成 JSON

然後把注意力集中到 constructor上先把建構子弄好

constructor (options) -gt

super()

if optionskey and optionssecret and optionstoken and optionstoken secret

key = optionskey

secret = optionssecret

token = optionstoken

token secret = optionstoken secret

83 建立 Robot跟 API 73

Nodejs中文電子書

建立 OAuth 連接

consumer = new oauthOAuth(

rdquohttpwwwplurkcomOAuthrequest tokenrdquo

rdquohttpwwwplurkcomOAuthaccess tokenrdquo

key

secret

rdquo10rdquo

rdquohttpwwwplurkcomOAuthauthorizerdquo

rdquoHMAC-SHA1rdquo

)

domain = rdquowwwplurkcomrdquo

初始化取得 Comet 網址

do getChannel

else

throw new Error(rdquo 參數不足需要 Key Secret Token Token Secretrdquo)

接著來處理 request這個 method

request (method path body callback) -gt

記錄一下這次的 Request

consolelog(rdquohttpdomainpathrdquo)

Callback 這邊先不丟進去要用另一種方式處理

request = consumerget(rdquohttpdomainpathrdquo token token secret null)

requeston rdquoresponserdquo (res) -gt

reson rdquodatardquo (chunk) -gt

parseResponse(chunk+rsquorsquo callback)

reson rdquoendrdquo (data) -gt

consolelog rdquoEnd Request pathrdquo

reson rdquoerrorrdquo (data) -gt

consolelog rdquoError rdquo + data

requestend()

處理資料

parseResponse = (data callback) -gt

if datalength gt 0

用 TryCatch 避免處理 JSON 出錯導致整個中斷

try

callback null JSONparse(data)

catch err

74 8 製作一個 Hubot的 Plurk Adapter

Nodejs中文電子書

consolelog(rdquoError Parse JSONrdquo + data err)

繼續執行

callback null data ||

大致上就是這樣上面程式的架構已經將整個 Hubot Plurk Adapter完成因為在測試時竟然因為噗浪 Lag而沒讀到完整的 Comet資料然後造成程式異常為了避免這個問題發生因此需要加上為了完美呈現需要再

加上 Comet的處理所以要使用到 EventEmitter的功能

comet (server callback) -gt

在 Callback 裡面會找不到自身所以設定區域變數

self =

記錄一下這次的 Request

consolelog(rdquo[Comet] serverrdquo)

Callback 這邊先不丟進去要用另一種方式處理

request = consumerget(rdquohttpdomainpathrdquo token token secret null)

requeston rdquoresponserdquo (res) -gt

reson rdquodatardquo (chunk) -gt

parseResponse(chunk+rsquorsquo callback)

reson rdquoendrdquo (data) -gt

consolelog rdquoEnd Request pathrdquo

請求結束發出事件通知可以進行下一次請求

selfemit rdquonextPlurkrdquo

reson rdquoerrorrdquo (data) -gt

consolelog rdquoError rdquo + data

requestend()

處理資料

parseResponse = (data callback) -gt

if datalength gt 0

用 trycatch 避免失敗中斷

try

去掉 JavaScript 的 Callback

data = datamatch(CometChannelscriptCallback((+))s)

jsonData = rdquordquo

if data

83 建立 Robot跟 API 75

Nodejs中文電子書

jsonData = JSONparse(data[1])

else

如果沒有任何 Match 嘗試直接 parse

jsonData = JSONparse(data)

catch err

consolelog(rdquo[Comet] Errorrdquo data err)

用 TryCatch 避免處理 JSON 出錯導致整個中斷

try

只傳入 json 的 data 部分

callback null jsonDatadata

catch err

consolelog(rdquo[Comet]Error Parse JSONrdquo + data err)

繼續執行

callback null data ||

後面的 get跟 post就簡單多了

get (path callback) -gt

request(rdquoGETrdquo path null callback)

post (path body callback) -gt

request(rdquoPOSTrdquo path body callback)

接著處理取的 Comet網址的 getChannel

getChannel -gt

self =

get rdquoAPPRealtimegetUserChannelrdquo (error data) -gt

if error

檢查是否有 comet server

if datacomet server

selfchannel = datacomet server

如果沒有 Channel Ready 就嘗試連接會失敗

selfemit(rsquochannel readyrsquo)

那麼先來處理 Plurk Adaper好處理的部份

send (plruk id strings⋯)-gt

跟 Reply 一樣直接交給 reply 做

reply plurk id strings⋯

76 8 製作一個 Hubot的 Plurk Adapter

Nodejs中文電子書

reply (plurk id strings⋯) -gt

stringsforEach (message) =gt

botreply(plruk id message)

接著把 run處理好就可以上線運作摟

run -gt

self =

options =

key processenvHUBOT PLURK KEY

secret processenvHUBOT PLURK SECRET

token processenvHUBOT PLURK TOKEN

token secret processenvHUBOT PLURK TOKEN SECRET

創建剛剛的 API

bot = new PlurkStreaming(options)

依照 Twitter 的 new RobotTextMessage 會沒有反應所以參考 hubot-minecraft 的方式

r = robotconstructor

處理噗浪河道訊息

doPlurk = (data)-gt

檢查是否為回噗

if dataresponse

datacontent raw = dataresponsecontent raw

datauser id = dataresponseuser id

確定有噗浪 ID 跟訊息

if dataplurk id and datacontent raw

selfreceive new rTextMessage(dataplurk id datacontent raw)

取得 Comet Server 完成開始第一次 Comet 連接

boton rdquochannel readyrdquo () -gt

botplurk selfdoPlurk

上一次 Comet 完成繼續 Polling

boton rdquonextPlurkrdquo ()-gt

botplurk selfdoPlurk

定時接受好友邀請

do botacceptFriends

bot = bot

83 建立 Robot跟 API 77

Nodejs中文電子書

終於完成 AdapterHubot專案裡面的 scripts資料夾內是互動部分不需要像 Adapter如此大費周章處理只新增檔案並且設計好對白之後就會回噗了我開發用的機器人在此大家可以去跟他玩玩[httpplurkcomelct9620 bot](httpplurkcomelct9620 bot)

84 原始資料提供

bull [製 作 一 個 Hubot 的 噗 浪 Adapter](http revo-skillfrosttw blog20120318create-a-hubot-plurk-adapter)

78 8 製作一個 Hubot的 Plurk Adapter

9

Nodejs好用工具介紹

91 logio

Real-time log monitoring in your browser

httplogioorg

nodejs (gt=0412 lt=0611)

npm config set unsafe-perm true

npm install -g --prefix=usrlocal logio

logio server start

httplocalhost8998

79

Nodejs中文電子書

80 9 Nodejs好用工具介紹

10

精選文章

101 Express

bull 使用 NodeJS + Express從 GETPOST Request取值 [邀稿中]

bull link2 [邀稿中]

bull link3 [邀稿中]

81

Nodejs中文電子書

82 10 精選文章

11

參考資源

111 Nodejs書籍

bull Programming Nodejs April 2012 (est) Travis Swicegood Pragmatic Bookshelf

bull e Little Book on CoffeeScript January 2012 Alex MacCaw OrsquoReilly Media

bull Node for Front-End Developers January 2012 Garann Means OrsquoReilly Media

bull Building Hypermedia APIs with HTML5 and Node November 2011 MikeAmundsen OrsquoReilly Media

bull Node Web Development August 2011 David Herron PacktPub

bull CoffeeScript Accelerated JavaScript Development July 2011 Trevor BurnhamPragmatic Bookshelf

bull Getting Started with GEO CouchDB and Nodejs July 2011 Mick omp-son OrsquoReilly Media

bull What Is Node July 2011 Brett McLaughlin OrsquoReilly Media

bull Node Up and Running May 2011 Tom Hughes-Croucher Mike WilsonOrsquoReilly Media

83

Nodejs中文電子書

bull Hands-on Nodejs e Nodejs introduction and API reference May 2011 Pe-dro Teixeira LeanPub

bull e Node Beginner Book Manuel Kiessling

bull Mastering Nodejs visionmedia

112 Nodejs影音教學

bull e Node Sessions e Best of OSCON 2011 August 2011 OrsquoReilly Media(Video)

bull Tom Hughes-Croucher on Node March 2011 OrsquoReilly Media (Video)

113 Nodejs教學網站

bull Node Tuts

114 Nodejs課程

bull Introduction To Nodejs Van Nguyen CodeLesson

84 11 參考資源

Page 69: Nodejs Wiki

Nodejs中文電子書

68 Express AJAX應用範例

在 Nodejs要使用 Ajax傳送資料並且與之互動在接受資料的部份沒有太大的差別client端不是用 GET就是用 POST來傳資料重點在處理完後用 JSON格式回傳當然 Ajax不見得只傳 JSON格式有時是回傳一段 HTML碼不過後者對伺服器來說基本上就和前兩篇沒有差別了所以我們還是以回傳 JSON做為這一回的主題

這一回其實大多數的工作都會落在前端 Ajax上面前端要負責發送與接收資料並在接收資料後撤掉原先發送資料的表單並將取得的資料

改成 HTML格式之後放上頁面

首先先準備 HTML靜態頁面資料

ltDOCTYPE htmlgt

lthtmlgt

ltheadgt

lttitlegtNodejs 菜鳥筆記 (3)lttitlegt

ltlink rel=rdquostylesheetrdquo href=rdquocssstylecssrdquo type=rdquotextcssrdquo media=rdquoallrdquo gt

ltscript src=rdquohttpsajaxgoogleapiscomajaxlibsjquery171jqueryminjsrdquogtltscriptgt

ltheadgt

ltbodygt

lth1gtNodejs 菜鳥筆記-註冊 (用 Ajax)lth1gt

ltform id=rdquosignuprdquo action=rdquoSignuprdquo method=rdquoPOSTrdquogt

ltlabelgt 使用者名稱ltlabelgtltinput type=rdquotextrdquo id=rdquousernamerdquo name=rdquousernamerdquo gtltbrgt

ltlabelgt 電子郵件ltlabelgtltinput type=rdquotextrdquo id=rdquoemailrdquo name=rdquoemailrdquo gtltbrgt

ltinput type=rdquosubmitrdquo value=rdquo 註冊我的帳號rdquo gtltbrgt

ltformgt

ltdiv id=rdquoresultrdquogt

ltdivgt

ltscript type=rdquotextjavascriptrdquogt

$(function()$(rsquosignup input[type=rdquosubmitrdquo]rsquo)click(function(e)

var user =

userusername = $(rdquousernamerdquo)val()useremail = $(rdquoemailrdquo)val()

$post(rdquoSignuprdquouserfunction(data)greet(data)

)

68 Express AJAX應用範例 63

Nodejs中文電子書

epreventDefault()

)

var greet = function(msg)

var resultNode = $(rsquoresultrsquo)greeting = $(rsquolth2gtHirsquo+msgusername+rsquo 你的會員 id 是rsquo+ msgid +rsquo 我們會將會員啟動信寄至rsquo+msgemail+rsquolth2gtrsquo)

$(rsquosignuprsquo)hide()resultNodehtml(rdquordquo)

resultNodeappend(greeting)

)

ltscriptgt

ltbodygt

lthtmlgt

HTML頁面上準備了一個表單用來傳送註冊資料接著直接引用了Google CDN來載入 jQuery用來幫我們處理 Ajax的工作這次要傳送和接收的工作很大的變動都在 HTML頁面上的 JavaScript當中我們要做的事有 (相關 jQuery處理這邊不多做贅述指提起主要功能解說)

bull 用 jQUery取得 submit按鈕綁定它的 click動作

bull 取得表單 username和 email的值存放在 user這個物件中

bull 用 jQuery的 $post方法將 user的資料傳到 Server

bull 一旦成功取得資料後透過 greet這個 function組成回報給 user的訊息

bull 清空原本給使用者填資料的表單

bull 將 Server回傳的 usernameemail和 id這 3個資料組成回應的訊息

bull 將訊息放到原本表單的位置

經過以上的處理後一個 Ajax的表單的基本功能已經完成

接著進行 nodejs主要程式的編輯部分程式碼如下

var fs = require(rdquofsrdquo)

64 6 Express介紹

Nodejs中文電子書

qs = require(rsquoquerystringrsquo)

if (action === rdquoSignuprdquo)

formData = rsquorsquo

reqon(rdquodatardquo function (data)

formData += data

)

reqon(rdquoendrdquo function ()

var msg

user = qsparse(formData)

userid = rdquo123456rdquo

msg = JSONstringify(user)

reswriteHead(200 rdquoContent-Typerdquordquoapplicationjson charset=utf-8rdquordquoContent-Lengthrdquomsglength)

resend(msg)

)

這裡的程式和前面 POST範例基本上大同小異差別在

bull 幫 user的資料加上 id隨意存放一些文字進去讓 Server回傳的資料多於 Client端傳上來的不然會覺得 Server都沒做事

bull 增加了 msg 這個變數存放將 user 物件 JSON 文字化的結果JSONstringify這個轉換函式是 V8引擎所提供的如果你好奇的話

bull 大重點來了我們要告訴 Client端這次回傳的資料格式是 JSON所在 Content-type和 Content-Length要提供給 Client

Server很輕鬆就完成任務了最後進行程式測試啟動 nodejs主程式之後開啟瀏覽器就會看到表單填寫完畢按下送出就可以看到結果了

最後 nodejs本篇範例程式碼如下

var http = require(rsquohttprsquo)

url = require(rsquourlrsquo)

fs = require(rdquofsrdquo)

qs = require(rsquoquerystringrsquo)

server

68 Express AJAX應用範例 65

Nodejs中文電子書

server = httpcreateServer(function (reqres)

var urlData

encode = rdquoutf8rdquo

filePath = rdquoviewexpress_ajax_example_formhtmlrdquo

formData

action

urlData = urlparse(requrltrue)

action = urlDatapathname

if (action === rdquoSignuprdquo)

formData = rsquorsquo

reqon(rdquodatardquo function (data)

formData += data

)

reqon(rdquoendrdquo function ()

var msg

user = qsparse(formData)

userid = rdquo123456rdquo

msg = JSONstringify(user)

reswriteHead(200 rdquoContent-TyperdquordquoapplicationjsonrdquordquoContent-Lengthrdquomsglength)

resend(msg)

)

else

fsreadFile(filePath encode function(err file)

reswriteHead(200 rdquoContent-Typerdquordquotexthtml charset=utf-8rdquo)

reswrite(file)

resend()

)

)

serverlisten(3000)

66 6 Express介紹

Nodejs中文電子書

consolelog(rsquoServer 跑起來了現在時間是rsquo + new Date())

69 原始資料提供

bull [NodeJS初學者筆記 (1)-用 GET傳送資料] (httpithelpithomecomtwquestion10087402)

bull [NodeJS初學者筆記 (2)-用 POST傳送資料] (httpithelpithomecomtwquestion10087489)

bull [NodeJS 初學者筆記 (3)-用 Ajax 傳送資料] (httpithelpithomecomtwquestion10087627)

69 原始資料提供 67

Nodejs中文電子書

68 6 Express介紹

7

CoffeeScript

bull Spine

bull Hem

bull Spineapp

Letrsquos Have a Cup of CoffeeScript

bull es5-shim ECMAScript 5 compatibility shims for legacy JavaScript engines

ESnext

node-iform - A middleware for node-validator httpsgithubcomguileennode-iform

69

Nodejs中文電子書

70 7 CoffeeScript

8

製作一個 Hubot的 Plurk Adapter

81 應用事項提醒

此應用範例以CoffeeScript編寫主要程式架構採用Github釋放的Hubot專案以下有幾個主要注意事項請讀者注意

bull Hubot 一開始的架構除了內建的兩個 Adapter 之外其餘都要以Nodejs的Module方式才能運作

bull Hubot的 binhubot裡面寫著 npm install所以不管你怎麼改原始碼也不能改變第一點的狀況

其實上述都在討論同一件事情NodeJS的模組而且模組不能設定相對路徑之類的來安裝一定要包裝成 npm相符和格式才有辦法正確執行

82 建立 Adapter

首先需要準備兩個檔案

bull packagejson

bull plurkcoffee

71

Nodejs中文電子書

有這兩個就足以變成 NodeJS的模組了在開始 Coding之前先來設定一下 packagejson相依性

rdquonamerdquo rdquohubot-plurkrdquo

rdquoversionrdquo rdquo011rdquo

rdquomainrdquo rdquoplurkrdquo

rdquodependenciesrdquo

rdquohubotrdquo rdquogt=205rdquo

rdquooauthrdquo rdquordquo

rdquocronrdquordquordquo

這邊會使用到NodeJS的 cron模組(Module)而登入 Plurk需要OAuth標準授權才行所以也需要加載 OAuth模組

注意Hubot在讀取非內建模組時會自動在前面加上 hubot-的前置

83 建立 Robot跟 API

本篇應用基本上程式碼大部分參考 Hubot - Twitter Adapter來製作主要差異只有在使用 OAuth這個模組Twitter有 Streaming可用而 Plurk則得用 Comet方式來達到即時讀取

plurkcoffee程式碼

Robot = require(rdquohubotrdquo)robot()

Adapter = require(rdquohubtordquo)adapter()

EventEmitter = require(rdquoeventsrdquo)EventEmitter

oauth = require(rdquooauthrdquo)

cronJob = require(rdquocronrdquo)CronJob

class Plurk exntends Adapter

class PlurkStreaming exnteds EventEmitter

72 8 製作一個 Hubot的 Plurk Adapter

Nodejs中文電子書

先弄個基本架構主要 Class皆參考 Twitter Adapter命名方式接著再將 Plurk這個 Class增加幾個Method基本上只要有 run send reply就夠了而 run用來做初始化的部分

class Plurk entends Adapter

send (plurk id strings⋯) -gt

reply (plurk id strings⋯) -gt

run -gt

看起來有點東西接著來處理主要的 Plurk API結合部分

class PlurkStreaming extends EventEmitter

consuctor (options) -gt

plurk (callback) -gt

觀察河道

getChannel -gt

取得 Comet 網址

reply (plurk id message) -gt

回噗

acceptFriends -gt

接受好友

get (path callback) -gt

GET 請求

post (path body callback)-gt

POST 請求(其實是裝飾)

request (method path body callback)-gt

主要的 OAuth 請求

comet (server callback)-gt

噗浪的 Comet 傳回是 JavaScript Callback 要另外處理後才會變成 JSON

然後把注意力集中到 constructor上先把建構子弄好

constructor (options) -gt

super()

if optionskey and optionssecret and optionstoken and optionstoken secret

key = optionskey

secret = optionssecret

token = optionstoken

token secret = optionstoken secret

83 建立 Robot跟 API 73

Nodejs中文電子書

建立 OAuth 連接

consumer = new oauthOAuth(

rdquohttpwwwplurkcomOAuthrequest tokenrdquo

rdquohttpwwwplurkcomOAuthaccess tokenrdquo

key

secret

rdquo10rdquo

rdquohttpwwwplurkcomOAuthauthorizerdquo

rdquoHMAC-SHA1rdquo

)

domain = rdquowwwplurkcomrdquo

初始化取得 Comet 網址

do getChannel

else

throw new Error(rdquo 參數不足需要 Key Secret Token Token Secretrdquo)

接著來處理 request這個 method

request (method path body callback) -gt

記錄一下這次的 Request

consolelog(rdquohttpdomainpathrdquo)

Callback 這邊先不丟進去要用另一種方式處理

request = consumerget(rdquohttpdomainpathrdquo token token secret null)

requeston rdquoresponserdquo (res) -gt

reson rdquodatardquo (chunk) -gt

parseResponse(chunk+rsquorsquo callback)

reson rdquoendrdquo (data) -gt

consolelog rdquoEnd Request pathrdquo

reson rdquoerrorrdquo (data) -gt

consolelog rdquoError rdquo + data

requestend()

處理資料

parseResponse = (data callback) -gt

if datalength gt 0

用 TryCatch 避免處理 JSON 出錯導致整個中斷

try

callback null JSONparse(data)

catch err

74 8 製作一個 Hubot的 Plurk Adapter

Nodejs中文電子書

consolelog(rdquoError Parse JSONrdquo + data err)

繼續執行

callback null data ||

大致上就是這樣上面程式的架構已經將整個 Hubot Plurk Adapter完成因為在測試時竟然因為噗浪 Lag而沒讀到完整的 Comet資料然後造成程式異常為了避免這個問題發生因此需要加上為了完美呈現需要再

加上 Comet的處理所以要使用到 EventEmitter的功能

comet (server callback) -gt

在 Callback 裡面會找不到自身所以設定區域變數

self =

記錄一下這次的 Request

consolelog(rdquo[Comet] serverrdquo)

Callback 這邊先不丟進去要用另一種方式處理

request = consumerget(rdquohttpdomainpathrdquo token token secret null)

requeston rdquoresponserdquo (res) -gt

reson rdquodatardquo (chunk) -gt

parseResponse(chunk+rsquorsquo callback)

reson rdquoendrdquo (data) -gt

consolelog rdquoEnd Request pathrdquo

請求結束發出事件通知可以進行下一次請求

selfemit rdquonextPlurkrdquo

reson rdquoerrorrdquo (data) -gt

consolelog rdquoError rdquo + data

requestend()

處理資料

parseResponse = (data callback) -gt

if datalength gt 0

用 trycatch 避免失敗中斷

try

去掉 JavaScript 的 Callback

data = datamatch(CometChannelscriptCallback((+))s)

jsonData = rdquordquo

if data

83 建立 Robot跟 API 75

Nodejs中文電子書

jsonData = JSONparse(data[1])

else

如果沒有任何 Match 嘗試直接 parse

jsonData = JSONparse(data)

catch err

consolelog(rdquo[Comet] Errorrdquo data err)

用 TryCatch 避免處理 JSON 出錯導致整個中斷

try

只傳入 json 的 data 部分

callback null jsonDatadata

catch err

consolelog(rdquo[Comet]Error Parse JSONrdquo + data err)

繼續執行

callback null data ||

後面的 get跟 post就簡單多了

get (path callback) -gt

request(rdquoGETrdquo path null callback)

post (path body callback) -gt

request(rdquoPOSTrdquo path body callback)

接著處理取的 Comet網址的 getChannel

getChannel -gt

self =

get rdquoAPPRealtimegetUserChannelrdquo (error data) -gt

if error

檢查是否有 comet server

if datacomet server

selfchannel = datacomet server

如果沒有 Channel Ready 就嘗試連接會失敗

selfemit(rsquochannel readyrsquo)

那麼先來處理 Plurk Adaper好處理的部份

send (plruk id strings⋯)-gt

跟 Reply 一樣直接交給 reply 做

reply plurk id strings⋯

76 8 製作一個 Hubot的 Plurk Adapter

Nodejs中文電子書

reply (plurk id strings⋯) -gt

stringsforEach (message) =gt

botreply(plruk id message)

接著把 run處理好就可以上線運作摟

run -gt

self =

options =

key processenvHUBOT PLURK KEY

secret processenvHUBOT PLURK SECRET

token processenvHUBOT PLURK TOKEN

token secret processenvHUBOT PLURK TOKEN SECRET

創建剛剛的 API

bot = new PlurkStreaming(options)

依照 Twitter 的 new RobotTextMessage 會沒有反應所以參考 hubot-minecraft 的方式

r = robotconstructor

處理噗浪河道訊息

doPlurk = (data)-gt

檢查是否為回噗

if dataresponse

datacontent raw = dataresponsecontent raw

datauser id = dataresponseuser id

確定有噗浪 ID 跟訊息

if dataplurk id and datacontent raw

selfreceive new rTextMessage(dataplurk id datacontent raw)

取得 Comet Server 完成開始第一次 Comet 連接

boton rdquochannel readyrdquo () -gt

botplurk selfdoPlurk

上一次 Comet 完成繼續 Polling

boton rdquonextPlurkrdquo ()-gt

botplurk selfdoPlurk

定時接受好友邀請

do botacceptFriends

bot = bot

83 建立 Robot跟 API 77

Nodejs中文電子書

終於完成 AdapterHubot專案裡面的 scripts資料夾內是互動部分不需要像 Adapter如此大費周章處理只新增檔案並且設計好對白之後就會回噗了我開發用的機器人在此大家可以去跟他玩玩[httpplurkcomelct9620 bot](httpplurkcomelct9620 bot)

84 原始資料提供

bull [製 作 一 個 Hubot 的 噗 浪 Adapter](http revo-skillfrosttw blog20120318create-a-hubot-plurk-adapter)

78 8 製作一個 Hubot的 Plurk Adapter

9

Nodejs好用工具介紹

91 logio

Real-time log monitoring in your browser

httplogioorg

nodejs (gt=0412 lt=0611)

npm config set unsafe-perm true

npm install -g --prefix=usrlocal logio

logio server start

httplocalhost8998

79

Nodejs中文電子書

80 9 Nodejs好用工具介紹

10

精選文章

101 Express

bull 使用 NodeJS + Express從 GETPOST Request取值 [邀稿中]

bull link2 [邀稿中]

bull link3 [邀稿中]

81

Nodejs中文電子書

82 10 精選文章

11

參考資源

111 Nodejs書籍

bull Programming Nodejs April 2012 (est) Travis Swicegood Pragmatic Bookshelf

bull e Little Book on CoffeeScript January 2012 Alex MacCaw OrsquoReilly Media

bull Node for Front-End Developers January 2012 Garann Means OrsquoReilly Media

bull Building Hypermedia APIs with HTML5 and Node November 2011 MikeAmundsen OrsquoReilly Media

bull Node Web Development August 2011 David Herron PacktPub

bull CoffeeScript Accelerated JavaScript Development July 2011 Trevor BurnhamPragmatic Bookshelf

bull Getting Started with GEO CouchDB and Nodejs July 2011 Mick omp-son OrsquoReilly Media

bull What Is Node July 2011 Brett McLaughlin OrsquoReilly Media

bull Node Up and Running May 2011 Tom Hughes-Croucher Mike WilsonOrsquoReilly Media

83

Nodejs中文電子書

bull Hands-on Nodejs e Nodejs introduction and API reference May 2011 Pe-dro Teixeira LeanPub

bull e Node Beginner Book Manuel Kiessling

bull Mastering Nodejs visionmedia

112 Nodejs影音教學

bull e Node Sessions e Best of OSCON 2011 August 2011 OrsquoReilly Media(Video)

bull Tom Hughes-Croucher on Node March 2011 OrsquoReilly Media (Video)

113 Nodejs教學網站

bull Node Tuts

114 Nodejs課程

bull Introduction To Nodejs Van Nguyen CodeLesson

84 11 參考資源

Page 70: Nodejs Wiki

Nodejs中文電子書

epreventDefault()

)

var greet = function(msg)

var resultNode = $(rsquoresultrsquo)greeting = $(rsquolth2gtHirsquo+msgusername+rsquo 你的會員 id 是rsquo+ msgid +rsquo 我們會將會員啟動信寄至rsquo+msgemail+rsquolth2gtrsquo)

$(rsquosignuprsquo)hide()resultNodehtml(rdquordquo)

resultNodeappend(greeting)

)

ltscriptgt

ltbodygt

lthtmlgt

HTML頁面上準備了一個表單用來傳送註冊資料接著直接引用了Google CDN來載入 jQuery用來幫我們處理 Ajax的工作這次要傳送和接收的工作很大的變動都在 HTML頁面上的 JavaScript當中我們要做的事有 (相關 jQuery處理這邊不多做贅述指提起主要功能解說)

bull 用 jQUery取得 submit按鈕綁定它的 click動作

bull 取得表單 username和 email的值存放在 user這個物件中

bull 用 jQuery的 $post方法將 user的資料傳到 Server

bull 一旦成功取得資料後透過 greet這個 function組成回報給 user的訊息

bull 清空原本給使用者填資料的表單

bull 將 Server回傳的 usernameemail和 id這 3個資料組成回應的訊息

bull 將訊息放到原本表單的位置

經過以上的處理後一個 Ajax的表單的基本功能已經完成

接著進行 nodejs主要程式的編輯部分程式碼如下

var fs = require(rdquofsrdquo)

64 6 Express介紹

Nodejs中文電子書

qs = require(rsquoquerystringrsquo)

if (action === rdquoSignuprdquo)

formData = rsquorsquo

reqon(rdquodatardquo function (data)

formData += data

)

reqon(rdquoendrdquo function ()

var msg

user = qsparse(formData)

userid = rdquo123456rdquo

msg = JSONstringify(user)

reswriteHead(200 rdquoContent-Typerdquordquoapplicationjson charset=utf-8rdquordquoContent-Lengthrdquomsglength)

resend(msg)

)

這裡的程式和前面 POST範例基本上大同小異差別在

bull 幫 user的資料加上 id隨意存放一些文字進去讓 Server回傳的資料多於 Client端傳上來的不然會覺得 Server都沒做事

bull 增加了 msg 這個變數存放將 user 物件 JSON 文字化的結果JSONstringify這個轉換函式是 V8引擎所提供的如果你好奇的話

bull 大重點來了我們要告訴 Client端這次回傳的資料格式是 JSON所在 Content-type和 Content-Length要提供給 Client

Server很輕鬆就完成任務了最後進行程式測試啟動 nodejs主程式之後開啟瀏覽器就會看到表單填寫完畢按下送出就可以看到結果了

最後 nodejs本篇範例程式碼如下

var http = require(rsquohttprsquo)

url = require(rsquourlrsquo)

fs = require(rdquofsrdquo)

qs = require(rsquoquerystringrsquo)

server

68 Express AJAX應用範例 65

Nodejs中文電子書

server = httpcreateServer(function (reqres)

var urlData

encode = rdquoutf8rdquo

filePath = rdquoviewexpress_ajax_example_formhtmlrdquo

formData

action

urlData = urlparse(requrltrue)

action = urlDatapathname

if (action === rdquoSignuprdquo)

formData = rsquorsquo

reqon(rdquodatardquo function (data)

formData += data

)

reqon(rdquoendrdquo function ()

var msg

user = qsparse(formData)

userid = rdquo123456rdquo

msg = JSONstringify(user)

reswriteHead(200 rdquoContent-TyperdquordquoapplicationjsonrdquordquoContent-Lengthrdquomsglength)

resend(msg)

)

else

fsreadFile(filePath encode function(err file)

reswriteHead(200 rdquoContent-Typerdquordquotexthtml charset=utf-8rdquo)

reswrite(file)

resend()

)

)

serverlisten(3000)

66 6 Express介紹

Nodejs中文電子書

consolelog(rsquoServer 跑起來了現在時間是rsquo + new Date())

69 原始資料提供

bull [NodeJS初學者筆記 (1)-用 GET傳送資料] (httpithelpithomecomtwquestion10087402)

bull [NodeJS初學者筆記 (2)-用 POST傳送資料] (httpithelpithomecomtwquestion10087489)

bull [NodeJS 初學者筆記 (3)-用 Ajax 傳送資料] (httpithelpithomecomtwquestion10087627)

69 原始資料提供 67

Nodejs中文電子書

68 6 Express介紹

7

CoffeeScript

bull Spine

bull Hem

bull Spineapp

Letrsquos Have a Cup of CoffeeScript

bull es5-shim ECMAScript 5 compatibility shims for legacy JavaScript engines

ESnext

node-iform - A middleware for node-validator httpsgithubcomguileennode-iform

69

Nodejs中文電子書

70 7 CoffeeScript

8

製作一個 Hubot的 Plurk Adapter

81 應用事項提醒

此應用範例以CoffeeScript編寫主要程式架構採用Github釋放的Hubot專案以下有幾個主要注意事項請讀者注意

bull Hubot 一開始的架構除了內建的兩個 Adapter 之外其餘都要以Nodejs的Module方式才能運作

bull Hubot的 binhubot裡面寫著 npm install所以不管你怎麼改原始碼也不能改變第一點的狀況

其實上述都在討論同一件事情NodeJS的模組而且模組不能設定相對路徑之類的來安裝一定要包裝成 npm相符和格式才有辦法正確執行

82 建立 Adapter

首先需要準備兩個檔案

bull packagejson

bull plurkcoffee

71

Nodejs中文電子書

有這兩個就足以變成 NodeJS的模組了在開始 Coding之前先來設定一下 packagejson相依性

rdquonamerdquo rdquohubot-plurkrdquo

rdquoversionrdquo rdquo011rdquo

rdquomainrdquo rdquoplurkrdquo

rdquodependenciesrdquo

rdquohubotrdquo rdquogt=205rdquo

rdquooauthrdquo rdquordquo

rdquocronrdquordquordquo

這邊會使用到NodeJS的 cron模組(Module)而登入 Plurk需要OAuth標準授權才行所以也需要加載 OAuth模組

注意Hubot在讀取非內建模組時會自動在前面加上 hubot-的前置

83 建立 Robot跟 API

本篇應用基本上程式碼大部分參考 Hubot - Twitter Adapter來製作主要差異只有在使用 OAuth這個模組Twitter有 Streaming可用而 Plurk則得用 Comet方式來達到即時讀取

plurkcoffee程式碼

Robot = require(rdquohubotrdquo)robot()

Adapter = require(rdquohubtordquo)adapter()

EventEmitter = require(rdquoeventsrdquo)EventEmitter

oauth = require(rdquooauthrdquo)

cronJob = require(rdquocronrdquo)CronJob

class Plurk exntends Adapter

class PlurkStreaming exnteds EventEmitter

72 8 製作一個 Hubot的 Plurk Adapter

Nodejs中文電子書

先弄個基本架構主要 Class皆參考 Twitter Adapter命名方式接著再將 Plurk這個 Class增加幾個Method基本上只要有 run send reply就夠了而 run用來做初始化的部分

class Plurk entends Adapter

send (plurk id strings⋯) -gt

reply (plurk id strings⋯) -gt

run -gt

看起來有點東西接著來處理主要的 Plurk API結合部分

class PlurkStreaming extends EventEmitter

consuctor (options) -gt

plurk (callback) -gt

觀察河道

getChannel -gt

取得 Comet 網址

reply (plurk id message) -gt

回噗

acceptFriends -gt

接受好友

get (path callback) -gt

GET 請求

post (path body callback)-gt

POST 請求(其實是裝飾)

request (method path body callback)-gt

主要的 OAuth 請求

comet (server callback)-gt

噗浪的 Comet 傳回是 JavaScript Callback 要另外處理後才會變成 JSON

然後把注意力集中到 constructor上先把建構子弄好

constructor (options) -gt

super()

if optionskey and optionssecret and optionstoken and optionstoken secret

key = optionskey

secret = optionssecret

token = optionstoken

token secret = optionstoken secret

83 建立 Robot跟 API 73

Nodejs中文電子書

建立 OAuth 連接

consumer = new oauthOAuth(

rdquohttpwwwplurkcomOAuthrequest tokenrdquo

rdquohttpwwwplurkcomOAuthaccess tokenrdquo

key

secret

rdquo10rdquo

rdquohttpwwwplurkcomOAuthauthorizerdquo

rdquoHMAC-SHA1rdquo

)

domain = rdquowwwplurkcomrdquo

初始化取得 Comet 網址

do getChannel

else

throw new Error(rdquo 參數不足需要 Key Secret Token Token Secretrdquo)

接著來處理 request這個 method

request (method path body callback) -gt

記錄一下這次的 Request

consolelog(rdquohttpdomainpathrdquo)

Callback 這邊先不丟進去要用另一種方式處理

request = consumerget(rdquohttpdomainpathrdquo token token secret null)

requeston rdquoresponserdquo (res) -gt

reson rdquodatardquo (chunk) -gt

parseResponse(chunk+rsquorsquo callback)

reson rdquoendrdquo (data) -gt

consolelog rdquoEnd Request pathrdquo

reson rdquoerrorrdquo (data) -gt

consolelog rdquoError rdquo + data

requestend()

處理資料

parseResponse = (data callback) -gt

if datalength gt 0

用 TryCatch 避免處理 JSON 出錯導致整個中斷

try

callback null JSONparse(data)

catch err

74 8 製作一個 Hubot的 Plurk Adapter

Nodejs中文電子書

consolelog(rdquoError Parse JSONrdquo + data err)

繼續執行

callback null data ||

大致上就是這樣上面程式的架構已經將整個 Hubot Plurk Adapter完成因為在測試時竟然因為噗浪 Lag而沒讀到完整的 Comet資料然後造成程式異常為了避免這個問題發生因此需要加上為了完美呈現需要再

加上 Comet的處理所以要使用到 EventEmitter的功能

comet (server callback) -gt

在 Callback 裡面會找不到自身所以設定區域變數

self =

記錄一下這次的 Request

consolelog(rdquo[Comet] serverrdquo)

Callback 這邊先不丟進去要用另一種方式處理

request = consumerget(rdquohttpdomainpathrdquo token token secret null)

requeston rdquoresponserdquo (res) -gt

reson rdquodatardquo (chunk) -gt

parseResponse(chunk+rsquorsquo callback)

reson rdquoendrdquo (data) -gt

consolelog rdquoEnd Request pathrdquo

請求結束發出事件通知可以進行下一次請求

selfemit rdquonextPlurkrdquo

reson rdquoerrorrdquo (data) -gt

consolelog rdquoError rdquo + data

requestend()

處理資料

parseResponse = (data callback) -gt

if datalength gt 0

用 trycatch 避免失敗中斷

try

去掉 JavaScript 的 Callback

data = datamatch(CometChannelscriptCallback((+))s)

jsonData = rdquordquo

if data

83 建立 Robot跟 API 75

Nodejs中文電子書

jsonData = JSONparse(data[1])

else

如果沒有任何 Match 嘗試直接 parse

jsonData = JSONparse(data)

catch err

consolelog(rdquo[Comet] Errorrdquo data err)

用 TryCatch 避免處理 JSON 出錯導致整個中斷

try

只傳入 json 的 data 部分

callback null jsonDatadata

catch err

consolelog(rdquo[Comet]Error Parse JSONrdquo + data err)

繼續執行

callback null data ||

後面的 get跟 post就簡單多了

get (path callback) -gt

request(rdquoGETrdquo path null callback)

post (path body callback) -gt

request(rdquoPOSTrdquo path body callback)

接著處理取的 Comet網址的 getChannel

getChannel -gt

self =

get rdquoAPPRealtimegetUserChannelrdquo (error data) -gt

if error

檢查是否有 comet server

if datacomet server

selfchannel = datacomet server

如果沒有 Channel Ready 就嘗試連接會失敗

selfemit(rsquochannel readyrsquo)

那麼先來處理 Plurk Adaper好處理的部份

send (plruk id strings⋯)-gt

跟 Reply 一樣直接交給 reply 做

reply plurk id strings⋯

76 8 製作一個 Hubot的 Plurk Adapter

Nodejs中文電子書

reply (plurk id strings⋯) -gt

stringsforEach (message) =gt

botreply(plruk id message)

接著把 run處理好就可以上線運作摟

run -gt

self =

options =

key processenvHUBOT PLURK KEY

secret processenvHUBOT PLURK SECRET

token processenvHUBOT PLURK TOKEN

token secret processenvHUBOT PLURK TOKEN SECRET

創建剛剛的 API

bot = new PlurkStreaming(options)

依照 Twitter 的 new RobotTextMessage 會沒有反應所以參考 hubot-minecraft 的方式

r = robotconstructor

處理噗浪河道訊息

doPlurk = (data)-gt

檢查是否為回噗

if dataresponse

datacontent raw = dataresponsecontent raw

datauser id = dataresponseuser id

確定有噗浪 ID 跟訊息

if dataplurk id and datacontent raw

selfreceive new rTextMessage(dataplurk id datacontent raw)

取得 Comet Server 完成開始第一次 Comet 連接

boton rdquochannel readyrdquo () -gt

botplurk selfdoPlurk

上一次 Comet 完成繼續 Polling

boton rdquonextPlurkrdquo ()-gt

botplurk selfdoPlurk

定時接受好友邀請

do botacceptFriends

bot = bot

83 建立 Robot跟 API 77

Nodejs中文電子書

終於完成 AdapterHubot專案裡面的 scripts資料夾內是互動部分不需要像 Adapter如此大費周章處理只新增檔案並且設計好對白之後就會回噗了我開發用的機器人在此大家可以去跟他玩玩[httpplurkcomelct9620 bot](httpplurkcomelct9620 bot)

84 原始資料提供

bull [製 作 一 個 Hubot 的 噗 浪 Adapter](http revo-skillfrosttw blog20120318create-a-hubot-plurk-adapter)

78 8 製作一個 Hubot的 Plurk Adapter

9

Nodejs好用工具介紹

91 logio

Real-time log monitoring in your browser

httplogioorg

nodejs (gt=0412 lt=0611)

npm config set unsafe-perm true

npm install -g --prefix=usrlocal logio

logio server start

httplocalhost8998

79

Nodejs中文電子書

80 9 Nodejs好用工具介紹

10

精選文章

101 Express

bull 使用 NodeJS + Express從 GETPOST Request取值 [邀稿中]

bull link2 [邀稿中]

bull link3 [邀稿中]

81

Nodejs中文電子書

82 10 精選文章

11

參考資源

111 Nodejs書籍

bull Programming Nodejs April 2012 (est) Travis Swicegood Pragmatic Bookshelf

bull e Little Book on CoffeeScript January 2012 Alex MacCaw OrsquoReilly Media

bull Node for Front-End Developers January 2012 Garann Means OrsquoReilly Media

bull Building Hypermedia APIs with HTML5 and Node November 2011 MikeAmundsen OrsquoReilly Media

bull Node Web Development August 2011 David Herron PacktPub

bull CoffeeScript Accelerated JavaScript Development July 2011 Trevor BurnhamPragmatic Bookshelf

bull Getting Started with GEO CouchDB and Nodejs July 2011 Mick omp-son OrsquoReilly Media

bull What Is Node July 2011 Brett McLaughlin OrsquoReilly Media

bull Node Up and Running May 2011 Tom Hughes-Croucher Mike WilsonOrsquoReilly Media

83

Nodejs中文電子書

bull Hands-on Nodejs e Nodejs introduction and API reference May 2011 Pe-dro Teixeira LeanPub

bull e Node Beginner Book Manuel Kiessling

bull Mastering Nodejs visionmedia

112 Nodejs影音教學

bull e Node Sessions e Best of OSCON 2011 August 2011 OrsquoReilly Media(Video)

bull Tom Hughes-Croucher on Node March 2011 OrsquoReilly Media (Video)

113 Nodejs教學網站

bull Node Tuts

114 Nodejs課程

bull Introduction To Nodejs Van Nguyen CodeLesson

84 11 參考資源

Page 71: Nodejs Wiki

Nodejs中文電子書

qs = require(rsquoquerystringrsquo)

if (action === rdquoSignuprdquo)

formData = rsquorsquo

reqon(rdquodatardquo function (data)

formData += data

)

reqon(rdquoendrdquo function ()

var msg

user = qsparse(formData)

userid = rdquo123456rdquo

msg = JSONstringify(user)

reswriteHead(200 rdquoContent-Typerdquordquoapplicationjson charset=utf-8rdquordquoContent-Lengthrdquomsglength)

resend(msg)

)

這裡的程式和前面 POST範例基本上大同小異差別在

bull 幫 user的資料加上 id隨意存放一些文字進去讓 Server回傳的資料多於 Client端傳上來的不然會覺得 Server都沒做事

bull 增加了 msg 這個變數存放將 user 物件 JSON 文字化的結果JSONstringify這個轉換函式是 V8引擎所提供的如果你好奇的話

bull 大重點來了我們要告訴 Client端這次回傳的資料格式是 JSON所在 Content-type和 Content-Length要提供給 Client

Server很輕鬆就完成任務了最後進行程式測試啟動 nodejs主程式之後開啟瀏覽器就會看到表單填寫完畢按下送出就可以看到結果了

最後 nodejs本篇範例程式碼如下

var http = require(rsquohttprsquo)

url = require(rsquourlrsquo)

fs = require(rdquofsrdquo)

qs = require(rsquoquerystringrsquo)

server

68 Express AJAX應用範例 65

Nodejs中文電子書

server = httpcreateServer(function (reqres)

var urlData

encode = rdquoutf8rdquo

filePath = rdquoviewexpress_ajax_example_formhtmlrdquo

formData

action

urlData = urlparse(requrltrue)

action = urlDatapathname

if (action === rdquoSignuprdquo)

formData = rsquorsquo

reqon(rdquodatardquo function (data)

formData += data

)

reqon(rdquoendrdquo function ()

var msg

user = qsparse(formData)

userid = rdquo123456rdquo

msg = JSONstringify(user)

reswriteHead(200 rdquoContent-TyperdquordquoapplicationjsonrdquordquoContent-Lengthrdquomsglength)

resend(msg)

)

else

fsreadFile(filePath encode function(err file)

reswriteHead(200 rdquoContent-Typerdquordquotexthtml charset=utf-8rdquo)

reswrite(file)

resend()

)

)

serverlisten(3000)

66 6 Express介紹

Nodejs中文電子書

consolelog(rsquoServer 跑起來了現在時間是rsquo + new Date())

69 原始資料提供

bull [NodeJS初學者筆記 (1)-用 GET傳送資料] (httpithelpithomecomtwquestion10087402)

bull [NodeJS初學者筆記 (2)-用 POST傳送資料] (httpithelpithomecomtwquestion10087489)

bull [NodeJS 初學者筆記 (3)-用 Ajax 傳送資料] (httpithelpithomecomtwquestion10087627)

69 原始資料提供 67

Nodejs中文電子書

68 6 Express介紹

7

CoffeeScript

bull Spine

bull Hem

bull Spineapp

Letrsquos Have a Cup of CoffeeScript

bull es5-shim ECMAScript 5 compatibility shims for legacy JavaScript engines

ESnext

node-iform - A middleware for node-validator httpsgithubcomguileennode-iform

69

Nodejs中文電子書

70 7 CoffeeScript

8

製作一個 Hubot的 Plurk Adapter

81 應用事項提醒

此應用範例以CoffeeScript編寫主要程式架構採用Github釋放的Hubot專案以下有幾個主要注意事項請讀者注意

bull Hubot 一開始的架構除了內建的兩個 Adapter 之外其餘都要以Nodejs的Module方式才能運作

bull Hubot的 binhubot裡面寫著 npm install所以不管你怎麼改原始碼也不能改變第一點的狀況

其實上述都在討論同一件事情NodeJS的模組而且模組不能設定相對路徑之類的來安裝一定要包裝成 npm相符和格式才有辦法正確執行

82 建立 Adapter

首先需要準備兩個檔案

bull packagejson

bull plurkcoffee

71

Nodejs中文電子書

有這兩個就足以變成 NodeJS的模組了在開始 Coding之前先來設定一下 packagejson相依性

rdquonamerdquo rdquohubot-plurkrdquo

rdquoversionrdquo rdquo011rdquo

rdquomainrdquo rdquoplurkrdquo

rdquodependenciesrdquo

rdquohubotrdquo rdquogt=205rdquo

rdquooauthrdquo rdquordquo

rdquocronrdquordquordquo

這邊會使用到NodeJS的 cron模組(Module)而登入 Plurk需要OAuth標準授權才行所以也需要加載 OAuth模組

注意Hubot在讀取非內建模組時會自動在前面加上 hubot-的前置

83 建立 Robot跟 API

本篇應用基本上程式碼大部分參考 Hubot - Twitter Adapter來製作主要差異只有在使用 OAuth這個模組Twitter有 Streaming可用而 Plurk則得用 Comet方式來達到即時讀取

plurkcoffee程式碼

Robot = require(rdquohubotrdquo)robot()

Adapter = require(rdquohubtordquo)adapter()

EventEmitter = require(rdquoeventsrdquo)EventEmitter

oauth = require(rdquooauthrdquo)

cronJob = require(rdquocronrdquo)CronJob

class Plurk exntends Adapter

class PlurkStreaming exnteds EventEmitter

72 8 製作一個 Hubot的 Plurk Adapter

Nodejs中文電子書

先弄個基本架構主要 Class皆參考 Twitter Adapter命名方式接著再將 Plurk這個 Class增加幾個Method基本上只要有 run send reply就夠了而 run用來做初始化的部分

class Plurk entends Adapter

send (plurk id strings⋯) -gt

reply (plurk id strings⋯) -gt

run -gt

看起來有點東西接著來處理主要的 Plurk API結合部分

class PlurkStreaming extends EventEmitter

consuctor (options) -gt

plurk (callback) -gt

觀察河道

getChannel -gt

取得 Comet 網址

reply (plurk id message) -gt

回噗

acceptFriends -gt

接受好友

get (path callback) -gt

GET 請求

post (path body callback)-gt

POST 請求(其實是裝飾)

request (method path body callback)-gt

主要的 OAuth 請求

comet (server callback)-gt

噗浪的 Comet 傳回是 JavaScript Callback 要另外處理後才會變成 JSON

然後把注意力集中到 constructor上先把建構子弄好

constructor (options) -gt

super()

if optionskey and optionssecret and optionstoken and optionstoken secret

key = optionskey

secret = optionssecret

token = optionstoken

token secret = optionstoken secret

83 建立 Robot跟 API 73

Nodejs中文電子書

建立 OAuth 連接

consumer = new oauthOAuth(

rdquohttpwwwplurkcomOAuthrequest tokenrdquo

rdquohttpwwwplurkcomOAuthaccess tokenrdquo

key

secret

rdquo10rdquo

rdquohttpwwwplurkcomOAuthauthorizerdquo

rdquoHMAC-SHA1rdquo

)

domain = rdquowwwplurkcomrdquo

初始化取得 Comet 網址

do getChannel

else

throw new Error(rdquo 參數不足需要 Key Secret Token Token Secretrdquo)

接著來處理 request這個 method

request (method path body callback) -gt

記錄一下這次的 Request

consolelog(rdquohttpdomainpathrdquo)

Callback 這邊先不丟進去要用另一種方式處理

request = consumerget(rdquohttpdomainpathrdquo token token secret null)

requeston rdquoresponserdquo (res) -gt

reson rdquodatardquo (chunk) -gt

parseResponse(chunk+rsquorsquo callback)

reson rdquoendrdquo (data) -gt

consolelog rdquoEnd Request pathrdquo

reson rdquoerrorrdquo (data) -gt

consolelog rdquoError rdquo + data

requestend()

處理資料

parseResponse = (data callback) -gt

if datalength gt 0

用 TryCatch 避免處理 JSON 出錯導致整個中斷

try

callback null JSONparse(data)

catch err

74 8 製作一個 Hubot的 Plurk Adapter

Nodejs中文電子書

consolelog(rdquoError Parse JSONrdquo + data err)

繼續執行

callback null data ||

大致上就是這樣上面程式的架構已經將整個 Hubot Plurk Adapter完成因為在測試時竟然因為噗浪 Lag而沒讀到完整的 Comet資料然後造成程式異常為了避免這個問題發生因此需要加上為了完美呈現需要再

加上 Comet的處理所以要使用到 EventEmitter的功能

comet (server callback) -gt

在 Callback 裡面會找不到自身所以設定區域變數

self =

記錄一下這次的 Request

consolelog(rdquo[Comet] serverrdquo)

Callback 這邊先不丟進去要用另一種方式處理

request = consumerget(rdquohttpdomainpathrdquo token token secret null)

requeston rdquoresponserdquo (res) -gt

reson rdquodatardquo (chunk) -gt

parseResponse(chunk+rsquorsquo callback)

reson rdquoendrdquo (data) -gt

consolelog rdquoEnd Request pathrdquo

請求結束發出事件通知可以進行下一次請求

selfemit rdquonextPlurkrdquo

reson rdquoerrorrdquo (data) -gt

consolelog rdquoError rdquo + data

requestend()

處理資料

parseResponse = (data callback) -gt

if datalength gt 0

用 trycatch 避免失敗中斷

try

去掉 JavaScript 的 Callback

data = datamatch(CometChannelscriptCallback((+))s)

jsonData = rdquordquo

if data

83 建立 Robot跟 API 75

Nodejs中文電子書

jsonData = JSONparse(data[1])

else

如果沒有任何 Match 嘗試直接 parse

jsonData = JSONparse(data)

catch err

consolelog(rdquo[Comet] Errorrdquo data err)

用 TryCatch 避免處理 JSON 出錯導致整個中斷

try

只傳入 json 的 data 部分

callback null jsonDatadata

catch err

consolelog(rdquo[Comet]Error Parse JSONrdquo + data err)

繼續執行

callback null data ||

後面的 get跟 post就簡單多了

get (path callback) -gt

request(rdquoGETrdquo path null callback)

post (path body callback) -gt

request(rdquoPOSTrdquo path body callback)

接著處理取的 Comet網址的 getChannel

getChannel -gt

self =

get rdquoAPPRealtimegetUserChannelrdquo (error data) -gt

if error

檢查是否有 comet server

if datacomet server

selfchannel = datacomet server

如果沒有 Channel Ready 就嘗試連接會失敗

selfemit(rsquochannel readyrsquo)

那麼先來處理 Plurk Adaper好處理的部份

send (plruk id strings⋯)-gt

跟 Reply 一樣直接交給 reply 做

reply plurk id strings⋯

76 8 製作一個 Hubot的 Plurk Adapter

Nodejs中文電子書

reply (plurk id strings⋯) -gt

stringsforEach (message) =gt

botreply(plruk id message)

接著把 run處理好就可以上線運作摟

run -gt

self =

options =

key processenvHUBOT PLURK KEY

secret processenvHUBOT PLURK SECRET

token processenvHUBOT PLURK TOKEN

token secret processenvHUBOT PLURK TOKEN SECRET

創建剛剛的 API

bot = new PlurkStreaming(options)

依照 Twitter 的 new RobotTextMessage 會沒有反應所以參考 hubot-minecraft 的方式

r = robotconstructor

處理噗浪河道訊息

doPlurk = (data)-gt

檢查是否為回噗

if dataresponse

datacontent raw = dataresponsecontent raw

datauser id = dataresponseuser id

確定有噗浪 ID 跟訊息

if dataplurk id and datacontent raw

selfreceive new rTextMessage(dataplurk id datacontent raw)

取得 Comet Server 完成開始第一次 Comet 連接

boton rdquochannel readyrdquo () -gt

botplurk selfdoPlurk

上一次 Comet 完成繼續 Polling

boton rdquonextPlurkrdquo ()-gt

botplurk selfdoPlurk

定時接受好友邀請

do botacceptFriends

bot = bot

83 建立 Robot跟 API 77

Nodejs中文電子書

終於完成 AdapterHubot專案裡面的 scripts資料夾內是互動部分不需要像 Adapter如此大費周章處理只新增檔案並且設計好對白之後就會回噗了我開發用的機器人在此大家可以去跟他玩玩[httpplurkcomelct9620 bot](httpplurkcomelct9620 bot)

84 原始資料提供

bull [製 作 一 個 Hubot 的 噗 浪 Adapter](http revo-skillfrosttw blog20120318create-a-hubot-plurk-adapter)

78 8 製作一個 Hubot的 Plurk Adapter

9

Nodejs好用工具介紹

91 logio

Real-time log monitoring in your browser

httplogioorg

nodejs (gt=0412 lt=0611)

npm config set unsafe-perm true

npm install -g --prefix=usrlocal logio

logio server start

httplocalhost8998

79

Nodejs中文電子書

80 9 Nodejs好用工具介紹

10

精選文章

101 Express

bull 使用 NodeJS + Express從 GETPOST Request取值 [邀稿中]

bull link2 [邀稿中]

bull link3 [邀稿中]

81

Nodejs中文電子書

82 10 精選文章

11

參考資源

111 Nodejs書籍

bull Programming Nodejs April 2012 (est) Travis Swicegood Pragmatic Bookshelf

bull e Little Book on CoffeeScript January 2012 Alex MacCaw OrsquoReilly Media

bull Node for Front-End Developers January 2012 Garann Means OrsquoReilly Media

bull Building Hypermedia APIs with HTML5 and Node November 2011 MikeAmundsen OrsquoReilly Media

bull Node Web Development August 2011 David Herron PacktPub

bull CoffeeScript Accelerated JavaScript Development July 2011 Trevor BurnhamPragmatic Bookshelf

bull Getting Started with GEO CouchDB and Nodejs July 2011 Mick omp-son OrsquoReilly Media

bull What Is Node July 2011 Brett McLaughlin OrsquoReilly Media

bull Node Up and Running May 2011 Tom Hughes-Croucher Mike WilsonOrsquoReilly Media

83

Nodejs中文電子書

bull Hands-on Nodejs e Nodejs introduction and API reference May 2011 Pe-dro Teixeira LeanPub

bull e Node Beginner Book Manuel Kiessling

bull Mastering Nodejs visionmedia

112 Nodejs影音教學

bull e Node Sessions e Best of OSCON 2011 August 2011 OrsquoReilly Media(Video)

bull Tom Hughes-Croucher on Node March 2011 OrsquoReilly Media (Video)

113 Nodejs教學網站

bull Node Tuts

114 Nodejs課程

bull Introduction To Nodejs Van Nguyen CodeLesson

84 11 參考資源

Page 72: Nodejs Wiki

Nodejs中文電子書

server = httpcreateServer(function (reqres)

var urlData

encode = rdquoutf8rdquo

filePath = rdquoviewexpress_ajax_example_formhtmlrdquo

formData

action

urlData = urlparse(requrltrue)

action = urlDatapathname

if (action === rdquoSignuprdquo)

formData = rsquorsquo

reqon(rdquodatardquo function (data)

formData += data

)

reqon(rdquoendrdquo function ()

var msg

user = qsparse(formData)

userid = rdquo123456rdquo

msg = JSONstringify(user)

reswriteHead(200 rdquoContent-TyperdquordquoapplicationjsonrdquordquoContent-Lengthrdquomsglength)

resend(msg)

)

else

fsreadFile(filePath encode function(err file)

reswriteHead(200 rdquoContent-Typerdquordquotexthtml charset=utf-8rdquo)

reswrite(file)

resend()

)

)

serverlisten(3000)

66 6 Express介紹

Nodejs中文電子書

consolelog(rsquoServer 跑起來了現在時間是rsquo + new Date())

69 原始資料提供

bull [NodeJS初學者筆記 (1)-用 GET傳送資料] (httpithelpithomecomtwquestion10087402)

bull [NodeJS初學者筆記 (2)-用 POST傳送資料] (httpithelpithomecomtwquestion10087489)

bull [NodeJS 初學者筆記 (3)-用 Ajax 傳送資料] (httpithelpithomecomtwquestion10087627)

69 原始資料提供 67

Nodejs中文電子書

68 6 Express介紹

7

CoffeeScript

bull Spine

bull Hem

bull Spineapp

Letrsquos Have a Cup of CoffeeScript

bull es5-shim ECMAScript 5 compatibility shims for legacy JavaScript engines

ESnext

node-iform - A middleware for node-validator httpsgithubcomguileennode-iform

69

Nodejs中文電子書

70 7 CoffeeScript

8

製作一個 Hubot的 Plurk Adapter

81 應用事項提醒

此應用範例以CoffeeScript編寫主要程式架構採用Github釋放的Hubot專案以下有幾個主要注意事項請讀者注意

bull Hubot 一開始的架構除了內建的兩個 Adapter 之外其餘都要以Nodejs的Module方式才能運作

bull Hubot的 binhubot裡面寫著 npm install所以不管你怎麼改原始碼也不能改變第一點的狀況

其實上述都在討論同一件事情NodeJS的模組而且模組不能設定相對路徑之類的來安裝一定要包裝成 npm相符和格式才有辦法正確執行

82 建立 Adapter

首先需要準備兩個檔案

bull packagejson

bull plurkcoffee

71

Nodejs中文電子書

有這兩個就足以變成 NodeJS的模組了在開始 Coding之前先來設定一下 packagejson相依性

rdquonamerdquo rdquohubot-plurkrdquo

rdquoversionrdquo rdquo011rdquo

rdquomainrdquo rdquoplurkrdquo

rdquodependenciesrdquo

rdquohubotrdquo rdquogt=205rdquo

rdquooauthrdquo rdquordquo

rdquocronrdquordquordquo

這邊會使用到NodeJS的 cron模組(Module)而登入 Plurk需要OAuth標準授權才行所以也需要加載 OAuth模組

注意Hubot在讀取非內建模組時會自動在前面加上 hubot-的前置

83 建立 Robot跟 API

本篇應用基本上程式碼大部分參考 Hubot - Twitter Adapter來製作主要差異只有在使用 OAuth這個模組Twitter有 Streaming可用而 Plurk則得用 Comet方式來達到即時讀取

plurkcoffee程式碼

Robot = require(rdquohubotrdquo)robot()

Adapter = require(rdquohubtordquo)adapter()

EventEmitter = require(rdquoeventsrdquo)EventEmitter

oauth = require(rdquooauthrdquo)

cronJob = require(rdquocronrdquo)CronJob

class Plurk exntends Adapter

class PlurkStreaming exnteds EventEmitter

72 8 製作一個 Hubot的 Plurk Adapter

Nodejs中文電子書

先弄個基本架構主要 Class皆參考 Twitter Adapter命名方式接著再將 Plurk這個 Class增加幾個Method基本上只要有 run send reply就夠了而 run用來做初始化的部分

class Plurk entends Adapter

send (plurk id strings⋯) -gt

reply (plurk id strings⋯) -gt

run -gt

看起來有點東西接著來處理主要的 Plurk API結合部分

class PlurkStreaming extends EventEmitter

consuctor (options) -gt

plurk (callback) -gt

觀察河道

getChannel -gt

取得 Comet 網址

reply (plurk id message) -gt

回噗

acceptFriends -gt

接受好友

get (path callback) -gt

GET 請求

post (path body callback)-gt

POST 請求(其實是裝飾)

request (method path body callback)-gt

主要的 OAuth 請求

comet (server callback)-gt

噗浪的 Comet 傳回是 JavaScript Callback 要另外處理後才會變成 JSON

然後把注意力集中到 constructor上先把建構子弄好

constructor (options) -gt

super()

if optionskey and optionssecret and optionstoken and optionstoken secret

key = optionskey

secret = optionssecret

token = optionstoken

token secret = optionstoken secret

83 建立 Robot跟 API 73

Nodejs中文電子書

建立 OAuth 連接

consumer = new oauthOAuth(

rdquohttpwwwplurkcomOAuthrequest tokenrdquo

rdquohttpwwwplurkcomOAuthaccess tokenrdquo

key

secret

rdquo10rdquo

rdquohttpwwwplurkcomOAuthauthorizerdquo

rdquoHMAC-SHA1rdquo

)

domain = rdquowwwplurkcomrdquo

初始化取得 Comet 網址

do getChannel

else

throw new Error(rdquo 參數不足需要 Key Secret Token Token Secretrdquo)

接著來處理 request這個 method

request (method path body callback) -gt

記錄一下這次的 Request

consolelog(rdquohttpdomainpathrdquo)

Callback 這邊先不丟進去要用另一種方式處理

request = consumerget(rdquohttpdomainpathrdquo token token secret null)

requeston rdquoresponserdquo (res) -gt

reson rdquodatardquo (chunk) -gt

parseResponse(chunk+rsquorsquo callback)

reson rdquoendrdquo (data) -gt

consolelog rdquoEnd Request pathrdquo

reson rdquoerrorrdquo (data) -gt

consolelog rdquoError rdquo + data

requestend()

處理資料

parseResponse = (data callback) -gt

if datalength gt 0

用 TryCatch 避免處理 JSON 出錯導致整個中斷

try

callback null JSONparse(data)

catch err

74 8 製作一個 Hubot的 Plurk Adapter

Nodejs中文電子書

consolelog(rdquoError Parse JSONrdquo + data err)

繼續執行

callback null data ||

大致上就是這樣上面程式的架構已經將整個 Hubot Plurk Adapter完成因為在測試時竟然因為噗浪 Lag而沒讀到完整的 Comet資料然後造成程式異常為了避免這個問題發生因此需要加上為了完美呈現需要再

加上 Comet的處理所以要使用到 EventEmitter的功能

comet (server callback) -gt

在 Callback 裡面會找不到自身所以設定區域變數

self =

記錄一下這次的 Request

consolelog(rdquo[Comet] serverrdquo)

Callback 這邊先不丟進去要用另一種方式處理

request = consumerget(rdquohttpdomainpathrdquo token token secret null)

requeston rdquoresponserdquo (res) -gt

reson rdquodatardquo (chunk) -gt

parseResponse(chunk+rsquorsquo callback)

reson rdquoendrdquo (data) -gt

consolelog rdquoEnd Request pathrdquo

請求結束發出事件通知可以進行下一次請求

selfemit rdquonextPlurkrdquo

reson rdquoerrorrdquo (data) -gt

consolelog rdquoError rdquo + data

requestend()

處理資料

parseResponse = (data callback) -gt

if datalength gt 0

用 trycatch 避免失敗中斷

try

去掉 JavaScript 的 Callback

data = datamatch(CometChannelscriptCallback((+))s)

jsonData = rdquordquo

if data

83 建立 Robot跟 API 75

Nodejs中文電子書

jsonData = JSONparse(data[1])

else

如果沒有任何 Match 嘗試直接 parse

jsonData = JSONparse(data)

catch err

consolelog(rdquo[Comet] Errorrdquo data err)

用 TryCatch 避免處理 JSON 出錯導致整個中斷

try

只傳入 json 的 data 部分

callback null jsonDatadata

catch err

consolelog(rdquo[Comet]Error Parse JSONrdquo + data err)

繼續執行

callback null data ||

後面的 get跟 post就簡單多了

get (path callback) -gt

request(rdquoGETrdquo path null callback)

post (path body callback) -gt

request(rdquoPOSTrdquo path body callback)

接著處理取的 Comet網址的 getChannel

getChannel -gt

self =

get rdquoAPPRealtimegetUserChannelrdquo (error data) -gt

if error

檢查是否有 comet server

if datacomet server

selfchannel = datacomet server

如果沒有 Channel Ready 就嘗試連接會失敗

selfemit(rsquochannel readyrsquo)

那麼先來處理 Plurk Adaper好處理的部份

send (plruk id strings⋯)-gt

跟 Reply 一樣直接交給 reply 做

reply plurk id strings⋯

76 8 製作一個 Hubot的 Plurk Adapter

Nodejs中文電子書

reply (plurk id strings⋯) -gt

stringsforEach (message) =gt

botreply(plruk id message)

接著把 run處理好就可以上線運作摟

run -gt

self =

options =

key processenvHUBOT PLURK KEY

secret processenvHUBOT PLURK SECRET

token processenvHUBOT PLURK TOKEN

token secret processenvHUBOT PLURK TOKEN SECRET

創建剛剛的 API

bot = new PlurkStreaming(options)

依照 Twitter 的 new RobotTextMessage 會沒有反應所以參考 hubot-minecraft 的方式

r = robotconstructor

處理噗浪河道訊息

doPlurk = (data)-gt

檢查是否為回噗

if dataresponse

datacontent raw = dataresponsecontent raw

datauser id = dataresponseuser id

確定有噗浪 ID 跟訊息

if dataplurk id and datacontent raw

selfreceive new rTextMessage(dataplurk id datacontent raw)

取得 Comet Server 完成開始第一次 Comet 連接

boton rdquochannel readyrdquo () -gt

botplurk selfdoPlurk

上一次 Comet 完成繼續 Polling

boton rdquonextPlurkrdquo ()-gt

botplurk selfdoPlurk

定時接受好友邀請

do botacceptFriends

bot = bot

83 建立 Robot跟 API 77

Nodejs中文電子書

終於完成 AdapterHubot專案裡面的 scripts資料夾內是互動部分不需要像 Adapter如此大費周章處理只新增檔案並且設計好對白之後就會回噗了我開發用的機器人在此大家可以去跟他玩玩[httpplurkcomelct9620 bot](httpplurkcomelct9620 bot)

84 原始資料提供

bull [製 作 一 個 Hubot 的 噗 浪 Adapter](http revo-skillfrosttw blog20120318create-a-hubot-plurk-adapter)

78 8 製作一個 Hubot的 Plurk Adapter

9

Nodejs好用工具介紹

91 logio

Real-time log monitoring in your browser

httplogioorg

nodejs (gt=0412 lt=0611)

npm config set unsafe-perm true

npm install -g --prefix=usrlocal logio

logio server start

httplocalhost8998

79

Nodejs中文電子書

80 9 Nodejs好用工具介紹

10

精選文章

101 Express

bull 使用 NodeJS + Express從 GETPOST Request取值 [邀稿中]

bull link2 [邀稿中]

bull link3 [邀稿中]

81

Nodejs中文電子書

82 10 精選文章

11

參考資源

111 Nodejs書籍

bull Programming Nodejs April 2012 (est) Travis Swicegood Pragmatic Bookshelf

bull e Little Book on CoffeeScript January 2012 Alex MacCaw OrsquoReilly Media

bull Node for Front-End Developers January 2012 Garann Means OrsquoReilly Media

bull Building Hypermedia APIs with HTML5 and Node November 2011 MikeAmundsen OrsquoReilly Media

bull Node Web Development August 2011 David Herron PacktPub

bull CoffeeScript Accelerated JavaScript Development July 2011 Trevor BurnhamPragmatic Bookshelf

bull Getting Started with GEO CouchDB and Nodejs July 2011 Mick omp-son OrsquoReilly Media

bull What Is Node July 2011 Brett McLaughlin OrsquoReilly Media

bull Node Up and Running May 2011 Tom Hughes-Croucher Mike WilsonOrsquoReilly Media

83

Nodejs中文電子書

bull Hands-on Nodejs e Nodejs introduction and API reference May 2011 Pe-dro Teixeira LeanPub

bull e Node Beginner Book Manuel Kiessling

bull Mastering Nodejs visionmedia

112 Nodejs影音教學

bull e Node Sessions e Best of OSCON 2011 August 2011 OrsquoReilly Media(Video)

bull Tom Hughes-Croucher on Node March 2011 OrsquoReilly Media (Video)

113 Nodejs教學網站

bull Node Tuts

114 Nodejs課程

bull Introduction To Nodejs Van Nguyen CodeLesson

84 11 參考資源

Page 73: Nodejs Wiki

Nodejs中文電子書

consolelog(rsquoServer 跑起來了現在時間是rsquo + new Date())

69 原始資料提供

bull [NodeJS初學者筆記 (1)-用 GET傳送資料] (httpithelpithomecomtwquestion10087402)

bull [NodeJS初學者筆記 (2)-用 POST傳送資料] (httpithelpithomecomtwquestion10087489)

bull [NodeJS 初學者筆記 (3)-用 Ajax 傳送資料] (httpithelpithomecomtwquestion10087627)

69 原始資料提供 67

Nodejs中文電子書

68 6 Express介紹

7

CoffeeScript

bull Spine

bull Hem

bull Spineapp

Letrsquos Have a Cup of CoffeeScript

bull es5-shim ECMAScript 5 compatibility shims for legacy JavaScript engines

ESnext

node-iform - A middleware for node-validator httpsgithubcomguileennode-iform

69

Nodejs中文電子書

70 7 CoffeeScript

8

製作一個 Hubot的 Plurk Adapter

81 應用事項提醒

此應用範例以CoffeeScript編寫主要程式架構採用Github釋放的Hubot專案以下有幾個主要注意事項請讀者注意

bull Hubot 一開始的架構除了內建的兩個 Adapter 之外其餘都要以Nodejs的Module方式才能運作

bull Hubot的 binhubot裡面寫著 npm install所以不管你怎麼改原始碼也不能改變第一點的狀況

其實上述都在討論同一件事情NodeJS的模組而且模組不能設定相對路徑之類的來安裝一定要包裝成 npm相符和格式才有辦法正確執行

82 建立 Adapter

首先需要準備兩個檔案

bull packagejson

bull plurkcoffee

71

Nodejs中文電子書

有這兩個就足以變成 NodeJS的模組了在開始 Coding之前先來設定一下 packagejson相依性

rdquonamerdquo rdquohubot-plurkrdquo

rdquoversionrdquo rdquo011rdquo

rdquomainrdquo rdquoplurkrdquo

rdquodependenciesrdquo

rdquohubotrdquo rdquogt=205rdquo

rdquooauthrdquo rdquordquo

rdquocronrdquordquordquo

這邊會使用到NodeJS的 cron模組(Module)而登入 Plurk需要OAuth標準授權才行所以也需要加載 OAuth模組

注意Hubot在讀取非內建模組時會自動在前面加上 hubot-的前置

83 建立 Robot跟 API

本篇應用基本上程式碼大部分參考 Hubot - Twitter Adapter來製作主要差異只有在使用 OAuth這個模組Twitter有 Streaming可用而 Plurk則得用 Comet方式來達到即時讀取

plurkcoffee程式碼

Robot = require(rdquohubotrdquo)robot()

Adapter = require(rdquohubtordquo)adapter()

EventEmitter = require(rdquoeventsrdquo)EventEmitter

oauth = require(rdquooauthrdquo)

cronJob = require(rdquocronrdquo)CronJob

class Plurk exntends Adapter

class PlurkStreaming exnteds EventEmitter

72 8 製作一個 Hubot的 Plurk Adapter

Nodejs中文電子書

先弄個基本架構主要 Class皆參考 Twitter Adapter命名方式接著再將 Plurk這個 Class增加幾個Method基本上只要有 run send reply就夠了而 run用來做初始化的部分

class Plurk entends Adapter

send (plurk id strings⋯) -gt

reply (plurk id strings⋯) -gt

run -gt

看起來有點東西接著來處理主要的 Plurk API結合部分

class PlurkStreaming extends EventEmitter

consuctor (options) -gt

plurk (callback) -gt

觀察河道

getChannel -gt

取得 Comet 網址

reply (plurk id message) -gt

回噗

acceptFriends -gt

接受好友

get (path callback) -gt

GET 請求

post (path body callback)-gt

POST 請求(其實是裝飾)

request (method path body callback)-gt

主要的 OAuth 請求

comet (server callback)-gt

噗浪的 Comet 傳回是 JavaScript Callback 要另外處理後才會變成 JSON

然後把注意力集中到 constructor上先把建構子弄好

constructor (options) -gt

super()

if optionskey and optionssecret and optionstoken and optionstoken secret

key = optionskey

secret = optionssecret

token = optionstoken

token secret = optionstoken secret

83 建立 Robot跟 API 73

Nodejs中文電子書

建立 OAuth 連接

consumer = new oauthOAuth(

rdquohttpwwwplurkcomOAuthrequest tokenrdquo

rdquohttpwwwplurkcomOAuthaccess tokenrdquo

key

secret

rdquo10rdquo

rdquohttpwwwplurkcomOAuthauthorizerdquo

rdquoHMAC-SHA1rdquo

)

domain = rdquowwwplurkcomrdquo

初始化取得 Comet 網址

do getChannel

else

throw new Error(rdquo 參數不足需要 Key Secret Token Token Secretrdquo)

接著來處理 request這個 method

request (method path body callback) -gt

記錄一下這次的 Request

consolelog(rdquohttpdomainpathrdquo)

Callback 這邊先不丟進去要用另一種方式處理

request = consumerget(rdquohttpdomainpathrdquo token token secret null)

requeston rdquoresponserdquo (res) -gt

reson rdquodatardquo (chunk) -gt

parseResponse(chunk+rsquorsquo callback)

reson rdquoendrdquo (data) -gt

consolelog rdquoEnd Request pathrdquo

reson rdquoerrorrdquo (data) -gt

consolelog rdquoError rdquo + data

requestend()

處理資料

parseResponse = (data callback) -gt

if datalength gt 0

用 TryCatch 避免處理 JSON 出錯導致整個中斷

try

callback null JSONparse(data)

catch err

74 8 製作一個 Hubot的 Plurk Adapter

Nodejs中文電子書

consolelog(rdquoError Parse JSONrdquo + data err)

繼續執行

callback null data ||

大致上就是這樣上面程式的架構已經將整個 Hubot Plurk Adapter完成因為在測試時竟然因為噗浪 Lag而沒讀到完整的 Comet資料然後造成程式異常為了避免這個問題發生因此需要加上為了完美呈現需要再

加上 Comet的處理所以要使用到 EventEmitter的功能

comet (server callback) -gt

在 Callback 裡面會找不到自身所以設定區域變數

self =

記錄一下這次的 Request

consolelog(rdquo[Comet] serverrdquo)

Callback 這邊先不丟進去要用另一種方式處理

request = consumerget(rdquohttpdomainpathrdquo token token secret null)

requeston rdquoresponserdquo (res) -gt

reson rdquodatardquo (chunk) -gt

parseResponse(chunk+rsquorsquo callback)

reson rdquoendrdquo (data) -gt

consolelog rdquoEnd Request pathrdquo

請求結束發出事件通知可以進行下一次請求

selfemit rdquonextPlurkrdquo

reson rdquoerrorrdquo (data) -gt

consolelog rdquoError rdquo + data

requestend()

處理資料

parseResponse = (data callback) -gt

if datalength gt 0

用 trycatch 避免失敗中斷

try

去掉 JavaScript 的 Callback

data = datamatch(CometChannelscriptCallback((+))s)

jsonData = rdquordquo

if data

83 建立 Robot跟 API 75

Nodejs中文電子書

jsonData = JSONparse(data[1])

else

如果沒有任何 Match 嘗試直接 parse

jsonData = JSONparse(data)

catch err

consolelog(rdquo[Comet] Errorrdquo data err)

用 TryCatch 避免處理 JSON 出錯導致整個中斷

try

只傳入 json 的 data 部分

callback null jsonDatadata

catch err

consolelog(rdquo[Comet]Error Parse JSONrdquo + data err)

繼續執行

callback null data ||

後面的 get跟 post就簡單多了

get (path callback) -gt

request(rdquoGETrdquo path null callback)

post (path body callback) -gt

request(rdquoPOSTrdquo path body callback)

接著處理取的 Comet網址的 getChannel

getChannel -gt

self =

get rdquoAPPRealtimegetUserChannelrdquo (error data) -gt

if error

檢查是否有 comet server

if datacomet server

selfchannel = datacomet server

如果沒有 Channel Ready 就嘗試連接會失敗

selfemit(rsquochannel readyrsquo)

那麼先來處理 Plurk Adaper好處理的部份

send (plruk id strings⋯)-gt

跟 Reply 一樣直接交給 reply 做

reply plurk id strings⋯

76 8 製作一個 Hubot的 Plurk Adapter

Nodejs中文電子書

reply (plurk id strings⋯) -gt

stringsforEach (message) =gt

botreply(plruk id message)

接著把 run處理好就可以上線運作摟

run -gt

self =

options =

key processenvHUBOT PLURK KEY

secret processenvHUBOT PLURK SECRET

token processenvHUBOT PLURK TOKEN

token secret processenvHUBOT PLURK TOKEN SECRET

創建剛剛的 API

bot = new PlurkStreaming(options)

依照 Twitter 的 new RobotTextMessage 會沒有反應所以參考 hubot-minecraft 的方式

r = robotconstructor

處理噗浪河道訊息

doPlurk = (data)-gt

檢查是否為回噗

if dataresponse

datacontent raw = dataresponsecontent raw

datauser id = dataresponseuser id

確定有噗浪 ID 跟訊息

if dataplurk id and datacontent raw

selfreceive new rTextMessage(dataplurk id datacontent raw)

取得 Comet Server 完成開始第一次 Comet 連接

boton rdquochannel readyrdquo () -gt

botplurk selfdoPlurk

上一次 Comet 完成繼續 Polling

boton rdquonextPlurkrdquo ()-gt

botplurk selfdoPlurk

定時接受好友邀請

do botacceptFriends

bot = bot

83 建立 Robot跟 API 77

Nodejs中文電子書

終於完成 AdapterHubot專案裡面的 scripts資料夾內是互動部分不需要像 Adapter如此大費周章處理只新增檔案並且設計好對白之後就會回噗了我開發用的機器人在此大家可以去跟他玩玩[httpplurkcomelct9620 bot](httpplurkcomelct9620 bot)

84 原始資料提供

bull [製 作 一 個 Hubot 的 噗 浪 Adapter](http revo-skillfrosttw blog20120318create-a-hubot-plurk-adapter)

78 8 製作一個 Hubot的 Plurk Adapter

9

Nodejs好用工具介紹

91 logio

Real-time log monitoring in your browser

httplogioorg

nodejs (gt=0412 lt=0611)

npm config set unsafe-perm true

npm install -g --prefix=usrlocal logio

logio server start

httplocalhost8998

79

Nodejs中文電子書

80 9 Nodejs好用工具介紹

10

精選文章

101 Express

bull 使用 NodeJS + Express從 GETPOST Request取值 [邀稿中]

bull link2 [邀稿中]

bull link3 [邀稿中]

81

Nodejs中文電子書

82 10 精選文章

11

參考資源

111 Nodejs書籍

bull Programming Nodejs April 2012 (est) Travis Swicegood Pragmatic Bookshelf

bull e Little Book on CoffeeScript January 2012 Alex MacCaw OrsquoReilly Media

bull Node for Front-End Developers January 2012 Garann Means OrsquoReilly Media

bull Building Hypermedia APIs with HTML5 and Node November 2011 MikeAmundsen OrsquoReilly Media

bull Node Web Development August 2011 David Herron PacktPub

bull CoffeeScript Accelerated JavaScript Development July 2011 Trevor BurnhamPragmatic Bookshelf

bull Getting Started with GEO CouchDB and Nodejs July 2011 Mick omp-son OrsquoReilly Media

bull What Is Node July 2011 Brett McLaughlin OrsquoReilly Media

bull Node Up and Running May 2011 Tom Hughes-Croucher Mike WilsonOrsquoReilly Media

83

Nodejs中文電子書

bull Hands-on Nodejs e Nodejs introduction and API reference May 2011 Pe-dro Teixeira LeanPub

bull e Node Beginner Book Manuel Kiessling

bull Mastering Nodejs visionmedia

112 Nodejs影音教學

bull e Node Sessions e Best of OSCON 2011 August 2011 OrsquoReilly Media(Video)

bull Tom Hughes-Croucher on Node March 2011 OrsquoReilly Media (Video)

113 Nodejs教學網站

bull Node Tuts

114 Nodejs課程

bull Introduction To Nodejs Van Nguyen CodeLesson

84 11 參考資源

Page 74: Nodejs Wiki

Nodejs中文電子書

68 6 Express介紹

7

CoffeeScript

bull Spine

bull Hem

bull Spineapp

Letrsquos Have a Cup of CoffeeScript

bull es5-shim ECMAScript 5 compatibility shims for legacy JavaScript engines

ESnext

node-iform - A middleware for node-validator httpsgithubcomguileennode-iform

69

Nodejs中文電子書

70 7 CoffeeScript

8

製作一個 Hubot的 Plurk Adapter

81 應用事項提醒

此應用範例以CoffeeScript編寫主要程式架構採用Github釋放的Hubot專案以下有幾個主要注意事項請讀者注意

bull Hubot 一開始的架構除了內建的兩個 Adapter 之外其餘都要以Nodejs的Module方式才能運作

bull Hubot的 binhubot裡面寫著 npm install所以不管你怎麼改原始碼也不能改變第一點的狀況

其實上述都在討論同一件事情NodeJS的模組而且模組不能設定相對路徑之類的來安裝一定要包裝成 npm相符和格式才有辦法正確執行

82 建立 Adapter

首先需要準備兩個檔案

bull packagejson

bull plurkcoffee

71

Nodejs中文電子書

有這兩個就足以變成 NodeJS的模組了在開始 Coding之前先來設定一下 packagejson相依性

rdquonamerdquo rdquohubot-plurkrdquo

rdquoversionrdquo rdquo011rdquo

rdquomainrdquo rdquoplurkrdquo

rdquodependenciesrdquo

rdquohubotrdquo rdquogt=205rdquo

rdquooauthrdquo rdquordquo

rdquocronrdquordquordquo

這邊會使用到NodeJS的 cron模組(Module)而登入 Plurk需要OAuth標準授權才行所以也需要加載 OAuth模組

注意Hubot在讀取非內建模組時會自動在前面加上 hubot-的前置

83 建立 Robot跟 API

本篇應用基本上程式碼大部分參考 Hubot - Twitter Adapter來製作主要差異只有在使用 OAuth這個模組Twitter有 Streaming可用而 Plurk則得用 Comet方式來達到即時讀取

plurkcoffee程式碼

Robot = require(rdquohubotrdquo)robot()

Adapter = require(rdquohubtordquo)adapter()

EventEmitter = require(rdquoeventsrdquo)EventEmitter

oauth = require(rdquooauthrdquo)

cronJob = require(rdquocronrdquo)CronJob

class Plurk exntends Adapter

class PlurkStreaming exnteds EventEmitter

72 8 製作一個 Hubot的 Plurk Adapter

Nodejs中文電子書

先弄個基本架構主要 Class皆參考 Twitter Adapter命名方式接著再將 Plurk這個 Class增加幾個Method基本上只要有 run send reply就夠了而 run用來做初始化的部分

class Plurk entends Adapter

send (plurk id strings⋯) -gt

reply (plurk id strings⋯) -gt

run -gt

看起來有點東西接著來處理主要的 Plurk API結合部分

class PlurkStreaming extends EventEmitter

consuctor (options) -gt

plurk (callback) -gt

觀察河道

getChannel -gt

取得 Comet 網址

reply (plurk id message) -gt

回噗

acceptFriends -gt

接受好友

get (path callback) -gt

GET 請求

post (path body callback)-gt

POST 請求(其實是裝飾)

request (method path body callback)-gt

主要的 OAuth 請求

comet (server callback)-gt

噗浪的 Comet 傳回是 JavaScript Callback 要另外處理後才會變成 JSON

然後把注意力集中到 constructor上先把建構子弄好

constructor (options) -gt

super()

if optionskey and optionssecret and optionstoken and optionstoken secret

key = optionskey

secret = optionssecret

token = optionstoken

token secret = optionstoken secret

83 建立 Robot跟 API 73

Nodejs中文電子書

建立 OAuth 連接

consumer = new oauthOAuth(

rdquohttpwwwplurkcomOAuthrequest tokenrdquo

rdquohttpwwwplurkcomOAuthaccess tokenrdquo

key

secret

rdquo10rdquo

rdquohttpwwwplurkcomOAuthauthorizerdquo

rdquoHMAC-SHA1rdquo

)

domain = rdquowwwplurkcomrdquo

初始化取得 Comet 網址

do getChannel

else

throw new Error(rdquo 參數不足需要 Key Secret Token Token Secretrdquo)

接著來處理 request這個 method

request (method path body callback) -gt

記錄一下這次的 Request

consolelog(rdquohttpdomainpathrdquo)

Callback 這邊先不丟進去要用另一種方式處理

request = consumerget(rdquohttpdomainpathrdquo token token secret null)

requeston rdquoresponserdquo (res) -gt

reson rdquodatardquo (chunk) -gt

parseResponse(chunk+rsquorsquo callback)

reson rdquoendrdquo (data) -gt

consolelog rdquoEnd Request pathrdquo

reson rdquoerrorrdquo (data) -gt

consolelog rdquoError rdquo + data

requestend()

處理資料

parseResponse = (data callback) -gt

if datalength gt 0

用 TryCatch 避免處理 JSON 出錯導致整個中斷

try

callback null JSONparse(data)

catch err

74 8 製作一個 Hubot的 Plurk Adapter

Nodejs中文電子書

consolelog(rdquoError Parse JSONrdquo + data err)

繼續執行

callback null data ||

大致上就是這樣上面程式的架構已經將整個 Hubot Plurk Adapter完成因為在測試時竟然因為噗浪 Lag而沒讀到完整的 Comet資料然後造成程式異常為了避免這個問題發生因此需要加上為了完美呈現需要再

加上 Comet的處理所以要使用到 EventEmitter的功能

comet (server callback) -gt

在 Callback 裡面會找不到自身所以設定區域變數

self =

記錄一下這次的 Request

consolelog(rdquo[Comet] serverrdquo)

Callback 這邊先不丟進去要用另一種方式處理

request = consumerget(rdquohttpdomainpathrdquo token token secret null)

requeston rdquoresponserdquo (res) -gt

reson rdquodatardquo (chunk) -gt

parseResponse(chunk+rsquorsquo callback)

reson rdquoendrdquo (data) -gt

consolelog rdquoEnd Request pathrdquo

請求結束發出事件通知可以進行下一次請求

selfemit rdquonextPlurkrdquo

reson rdquoerrorrdquo (data) -gt

consolelog rdquoError rdquo + data

requestend()

處理資料

parseResponse = (data callback) -gt

if datalength gt 0

用 trycatch 避免失敗中斷

try

去掉 JavaScript 的 Callback

data = datamatch(CometChannelscriptCallback((+))s)

jsonData = rdquordquo

if data

83 建立 Robot跟 API 75

Nodejs中文電子書

jsonData = JSONparse(data[1])

else

如果沒有任何 Match 嘗試直接 parse

jsonData = JSONparse(data)

catch err

consolelog(rdquo[Comet] Errorrdquo data err)

用 TryCatch 避免處理 JSON 出錯導致整個中斷

try

只傳入 json 的 data 部分

callback null jsonDatadata

catch err

consolelog(rdquo[Comet]Error Parse JSONrdquo + data err)

繼續執行

callback null data ||

後面的 get跟 post就簡單多了

get (path callback) -gt

request(rdquoGETrdquo path null callback)

post (path body callback) -gt

request(rdquoPOSTrdquo path body callback)

接著處理取的 Comet網址的 getChannel

getChannel -gt

self =

get rdquoAPPRealtimegetUserChannelrdquo (error data) -gt

if error

檢查是否有 comet server

if datacomet server

selfchannel = datacomet server

如果沒有 Channel Ready 就嘗試連接會失敗

selfemit(rsquochannel readyrsquo)

那麼先來處理 Plurk Adaper好處理的部份

send (plruk id strings⋯)-gt

跟 Reply 一樣直接交給 reply 做

reply plurk id strings⋯

76 8 製作一個 Hubot的 Plurk Adapter

Nodejs中文電子書

reply (plurk id strings⋯) -gt

stringsforEach (message) =gt

botreply(plruk id message)

接著把 run處理好就可以上線運作摟

run -gt

self =

options =

key processenvHUBOT PLURK KEY

secret processenvHUBOT PLURK SECRET

token processenvHUBOT PLURK TOKEN

token secret processenvHUBOT PLURK TOKEN SECRET

創建剛剛的 API

bot = new PlurkStreaming(options)

依照 Twitter 的 new RobotTextMessage 會沒有反應所以參考 hubot-minecraft 的方式

r = robotconstructor

處理噗浪河道訊息

doPlurk = (data)-gt

檢查是否為回噗

if dataresponse

datacontent raw = dataresponsecontent raw

datauser id = dataresponseuser id

確定有噗浪 ID 跟訊息

if dataplurk id and datacontent raw

selfreceive new rTextMessage(dataplurk id datacontent raw)

取得 Comet Server 完成開始第一次 Comet 連接

boton rdquochannel readyrdquo () -gt

botplurk selfdoPlurk

上一次 Comet 完成繼續 Polling

boton rdquonextPlurkrdquo ()-gt

botplurk selfdoPlurk

定時接受好友邀請

do botacceptFriends

bot = bot

83 建立 Robot跟 API 77

Nodejs中文電子書

終於完成 AdapterHubot專案裡面的 scripts資料夾內是互動部分不需要像 Adapter如此大費周章處理只新增檔案並且設計好對白之後就會回噗了我開發用的機器人在此大家可以去跟他玩玩[httpplurkcomelct9620 bot](httpplurkcomelct9620 bot)

84 原始資料提供

bull [製 作 一 個 Hubot 的 噗 浪 Adapter](http revo-skillfrosttw blog20120318create-a-hubot-plurk-adapter)

78 8 製作一個 Hubot的 Plurk Adapter

9

Nodejs好用工具介紹

91 logio

Real-time log monitoring in your browser

httplogioorg

nodejs (gt=0412 lt=0611)

npm config set unsafe-perm true

npm install -g --prefix=usrlocal logio

logio server start

httplocalhost8998

79

Nodejs中文電子書

80 9 Nodejs好用工具介紹

10

精選文章

101 Express

bull 使用 NodeJS + Express從 GETPOST Request取值 [邀稿中]

bull link2 [邀稿中]

bull link3 [邀稿中]

81

Nodejs中文電子書

82 10 精選文章

11

參考資源

111 Nodejs書籍

bull Programming Nodejs April 2012 (est) Travis Swicegood Pragmatic Bookshelf

bull e Little Book on CoffeeScript January 2012 Alex MacCaw OrsquoReilly Media

bull Node for Front-End Developers January 2012 Garann Means OrsquoReilly Media

bull Building Hypermedia APIs with HTML5 and Node November 2011 MikeAmundsen OrsquoReilly Media

bull Node Web Development August 2011 David Herron PacktPub

bull CoffeeScript Accelerated JavaScript Development July 2011 Trevor BurnhamPragmatic Bookshelf

bull Getting Started with GEO CouchDB and Nodejs July 2011 Mick omp-son OrsquoReilly Media

bull What Is Node July 2011 Brett McLaughlin OrsquoReilly Media

bull Node Up and Running May 2011 Tom Hughes-Croucher Mike WilsonOrsquoReilly Media

83

Nodejs中文電子書

bull Hands-on Nodejs e Nodejs introduction and API reference May 2011 Pe-dro Teixeira LeanPub

bull e Node Beginner Book Manuel Kiessling

bull Mastering Nodejs visionmedia

112 Nodejs影音教學

bull e Node Sessions e Best of OSCON 2011 August 2011 OrsquoReilly Media(Video)

bull Tom Hughes-Croucher on Node March 2011 OrsquoReilly Media (Video)

113 Nodejs教學網站

bull Node Tuts

114 Nodejs課程

bull Introduction To Nodejs Van Nguyen CodeLesson

84 11 參考資源

Page 75: Nodejs Wiki

7

CoffeeScript

bull Spine

bull Hem

bull Spineapp

Letrsquos Have a Cup of CoffeeScript

bull es5-shim ECMAScript 5 compatibility shims for legacy JavaScript engines

ESnext

node-iform - A middleware for node-validator httpsgithubcomguileennode-iform

69

Nodejs中文電子書

70 7 CoffeeScript

8

製作一個 Hubot的 Plurk Adapter

81 應用事項提醒

此應用範例以CoffeeScript編寫主要程式架構採用Github釋放的Hubot專案以下有幾個主要注意事項請讀者注意

bull Hubot 一開始的架構除了內建的兩個 Adapter 之外其餘都要以Nodejs的Module方式才能運作

bull Hubot的 binhubot裡面寫著 npm install所以不管你怎麼改原始碼也不能改變第一點的狀況

其實上述都在討論同一件事情NodeJS的模組而且模組不能設定相對路徑之類的來安裝一定要包裝成 npm相符和格式才有辦法正確執行

82 建立 Adapter

首先需要準備兩個檔案

bull packagejson

bull plurkcoffee

71

Nodejs中文電子書

有這兩個就足以變成 NodeJS的模組了在開始 Coding之前先來設定一下 packagejson相依性

rdquonamerdquo rdquohubot-plurkrdquo

rdquoversionrdquo rdquo011rdquo

rdquomainrdquo rdquoplurkrdquo

rdquodependenciesrdquo

rdquohubotrdquo rdquogt=205rdquo

rdquooauthrdquo rdquordquo

rdquocronrdquordquordquo

這邊會使用到NodeJS的 cron模組(Module)而登入 Plurk需要OAuth標準授權才行所以也需要加載 OAuth模組

注意Hubot在讀取非內建模組時會自動在前面加上 hubot-的前置

83 建立 Robot跟 API

本篇應用基本上程式碼大部分參考 Hubot - Twitter Adapter來製作主要差異只有在使用 OAuth這個模組Twitter有 Streaming可用而 Plurk則得用 Comet方式來達到即時讀取

plurkcoffee程式碼

Robot = require(rdquohubotrdquo)robot()

Adapter = require(rdquohubtordquo)adapter()

EventEmitter = require(rdquoeventsrdquo)EventEmitter

oauth = require(rdquooauthrdquo)

cronJob = require(rdquocronrdquo)CronJob

class Plurk exntends Adapter

class PlurkStreaming exnteds EventEmitter

72 8 製作一個 Hubot的 Plurk Adapter

Nodejs中文電子書

先弄個基本架構主要 Class皆參考 Twitter Adapter命名方式接著再將 Plurk這個 Class增加幾個Method基本上只要有 run send reply就夠了而 run用來做初始化的部分

class Plurk entends Adapter

send (plurk id strings⋯) -gt

reply (plurk id strings⋯) -gt

run -gt

看起來有點東西接著來處理主要的 Plurk API結合部分

class PlurkStreaming extends EventEmitter

consuctor (options) -gt

plurk (callback) -gt

觀察河道

getChannel -gt

取得 Comet 網址

reply (plurk id message) -gt

回噗

acceptFriends -gt

接受好友

get (path callback) -gt

GET 請求

post (path body callback)-gt

POST 請求(其實是裝飾)

request (method path body callback)-gt

主要的 OAuth 請求

comet (server callback)-gt

噗浪的 Comet 傳回是 JavaScript Callback 要另外處理後才會變成 JSON

然後把注意力集中到 constructor上先把建構子弄好

constructor (options) -gt

super()

if optionskey and optionssecret and optionstoken and optionstoken secret

key = optionskey

secret = optionssecret

token = optionstoken

token secret = optionstoken secret

83 建立 Robot跟 API 73

Nodejs中文電子書

建立 OAuth 連接

consumer = new oauthOAuth(

rdquohttpwwwplurkcomOAuthrequest tokenrdquo

rdquohttpwwwplurkcomOAuthaccess tokenrdquo

key

secret

rdquo10rdquo

rdquohttpwwwplurkcomOAuthauthorizerdquo

rdquoHMAC-SHA1rdquo

)

domain = rdquowwwplurkcomrdquo

初始化取得 Comet 網址

do getChannel

else

throw new Error(rdquo 參數不足需要 Key Secret Token Token Secretrdquo)

接著來處理 request這個 method

request (method path body callback) -gt

記錄一下這次的 Request

consolelog(rdquohttpdomainpathrdquo)

Callback 這邊先不丟進去要用另一種方式處理

request = consumerget(rdquohttpdomainpathrdquo token token secret null)

requeston rdquoresponserdquo (res) -gt

reson rdquodatardquo (chunk) -gt

parseResponse(chunk+rsquorsquo callback)

reson rdquoendrdquo (data) -gt

consolelog rdquoEnd Request pathrdquo

reson rdquoerrorrdquo (data) -gt

consolelog rdquoError rdquo + data

requestend()

處理資料

parseResponse = (data callback) -gt

if datalength gt 0

用 TryCatch 避免處理 JSON 出錯導致整個中斷

try

callback null JSONparse(data)

catch err

74 8 製作一個 Hubot的 Plurk Adapter

Nodejs中文電子書

consolelog(rdquoError Parse JSONrdquo + data err)

繼續執行

callback null data ||

大致上就是這樣上面程式的架構已經將整個 Hubot Plurk Adapter完成因為在測試時竟然因為噗浪 Lag而沒讀到完整的 Comet資料然後造成程式異常為了避免這個問題發生因此需要加上為了完美呈現需要再

加上 Comet的處理所以要使用到 EventEmitter的功能

comet (server callback) -gt

在 Callback 裡面會找不到自身所以設定區域變數

self =

記錄一下這次的 Request

consolelog(rdquo[Comet] serverrdquo)

Callback 這邊先不丟進去要用另一種方式處理

request = consumerget(rdquohttpdomainpathrdquo token token secret null)

requeston rdquoresponserdquo (res) -gt

reson rdquodatardquo (chunk) -gt

parseResponse(chunk+rsquorsquo callback)

reson rdquoendrdquo (data) -gt

consolelog rdquoEnd Request pathrdquo

請求結束發出事件通知可以進行下一次請求

selfemit rdquonextPlurkrdquo

reson rdquoerrorrdquo (data) -gt

consolelog rdquoError rdquo + data

requestend()

處理資料

parseResponse = (data callback) -gt

if datalength gt 0

用 trycatch 避免失敗中斷

try

去掉 JavaScript 的 Callback

data = datamatch(CometChannelscriptCallback((+))s)

jsonData = rdquordquo

if data

83 建立 Robot跟 API 75

Nodejs中文電子書

jsonData = JSONparse(data[1])

else

如果沒有任何 Match 嘗試直接 parse

jsonData = JSONparse(data)

catch err

consolelog(rdquo[Comet] Errorrdquo data err)

用 TryCatch 避免處理 JSON 出錯導致整個中斷

try

只傳入 json 的 data 部分

callback null jsonDatadata

catch err

consolelog(rdquo[Comet]Error Parse JSONrdquo + data err)

繼續執行

callback null data ||

後面的 get跟 post就簡單多了

get (path callback) -gt

request(rdquoGETrdquo path null callback)

post (path body callback) -gt

request(rdquoPOSTrdquo path body callback)

接著處理取的 Comet網址的 getChannel

getChannel -gt

self =

get rdquoAPPRealtimegetUserChannelrdquo (error data) -gt

if error

檢查是否有 comet server

if datacomet server

selfchannel = datacomet server

如果沒有 Channel Ready 就嘗試連接會失敗

selfemit(rsquochannel readyrsquo)

那麼先來處理 Plurk Adaper好處理的部份

send (plruk id strings⋯)-gt

跟 Reply 一樣直接交給 reply 做

reply plurk id strings⋯

76 8 製作一個 Hubot的 Plurk Adapter

Nodejs中文電子書

reply (plurk id strings⋯) -gt

stringsforEach (message) =gt

botreply(plruk id message)

接著把 run處理好就可以上線運作摟

run -gt

self =

options =

key processenvHUBOT PLURK KEY

secret processenvHUBOT PLURK SECRET

token processenvHUBOT PLURK TOKEN

token secret processenvHUBOT PLURK TOKEN SECRET

創建剛剛的 API

bot = new PlurkStreaming(options)

依照 Twitter 的 new RobotTextMessage 會沒有反應所以參考 hubot-minecraft 的方式

r = robotconstructor

處理噗浪河道訊息

doPlurk = (data)-gt

檢查是否為回噗

if dataresponse

datacontent raw = dataresponsecontent raw

datauser id = dataresponseuser id

確定有噗浪 ID 跟訊息

if dataplurk id and datacontent raw

selfreceive new rTextMessage(dataplurk id datacontent raw)

取得 Comet Server 完成開始第一次 Comet 連接

boton rdquochannel readyrdquo () -gt

botplurk selfdoPlurk

上一次 Comet 完成繼續 Polling

boton rdquonextPlurkrdquo ()-gt

botplurk selfdoPlurk

定時接受好友邀請

do botacceptFriends

bot = bot

83 建立 Robot跟 API 77

Nodejs中文電子書

終於完成 AdapterHubot專案裡面的 scripts資料夾內是互動部分不需要像 Adapter如此大費周章處理只新增檔案並且設計好對白之後就會回噗了我開發用的機器人在此大家可以去跟他玩玩[httpplurkcomelct9620 bot](httpplurkcomelct9620 bot)

84 原始資料提供

bull [製 作 一 個 Hubot 的 噗 浪 Adapter](http revo-skillfrosttw blog20120318create-a-hubot-plurk-adapter)

78 8 製作一個 Hubot的 Plurk Adapter

9

Nodejs好用工具介紹

91 logio

Real-time log monitoring in your browser

httplogioorg

nodejs (gt=0412 lt=0611)

npm config set unsafe-perm true

npm install -g --prefix=usrlocal logio

logio server start

httplocalhost8998

79

Nodejs中文電子書

80 9 Nodejs好用工具介紹

10

精選文章

101 Express

bull 使用 NodeJS + Express從 GETPOST Request取值 [邀稿中]

bull link2 [邀稿中]

bull link3 [邀稿中]

81

Nodejs中文電子書

82 10 精選文章

11

參考資源

111 Nodejs書籍

bull Programming Nodejs April 2012 (est) Travis Swicegood Pragmatic Bookshelf

bull e Little Book on CoffeeScript January 2012 Alex MacCaw OrsquoReilly Media

bull Node for Front-End Developers January 2012 Garann Means OrsquoReilly Media

bull Building Hypermedia APIs with HTML5 and Node November 2011 MikeAmundsen OrsquoReilly Media

bull Node Web Development August 2011 David Herron PacktPub

bull CoffeeScript Accelerated JavaScript Development July 2011 Trevor BurnhamPragmatic Bookshelf

bull Getting Started with GEO CouchDB and Nodejs July 2011 Mick omp-son OrsquoReilly Media

bull What Is Node July 2011 Brett McLaughlin OrsquoReilly Media

bull Node Up and Running May 2011 Tom Hughes-Croucher Mike WilsonOrsquoReilly Media

83

Nodejs中文電子書

bull Hands-on Nodejs e Nodejs introduction and API reference May 2011 Pe-dro Teixeira LeanPub

bull e Node Beginner Book Manuel Kiessling

bull Mastering Nodejs visionmedia

112 Nodejs影音教學

bull e Node Sessions e Best of OSCON 2011 August 2011 OrsquoReilly Media(Video)

bull Tom Hughes-Croucher on Node March 2011 OrsquoReilly Media (Video)

113 Nodejs教學網站

bull Node Tuts

114 Nodejs課程

bull Introduction To Nodejs Van Nguyen CodeLesson

84 11 參考資源

Page 76: Nodejs Wiki

Nodejs中文電子書

70 7 CoffeeScript

8

製作一個 Hubot的 Plurk Adapter

81 應用事項提醒

此應用範例以CoffeeScript編寫主要程式架構採用Github釋放的Hubot專案以下有幾個主要注意事項請讀者注意

bull Hubot 一開始的架構除了內建的兩個 Adapter 之外其餘都要以Nodejs的Module方式才能運作

bull Hubot的 binhubot裡面寫著 npm install所以不管你怎麼改原始碼也不能改變第一點的狀況

其實上述都在討論同一件事情NodeJS的模組而且模組不能設定相對路徑之類的來安裝一定要包裝成 npm相符和格式才有辦法正確執行

82 建立 Adapter

首先需要準備兩個檔案

bull packagejson

bull plurkcoffee

71

Nodejs中文電子書

有這兩個就足以變成 NodeJS的模組了在開始 Coding之前先來設定一下 packagejson相依性

rdquonamerdquo rdquohubot-plurkrdquo

rdquoversionrdquo rdquo011rdquo

rdquomainrdquo rdquoplurkrdquo

rdquodependenciesrdquo

rdquohubotrdquo rdquogt=205rdquo

rdquooauthrdquo rdquordquo

rdquocronrdquordquordquo

這邊會使用到NodeJS的 cron模組(Module)而登入 Plurk需要OAuth標準授權才行所以也需要加載 OAuth模組

注意Hubot在讀取非內建模組時會自動在前面加上 hubot-的前置

83 建立 Robot跟 API

本篇應用基本上程式碼大部分參考 Hubot - Twitter Adapter來製作主要差異只有在使用 OAuth這個模組Twitter有 Streaming可用而 Plurk則得用 Comet方式來達到即時讀取

plurkcoffee程式碼

Robot = require(rdquohubotrdquo)robot()

Adapter = require(rdquohubtordquo)adapter()

EventEmitter = require(rdquoeventsrdquo)EventEmitter

oauth = require(rdquooauthrdquo)

cronJob = require(rdquocronrdquo)CronJob

class Plurk exntends Adapter

class PlurkStreaming exnteds EventEmitter

72 8 製作一個 Hubot的 Plurk Adapter

Nodejs中文電子書

先弄個基本架構主要 Class皆參考 Twitter Adapter命名方式接著再將 Plurk這個 Class增加幾個Method基本上只要有 run send reply就夠了而 run用來做初始化的部分

class Plurk entends Adapter

send (plurk id strings⋯) -gt

reply (plurk id strings⋯) -gt

run -gt

看起來有點東西接著來處理主要的 Plurk API結合部分

class PlurkStreaming extends EventEmitter

consuctor (options) -gt

plurk (callback) -gt

觀察河道

getChannel -gt

取得 Comet 網址

reply (plurk id message) -gt

回噗

acceptFriends -gt

接受好友

get (path callback) -gt

GET 請求

post (path body callback)-gt

POST 請求(其實是裝飾)

request (method path body callback)-gt

主要的 OAuth 請求

comet (server callback)-gt

噗浪的 Comet 傳回是 JavaScript Callback 要另外處理後才會變成 JSON

然後把注意力集中到 constructor上先把建構子弄好

constructor (options) -gt

super()

if optionskey and optionssecret and optionstoken and optionstoken secret

key = optionskey

secret = optionssecret

token = optionstoken

token secret = optionstoken secret

83 建立 Robot跟 API 73

Nodejs中文電子書

建立 OAuth 連接

consumer = new oauthOAuth(

rdquohttpwwwplurkcomOAuthrequest tokenrdquo

rdquohttpwwwplurkcomOAuthaccess tokenrdquo

key

secret

rdquo10rdquo

rdquohttpwwwplurkcomOAuthauthorizerdquo

rdquoHMAC-SHA1rdquo

)

domain = rdquowwwplurkcomrdquo

初始化取得 Comet 網址

do getChannel

else

throw new Error(rdquo 參數不足需要 Key Secret Token Token Secretrdquo)

接著來處理 request這個 method

request (method path body callback) -gt

記錄一下這次的 Request

consolelog(rdquohttpdomainpathrdquo)

Callback 這邊先不丟進去要用另一種方式處理

request = consumerget(rdquohttpdomainpathrdquo token token secret null)

requeston rdquoresponserdquo (res) -gt

reson rdquodatardquo (chunk) -gt

parseResponse(chunk+rsquorsquo callback)

reson rdquoendrdquo (data) -gt

consolelog rdquoEnd Request pathrdquo

reson rdquoerrorrdquo (data) -gt

consolelog rdquoError rdquo + data

requestend()

處理資料

parseResponse = (data callback) -gt

if datalength gt 0

用 TryCatch 避免處理 JSON 出錯導致整個中斷

try

callback null JSONparse(data)

catch err

74 8 製作一個 Hubot的 Plurk Adapter

Nodejs中文電子書

consolelog(rdquoError Parse JSONrdquo + data err)

繼續執行

callback null data ||

大致上就是這樣上面程式的架構已經將整個 Hubot Plurk Adapter完成因為在測試時竟然因為噗浪 Lag而沒讀到完整的 Comet資料然後造成程式異常為了避免這個問題發生因此需要加上為了完美呈現需要再

加上 Comet的處理所以要使用到 EventEmitter的功能

comet (server callback) -gt

在 Callback 裡面會找不到自身所以設定區域變數

self =

記錄一下這次的 Request

consolelog(rdquo[Comet] serverrdquo)

Callback 這邊先不丟進去要用另一種方式處理

request = consumerget(rdquohttpdomainpathrdquo token token secret null)

requeston rdquoresponserdquo (res) -gt

reson rdquodatardquo (chunk) -gt

parseResponse(chunk+rsquorsquo callback)

reson rdquoendrdquo (data) -gt

consolelog rdquoEnd Request pathrdquo

請求結束發出事件通知可以進行下一次請求

selfemit rdquonextPlurkrdquo

reson rdquoerrorrdquo (data) -gt

consolelog rdquoError rdquo + data

requestend()

處理資料

parseResponse = (data callback) -gt

if datalength gt 0

用 trycatch 避免失敗中斷

try

去掉 JavaScript 的 Callback

data = datamatch(CometChannelscriptCallback((+))s)

jsonData = rdquordquo

if data

83 建立 Robot跟 API 75

Nodejs中文電子書

jsonData = JSONparse(data[1])

else

如果沒有任何 Match 嘗試直接 parse

jsonData = JSONparse(data)

catch err

consolelog(rdquo[Comet] Errorrdquo data err)

用 TryCatch 避免處理 JSON 出錯導致整個中斷

try

只傳入 json 的 data 部分

callback null jsonDatadata

catch err

consolelog(rdquo[Comet]Error Parse JSONrdquo + data err)

繼續執行

callback null data ||

後面的 get跟 post就簡單多了

get (path callback) -gt

request(rdquoGETrdquo path null callback)

post (path body callback) -gt

request(rdquoPOSTrdquo path body callback)

接著處理取的 Comet網址的 getChannel

getChannel -gt

self =

get rdquoAPPRealtimegetUserChannelrdquo (error data) -gt

if error

檢查是否有 comet server

if datacomet server

selfchannel = datacomet server

如果沒有 Channel Ready 就嘗試連接會失敗

selfemit(rsquochannel readyrsquo)

那麼先來處理 Plurk Adaper好處理的部份

send (plruk id strings⋯)-gt

跟 Reply 一樣直接交給 reply 做

reply plurk id strings⋯

76 8 製作一個 Hubot的 Plurk Adapter

Nodejs中文電子書

reply (plurk id strings⋯) -gt

stringsforEach (message) =gt

botreply(plruk id message)

接著把 run處理好就可以上線運作摟

run -gt

self =

options =

key processenvHUBOT PLURK KEY

secret processenvHUBOT PLURK SECRET

token processenvHUBOT PLURK TOKEN

token secret processenvHUBOT PLURK TOKEN SECRET

創建剛剛的 API

bot = new PlurkStreaming(options)

依照 Twitter 的 new RobotTextMessage 會沒有反應所以參考 hubot-minecraft 的方式

r = robotconstructor

處理噗浪河道訊息

doPlurk = (data)-gt

檢查是否為回噗

if dataresponse

datacontent raw = dataresponsecontent raw

datauser id = dataresponseuser id

確定有噗浪 ID 跟訊息

if dataplurk id and datacontent raw

selfreceive new rTextMessage(dataplurk id datacontent raw)

取得 Comet Server 完成開始第一次 Comet 連接

boton rdquochannel readyrdquo () -gt

botplurk selfdoPlurk

上一次 Comet 完成繼續 Polling

boton rdquonextPlurkrdquo ()-gt

botplurk selfdoPlurk

定時接受好友邀請

do botacceptFriends

bot = bot

83 建立 Robot跟 API 77

Nodejs中文電子書

終於完成 AdapterHubot專案裡面的 scripts資料夾內是互動部分不需要像 Adapter如此大費周章處理只新增檔案並且設計好對白之後就會回噗了我開發用的機器人在此大家可以去跟他玩玩[httpplurkcomelct9620 bot](httpplurkcomelct9620 bot)

84 原始資料提供

bull [製 作 一 個 Hubot 的 噗 浪 Adapter](http revo-skillfrosttw blog20120318create-a-hubot-plurk-adapter)

78 8 製作一個 Hubot的 Plurk Adapter

9

Nodejs好用工具介紹

91 logio

Real-time log monitoring in your browser

httplogioorg

nodejs (gt=0412 lt=0611)

npm config set unsafe-perm true

npm install -g --prefix=usrlocal logio

logio server start

httplocalhost8998

79

Nodejs中文電子書

80 9 Nodejs好用工具介紹

10

精選文章

101 Express

bull 使用 NodeJS + Express從 GETPOST Request取值 [邀稿中]

bull link2 [邀稿中]

bull link3 [邀稿中]

81

Nodejs中文電子書

82 10 精選文章

11

參考資源

111 Nodejs書籍

bull Programming Nodejs April 2012 (est) Travis Swicegood Pragmatic Bookshelf

bull e Little Book on CoffeeScript January 2012 Alex MacCaw OrsquoReilly Media

bull Node for Front-End Developers January 2012 Garann Means OrsquoReilly Media

bull Building Hypermedia APIs with HTML5 and Node November 2011 MikeAmundsen OrsquoReilly Media

bull Node Web Development August 2011 David Herron PacktPub

bull CoffeeScript Accelerated JavaScript Development July 2011 Trevor BurnhamPragmatic Bookshelf

bull Getting Started with GEO CouchDB and Nodejs July 2011 Mick omp-son OrsquoReilly Media

bull What Is Node July 2011 Brett McLaughlin OrsquoReilly Media

bull Node Up and Running May 2011 Tom Hughes-Croucher Mike WilsonOrsquoReilly Media

83

Nodejs中文電子書

bull Hands-on Nodejs e Nodejs introduction and API reference May 2011 Pe-dro Teixeira LeanPub

bull e Node Beginner Book Manuel Kiessling

bull Mastering Nodejs visionmedia

112 Nodejs影音教學

bull e Node Sessions e Best of OSCON 2011 August 2011 OrsquoReilly Media(Video)

bull Tom Hughes-Croucher on Node March 2011 OrsquoReilly Media (Video)

113 Nodejs教學網站

bull Node Tuts

114 Nodejs課程

bull Introduction To Nodejs Van Nguyen CodeLesson

84 11 參考資源

Page 77: Nodejs Wiki

8

製作一個 Hubot的 Plurk Adapter

81 應用事項提醒

此應用範例以CoffeeScript編寫主要程式架構採用Github釋放的Hubot專案以下有幾個主要注意事項請讀者注意

bull Hubot 一開始的架構除了內建的兩個 Adapter 之外其餘都要以Nodejs的Module方式才能運作

bull Hubot的 binhubot裡面寫著 npm install所以不管你怎麼改原始碼也不能改變第一點的狀況

其實上述都在討論同一件事情NodeJS的模組而且模組不能設定相對路徑之類的來安裝一定要包裝成 npm相符和格式才有辦法正確執行

82 建立 Adapter

首先需要準備兩個檔案

bull packagejson

bull plurkcoffee

71

Nodejs中文電子書

有這兩個就足以變成 NodeJS的模組了在開始 Coding之前先來設定一下 packagejson相依性

rdquonamerdquo rdquohubot-plurkrdquo

rdquoversionrdquo rdquo011rdquo

rdquomainrdquo rdquoplurkrdquo

rdquodependenciesrdquo

rdquohubotrdquo rdquogt=205rdquo

rdquooauthrdquo rdquordquo

rdquocronrdquordquordquo

這邊會使用到NodeJS的 cron模組(Module)而登入 Plurk需要OAuth標準授權才行所以也需要加載 OAuth模組

注意Hubot在讀取非內建模組時會自動在前面加上 hubot-的前置

83 建立 Robot跟 API

本篇應用基本上程式碼大部分參考 Hubot - Twitter Adapter來製作主要差異只有在使用 OAuth這個模組Twitter有 Streaming可用而 Plurk則得用 Comet方式來達到即時讀取

plurkcoffee程式碼

Robot = require(rdquohubotrdquo)robot()

Adapter = require(rdquohubtordquo)adapter()

EventEmitter = require(rdquoeventsrdquo)EventEmitter

oauth = require(rdquooauthrdquo)

cronJob = require(rdquocronrdquo)CronJob

class Plurk exntends Adapter

class PlurkStreaming exnteds EventEmitter

72 8 製作一個 Hubot的 Plurk Adapter

Nodejs中文電子書

先弄個基本架構主要 Class皆參考 Twitter Adapter命名方式接著再將 Plurk這個 Class增加幾個Method基本上只要有 run send reply就夠了而 run用來做初始化的部分

class Plurk entends Adapter

send (plurk id strings⋯) -gt

reply (plurk id strings⋯) -gt

run -gt

看起來有點東西接著來處理主要的 Plurk API結合部分

class PlurkStreaming extends EventEmitter

consuctor (options) -gt

plurk (callback) -gt

觀察河道

getChannel -gt

取得 Comet 網址

reply (plurk id message) -gt

回噗

acceptFriends -gt

接受好友

get (path callback) -gt

GET 請求

post (path body callback)-gt

POST 請求(其實是裝飾)

request (method path body callback)-gt

主要的 OAuth 請求

comet (server callback)-gt

噗浪的 Comet 傳回是 JavaScript Callback 要另外處理後才會變成 JSON

然後把注意力集中到 constructor上先把建構子弄好

constructor (options) -gt

super()

if optionskey and optionssecret and optionstoken and optionstoken secret

key = optionskey

secret = optionssecret

token = optionstoken

token secret = optionstoken secret

83 建立 Robot跟 API 73

Nodejs中文電子書

建立 OAuth 連接

consumer = new oauthOAuth(

rdquohttpwwwplurkcomOAuthrequest tokenrdquo

rdquohttpwwwplurkcomOAuthaccess tokenrdquo

key

secret

rdquo10rdquo

rdquohttpwwwplurkcomOAuthauthorizerdquo

rdquoHMAC-SHA1rdquo

)

domain = rdquowwwplurkcomrdquo

初始化取得 Comet 網址

do getChannel

else

throw new Error(rdquo 參數不足需要 Key Secret Token Token Secretrdquo)

接著來處理 request這個 method

request (method path body callback) -gt

記錄一下這次的 Request

consolelog(rdquohttpdomainpathrdquo)

Callback 這邊先不丟進去要用另一種方式處理

request = consumerget(rdquohttpdomainpathrdquo token token secret null)

requeston rdquoresponserdquo (res) -gt

reson rdquodatardquo (chunk) -gt

parseResponse(chunk+rsquorsquo callback)

reson rdquoendrdquo (data) -gt

consolelog rdquoEnd Request pathrdquo

reson rdquoerrorrdquo (data) -gt

consolelog rdquoError rdquo + data

requestend()

處理資料

parseResponse = (data callback) -gt

if datalength gt 0

用 TryCatch 避免處理 JSON 出錯導致整個中斷

try

callback null JSONparse(data)

catch err

74 8 製作一個 Hubot的 Plurk Adapter

Nodejs中文電子書

consolelog(rdquoError Parse JSONrdquo + data err)

繼續執行

callback null data ||

大致上就是這樣上面程式的架構已經將整個 Hubot Plurk Adapter完成因為在測試時竟然因為噗浪 Lag而沒讀到完整的 Comet資料然後造成程式異常為了避免這個問題發生因此需要加上為了完美呈現需要再

加上 Comet的處理所以要使用到 EventEmitter的功能

comet (server callback) -gt

在 Callback 裡面會找不到自身所以設定區域變數

self =

記錄一下這次的 Request

consolelog(rdquo[Comet] serverrdquo)

Callback 這邊先不丟進去要用另一種方式處理

request = consumerget(rdquohttpdomainpathrdquo token token secret null)

requeston rdquoresponserdquo (res) -gt

reson rdquodatardquo (chunk) -gt

parseResponse(chunk+rsquorsquo callback)

reson rdquoendrdquo (data) -gt

consolelog rdquoEnd Request pathrdquo

請求結束發出事件通知可以進行下一次請求

selfemit rdquonextPlurkrdquo

reson rdquoerrorrdquo (data) -gt

consolelog rdquoError rdquo + data

requestend()

處理資料

parseResponse = (data callback) -gt

if datalength gt 0

用 trycatch 避免失敗中斷

try

去掉 JavaScript 的 Callback

data = datamatch(CometChannelscriptCallback((+))s)

jsonData = rdquordquo

if data

83 建立 Robot跟 API 75

Nodejs中文電子書

jsonData = JSONparse(data[1])

else

如果沒有任何 Match 嘗試直接 parse

jsonData = JSONparse(data)

catch err

consolelog(rdquo[Comet] Errorrdquo data err)

用 TryCatch 避免處理 JSON 出錯導致整個中斷

try

只傳入 json 的 data 部分

callback null jsonDatadata

catch err

consolelog(rdquo[Comet]Error Parse JSONrdquo + data err)

繼續執行

callback null data ||

後面的 get跟 post就簡單多了

get (path callback) -gt

request(rdquoGETrdquo path null callback)

post (path body callback) -gt

request(rdquoPOSTrdquo path body callback)

接著處理取的 Comet網址的 getChannel

getChannel -gt

self =

get rdquoAPPRealtimegetUserChannelrdquo (error data) -gt

if error

檢查是否有 comet server

if datacomet server

selfchannel = datacomet server

如果沒有 Channel Ready 就嘗試連接會失敗

selfemit(rsquochannel readyrsquo)

那麼先來處理 Plurk Adaper好處理的部份

send (plruk id strings⋯)-gt

跟 Reply 一樣直接交給 reply 做

reply plurk id strings⋯

76 8 製作一個 Hubot的 Plurk Adapter

Nodejs中文電子書

reply (plurk id strings⋯) -gt

stringsforEach (message) =gt

botreply(plruk id message)

接著把 run處理好就可以上線運作摟

run -gt

self =

options =

key processenvHUBOT PLURK KEY

secret processenvHUBOT PLURK SECRET

token processenvHUBOT PLURK TOKEN

token secret processenvHUBOT PLURK TOKEN SECRET

創建剛剛的 API

bot = new PlurkStreaming(options)

依照 Twitter 的 new RobotTextMessage 會沒有反應所以參考 hubot-minecraft 的方式

r = robotconstructor

處理噗浪河道訊息

doPlurk = (data)-gt

檢查是否為回噗

if dataresponse

datacontent raw = dataresponsecontent raw

datauser id = dataresponseuser id

確定有噗浪 ID 跟訊息

if dataplurk id and datacontent raw

selfreceive new rTextMessage(dataplurk id datacontent raw)

取得 Comet Server 完成開始第一次 Comet 連接

boton rdquochannel readyrdquo () -gt

botplurk selfdoPlurk

上一次 Comet 完成繼續 Polling

boton rdquonextPlurkrdquo ()-gt

botplurk selfdoPlurk

定時接受好友邀請

do botacceptFriends

bot = bot

83 建立 Robot跟 API 77

Nodejs中文電子書

終於完成 AdapterHubot專案裡面的 scripts資料夾內是互動部分不需要像 Adapter如此大費周章處理只新增檔案並且設計好對白之後就會回噗了我開發用的機器人在此大家可以去跟他玩玩[httpplurkcomelct9620 bot](httpplurkcomelct9620 bot)

84 原始資料提供

bull [製 作 一 個 Hubot 的 噗 浪 Adapter](http revo-skillfrosttw blog20120318create-a-hubot-plurk-adapter)

78 8 製作一個 Hubot的 Plurk Adapter

9

Nodejs好用工具介紹

91 logio

Real-time log monitoring in your browser

httplogioorg

nodejs (gt=0412 lt=0611)

npm config set unsafe-perm true

npm install -g --prefix=usrlocal logio

logio server start

httplocalhost8998

79

Nodejs中文電子書

80 9 Nodejs好用工具介紹

10

精選文章

101 Express

bull 使用 NodeJS + Express從 GETPOST Request取值 [邀稿中]

bull link2 [邀稿中]

bull link3 [邀稿中]

81

Nodejs中文電子書

82 10 精選文章

11

參考資源

111 Nodejs書籍

bull Programming Nodejs April 2012 (est) Travis Swicegood Pragmatic Bookshelf

bull e Little Book on CoffeeScript January 2012 Alex MacCaw OrsquoReilly Media

bull Node for Front-End Developers January 2012 Garann Means OrsquoReilly Media

bull Building Hypermedia APIs with HTML5 and Node November 2011 MikeAmundsen OrsquoReilly Media

bull Node Web Development August 2011 David Herron PacktPub

bull CoffeeScript Accelerated JavaScript Development July 2011 Trevor BurnhamPragmatic Bookshelf

bull Getting Started with GEO CouchDB and Nodejs July 2011 Mick omp-son OrsquoReilly Media

bull What Is Node July 2011 Brett McLaughlin OrsquoReilly Media

bull Node Up and Running May 2011 Tom Hughes-Croucher Mike WilsonOrsquoReilly Media

83

Nodejs中文電子書

bull Hands-on Nodejs e Nodejs introduction and API reference May 2011 Pe-dro Teixeira LeanPub

bull e Node Beginner Book Manuel Kiessling

bull Mastering Nodejs visionmedia

112 Nodejs影音教學

bull e Node Sessions e Best of OSCON 2011 August 2011 OrsquoReilly Media(Video)

bull Tom Hughes-Croucher on Node March 2011 OrsquoReilly Media (Video)

113 Nodejs教學網站

bull Node Tuts

114 Nodejs課程

bull Introduction To Nodejs Van Nguyen CodeLesson

84 11 參考資源

Page 78: Nodejs Wiki

Nodejs中文電子書

有這兩個就足以變成 NodeJS的模組了在開始 Coding之前先來設定一下 packagejson相依性

rdquonamerdquo rdquohubot-plurkrdquo

rdquoversionrdquo rdquo011rdquo

rdquomainrdquo rdquoplurkrdquo

rdquodependenciesrdquo

rdquohubotrdquo rdquogt=205rdquo

rdquooauthrdquo rdquordquo

rdquocronrdquordquordquo

這邊會使用到NodeJS的 cron模組(Module)而登入 Plurk需要OAuth標準授權才行所以也需要加載 OAuth模組

注意Hubot在讀取非內建模組時會自動在前面加上 hubot-的前置

83 建立 Robot跟 API

本篇應用基本上程式碼大部分參考 Hubot - Twitter Adapter來製作主要差異只有在使用 OAuth這個模組Twitter有 Streaming可用而 Plurk則得用 Comet方式來達到即時讀取

plurkcoffee程式碼

Robot = require(rdquohubotrdquo)robot()

Adapter = require(rdquohubtordquo)adapter()

EventEmitter = require(rdquoeventsrdquo)EventEmitter

oauth = require(rdquooauthrdquo)

cronJob = require(rdquocronrdquo)CronJob

class Plurk exntends Adapter

class PlurkStreaming exnteds EventEmitter

72 8 製作一個 Hubot的 Plurk Adapter

Nodejs中文電子書

先弄個基本架構主要 Class皆參考 Twitter Adapter命名方式接著再將 Plurk這個 Class增加幾個Method基本上只要有 run send reply就夠了而 run用來做初始化的部分

class Plurk entends Adapter

send (plurk id strings⋯) -gt

reply (plurk id strings⋯) -gt

run -gt

看起來有點東西接著來處理主要的 Plurk API結合部分

class PlurkStreaming extends EventEmitter

consuctor (options) -gt

plurk (callback) -gt

觀察河道

getChannel -gt

取得 Comet 網址

reply (plurk id message) -gt

回噗

acceptFriends -gt

接受好友

get (path callback) -gt

GET 請求

post (path body callback)-gt

POST 請求(其實是裝飾)

request (method path body callback)-gt

主要的 OAuth 請求

comet (server callback)-gt

噗浪的 Comet 傳回是 JavaScript Callback 要另外處理後才會變成 JSON

然後把注意力集中到 constructor上先把建構子弄好

constructor (options) -gt

super()

if optionskey and optionssecret and optionstoken and optionstoken secret

key = optionskey

secret = optionssecret

token = optionstoken

token secret = optionstoken secret

83 建立 Robot跟 API 73

Nodejs中文電子書

建立 OAuth 連接

consumer = new oauthOAuth(

rdquohttpwwwplurkcomOAuthrequest tokenrdquo

rdquohttpwwwplurkcomOAuthaccess tokenrdquo

key

secret

rdquo10rdquo

rdquohttpwwwplurkcomOAuthauthorizerdquo

rdquoHMAC-SHA1rdquo

)

domain = rdquowwwplurkcomrdquo

初始化取得 Comet 網址

do getChannel

else

throw new Error(rdquo 參數不足需要 Key Secret Token Token Secretrdquo)

接著來處理 request這個 method

request (method path body callback) -gt

記錄一下這次的 Request

consolelog(rdquohttpdomainpathrdquo)

Callback 這邊先不丟進去要用另一種方式處理

request = consumerget(rdquohttpdomainpathrdquo token token secret null)

requeston rdquoresponserdquo (res) -gt

reson rdquodatardquo (chunk) -gt

parseResponse(chunk+rsquorsquo callback)

reson rdquoendrdquo (data) -gt

consolelog rdquoEnd Request pathrdquo

reson rdquoerrorrdquo (data) -gt

consolelog rdquoError rdquo + data

requestend()

處理資料

parseResponse = (data callback) -gt

if datalength gt 0

用 TryCatch 避免處理 JSON 出錯導致整個中斷

try

callback null JSONparse(data)

catch err

74 8 製作一個 Hubot的 Plurk Adapter

Nodejs中文電子書

consolelog(rdquoError Parse JSONrdquo + data err)

繼續執行

callback null data ||

大致上就是這樣上面程式的架構已經將整個 Hubot Plurk Adapter完成因為在測試時竟然因為噗浪 Lag而沒讀到完整的 Comet資料然後造成程式異常為了避免這個問題發生因此需要加上為了完美呈現需要再

加上 Comet的處理所以要使用到 EventEmitter的功能

comet (server callback) -gt

在 Callback 裡面會找不到自身所以設定區域變數

self =

記錄一下這次的 Request

consolelog(rdquo[Comet] serverrdquo)

Callback 這邊先不丟進去要用另一種方式處理

request = consumerget(rdquohttpdomainpathrdquo token token secret null)

requeston rdquoresponserdquo (res) -gt

reson rdquodatardquo (chunk) -gt

parseResponse(chunk+rsquorsquo callback)

reson rdquoendrdquo (data) -gt

consolelog rdquoEnd Request pathrdquo

請求結束發出事件通知可以進行下一次請求

selfemit rdquonextPlurkrdquo

reson rdquoerrorrdquo (data) -gt

consolelog rdquoError rdquo + data

requestend()

處理資料

parseResponse = (data callback) -gt

if datalength gt 0

用 trycatch 避免失敗中斷

try

去掉 JavaScript 的 Callback

data = datamatch(CometChannelscriptCallback((+))s)

jsonData = rdquordquo

if data

83 建立 Robot跟 API 75

Nodejs中文電子書

jsonData = JSONparse(data[1])

else

如果沒有任何 Match 嘗試直接 parse

jsonData = JSONparse(data)

catch err

consolelog(rdquo[Comet] Errorrdquo data err)

用 TryCatch 避免處理 JSON 出錯導致整個中斷

try

只傳入 json 的 data 部分

callback null jsonDatadata

catch err

consolelog(rdquo[Comet]Error Parse JSONrdquo + data err)

繼續執行

callback null data ||

後面的 get跟 post就簡單多了

get (path callback) -gt

request(rdquoGETrdquo path null callback)

post (path body callback) -gt

request(rdquoPOSTrdquo path body callback)

接著處理取的 Comet網址的 getChannel

getChannel -gt

self =

get rdquoAPPRealtimegetUserChannelrdquo (error data) -gt

if error

檢查是否有 comet server

if datacomet server

selfchannel = datacomet server

如果沒有 Channel Ready 就嘗試連接會失敗

selfemit(rsquochannel readyrsquo)

那麼先來處理 Plurk Adaper好處理的部份

send (plruk id strings⋯)-gt

跟 Reply 一樣直接交給 reply 做

reply plurk id strings⋯

76 8 製作一個 Hubot的 Plurk Adapter

Nodejs中文電子書

reply (plurk id strings⋯) -gt

stringsforEach (message) =gt

botreply(plruk id message)

接著把 run處理好就可以上線運作摟

run -gt

self =

options =

key processenvHUBOT PLURK KEY

secret processenvHUBOT PLURK SECRET

token processenvHUBOT PLURK TOKEN

token secret processenvHUBOT PLURK TOKEN SECRET

創建剛剛的 API

bot = new PlurkStreaming(options)

依照 Twitter 的 new RobotTextMessage 會沒有反應所以參考 hubot-minecraft 的方式

r = robotconstructor

處理噗浪河道訊息

doPlurk = (data)-gt

檢查是否為回噗

if dataresponse

datacontent raw = dataresponsecontent raw

datauser id = dataresponseuser id

確定有噗浪 ID 跟訊息

if dataplurk id and datacontent raw

selfreceive new rTextMessage(dataplurk id datacontent raw)

取得 Comet Server 完成開始第一次 Comet 連接

boton rdquochannel readyrdquo () -gt

botplurk selfdoPlurk

上一次 Comet 完成繼續 Polling

boton rdquonextPlurkrdquo ()-gt

botplurk selfdoPlurk

定時接受好友邀請

do botacceptFriends

bot = bot

83 建立 Robot跟 API 77

Nodejs中文電子書

終於完成 AdapterHubot專案裡面的 scripts資料夾內是互動部分不需要像 Adapter如此大費周章處理只新增檔案並且設計好對白之後就會回噗了我開發用的機器人在此大家可以去跟他玩玩[httpplurkcomelct9620 bot](httpplurkcomelct9620 bot)

84 原始資料提供

bull [製 作 一 個 Hubot 的 噗 浪 Adapter](http revo-skillfrosttw blog20120318create-a-hubot-plurk-adapter)

78 8 製作一個 Hubot的 Plurk Adapter

9

Nodejs好用工具介紹

91 logio

Real-time log monitoring in your browser

httplogioorg

nodejs (gt=0412 lt=0611)

npm config set unsafe-perm true

npm install -g --prefix=usrlocal logio

logio server start

httplocalhost8998

79

Nodejs中文電子書

80 9 Nodejs好用工具介紹

10

精選文章

101 Express

bull 使用 NodeJS + Express從 GETPOST Request取值 [邀稿中]

bull link2 [邀稿中]

bull link3 [邀稿中]

81

Nodejs中文電子書

82 10 精選文章

11

參考資源

111 Nodejs書籍

bull Programming Nodejs April 2012 (est) Travis Swicegood Pragmatic Bookshelf

bull e Little Book on CoffeeScript January 2012 Alex MacCaw OrsquoReilly Media

bull Node for Front-End Developers January 2012 Garann Means OrsquoReilly Media

bull Building Hypermedia APIs with HTML5 and Node November 2011 MikeAmundsen OrsquoReilly Media

bull Node Web Development August 2011 David Herron PacktPub

bull CoffeeScript Accelerated JavaScript Development July 2011 Trevor BurnhamPragmatic Bookshelf

bull Getting Started with GEO CouchDB and Nodejs July 2011 Mick omp-son OrsquoReilly Media

bull What Is Node July 2011 Brett McLaughlin OrsquoReilly Media

bull Node Up and Running May 2011 Tom Hughes-Croucher Mike WilsonOrsquoReilly Media

83

Nodejs中文電子書

bull Hands-on Nodejs e Nodejs introduction and API reference May 2011 Pe-dro Teixeira LeanPub

bull e Node Beginner Book Manuel Kiessling

bull Mastering Nodejs visionmedia

112 Nodejs影音教學

bull e Node Sessions e Best of OSCON 2011 August 2011 OrsquoReilly Media(Video)

bull Tom Hughes-Croucher on Node March 2011 OrsquoReilly Media (Video)

113 Nodejs教學網站

bull Node Tuts

114 Nodejs課程

bull Introduction To Nodejs Van Nguyen CodeLesson

84 11 參考資源

Page 79: Nodejs Wiki

Nodejs中文電子書

先弄個基本架構主要 Class皆參考 Twitter Adapter命名方式接著再將 Plurk這個 Class增加幾個Method基本上只要有 run send reply就夠了而 run用來做初始化的部分

class Plurk entends Adapter

send (plurk id strings⋯) -gt

reply (plurk id strings⋯) -gt

run -gt

看起來有點東西接著來處理主要的 Plurk API結合部分

class PlurkStreaming extends EventEmitter

consuctor (options) -gt

plurk (callback) -gt

觀察河道

getChannel -gt

取得 Comet 網址

reply (plurk id message) -gt

回噗

acceptFriends -gt

接受好友

get (path callback) -gt

GET 請求

post (path body callback)-gt

POST 請求(其實是裝飾)

request (method path body callback)-gt

主要的 OAuth 請求

comet (server callback)-gt

噗浪的 Comet 傳回是 JavaScript Callback 要另外處理後才會變成 JSON

然後把注意力集中到 constructor上先把建構子弄好

constructor (options) -gt

super()

if optionskey and optionssecret and optionstoken and optionstoken secret

key = optionskey

secret = optionssecret

token = optionstoken

token secret = optionstoken secret

83 建立 Robot跟 API 73

Nodejs中文電子書

建立 OAuth 連接

consumer = new oauthOAuth(

rdquohttpwwwplurkcomOAuthrequest tokenrdquo

rdquohttpwwwplurkcomOAuthaccess tokenrdquo

key

secret

rdquo10rdquo

rdquohttpwwwplurkcomOAuthauthorizerdquo

rdquoHMAC-SHA1rdquo

)

domain = rdquowwwplurkcomrdquo

初始化取得 Comet 網址

do getChannel

else

throw new Error(rdquo 參數不足需要 Key Secret Token Token Secretrdquo)

接著來處理 request這個 method

request (method path body callback) -gt

記錄一下這次的 Request

consolelog(rdquohttpdomainpathrdquo)

Callback 這邊先不丟進去要用另一種方式處理

request = consumerget(rdquohttpdomainpathrdquo token token secret null)

requeston rdquoresponserdquo (res) -gt

reson rdquodatardquo (chunk) -gt

parseResponse(chunk+rsquorsquo callback)

reson rdquoendrdquo (data) -gt

consolelog rdquoEnd Request pathrdquo

reson rdquoerrorrdquo (data) -gt

consolelog rdquoError rdquo + data

requestend()

處理資料

parseResponse = (data callback) -gt

if datalength gt 0

用 TryCatch 避免處理 JSON 出錯導致整個中斷

try

callback null JSONparse(data)

catch err

74 8 製作一個 Hubot的 Plurk Adapter

Nodejs中文電子書

consolelog(rdquoError Parse JSONrdquo + data err)

繼續執行

callback null data ||

大致上就是這樣上面程式的架構已經將整個 Hubot Plurk Adapter完成因為在測試時竟然因為噗浪 Lag而沒讀到完整的 Comet資料然後造成程式異常為了避免這個問題發生因此需要加上為了完美呈現需要再

加上 Comet的處理所以要使用到 EventEmitter的功能

comet (server callback) -gt

在 Callback 裡面會找不到自身所以設定區域變數

self =

記錄一下這次的 Request

consolelog(rdquo[Comet] serverrdquo)

Callback 這邊先不丟進去要用另一種方式處理

request = consumerget(rdquohttpdomainpathrdquo token token secret null)

requeston rdquoresponserdquo (res) -gt

reson rdquodatardquo (chunk) -gt

parseResponse(chunk+rsquorsquo callback)

reson rdquoendrdquo (data) -gt

consolelog rdquoEnd Request pathrdquo

請求結束發出事件通知可以進行下一次請求

selfemit rdquonextPlurkrdquo

reson rdquoerrorrdquo (data) -gt

consolelog rdquoError rdquo + data

requestend()

處理資料

parseResponse = (data callback) -gt

if datalength gt 0

用 trycatch 避免失敗中斷

try

去掉 JavaScript 的 Callback

data = datamatch(CometChannelscriptCallback((+))s)

jsonData = rdquordquo

if data

83 建立 Robot跟 API 75

Nodejs中文電子書

jsonData = JSONparse(data[1])

else

如果沒有任何 Match 嘗試直接 parse

jsonData = JSONparse(data)

catch err

consolelog(rdquo[Comet] Errorrdquo data err)

用 TryCatch 避免處理 JSON 出錯導致整個中斷

try

只傳入 json 的 data 部分

callback null jsonDatadata

catch err

consolelog(rdquo[Comet]Error Parse JSONrdquo + data err)

繼續執行

callback null data ||

後面的 get跟 post就簡單多了

get (path callback) -gt

request(rdquoGETrdquo path null callback)

post (path body callback) -gt

request(rdquoPOSTrdquo path body callback)

接著處理取的 Comet網址的 getChannel

getChannel -gt

self =

get rdquoAPPRealtimegetUserChannelrdquo (error data) -gt

if error

檢查是否有 comet server

if datacomet server

selfchannel = datacomet server

如果沒有 Channel Ready 就嘗試連接會失敗

selfemit(rsquochannel readyrsquo)

那麼先來處理 Plurk Adaper好處理的部份

send (plruk id strings⋯)-gt

跟 Reply 一樣直接交給 reply 做

reply plurk id strings⋯

76 8 製作一個 Hubot的 Plurk Adapter

Nodejs中文電子書

reply (plurk id strings⋯) -gt

stringsforEach (message) =gt

botreply(plruk id message)

接著把 run處理好就可以上線運作摟

run -gt

self =

options =

key processenvHUBOT PLURK KEY

secret processenvHUBOT PLURK SECRET

token processenvHUBOT PLURK TOKEN

token secret processenvHUBOT PLURK TOKEN SECRET

創建剛剛的 API

bot = new PlurkStreaming(options)

依照 Twitter 的 new RobotTextMessage 會沒有反應所以參考 hubot-minecraft 的方式

r = robotconstructor

處理噗浪河道訊息

doPlurk = (data)-gt

檢查是否為回噗

if dataresponse

datacontent raw = dataresponsecontent raw

datauser id = dataresponseuser id

確定有噗浪 ID 跟訊息

if dataplurk id and datacontent raw

selfreceive new rTextMessage(dataplurk id datacontent raw)

取得 Comet Server 完成開始第一次 Comet 連接

boton rdquochannel readyrdquo () -gt

botplurk selfdoPlurk

上一次 Comet 完成繼續 Polling

boton rdquonextPlurkrdquo ()-gt

botplurk selfdoPlurk

定時接受好友邀請

do botacceptFriends

bot = bot

83 建立 Robot跟 API 77

Nodejs中文電子書

終於完成 AdapterHubot專案裡面的 scripts資料夾內是互動部分不需要像 Adapter如此大費周章處理只新增檔案並且設計好對白之後就會回噗了我開發用的機器人在此大家可以去跟他玩玩[httpplurkcomelct9620 bot](httpplurkcomelct9620 bot)

84 原始資料提供

bull [製 作 一 個 Hubot 的 噗 浪 Adapter](http revo-skillfrosttw blog20120318create-a-hubot-plurk-adapter)

78 8 製作一個 Hubot的 Plurk Adapter

9

Nodejs好用工具介紹

91 logio

Real-time log monitoring in your browser

httplogioorg

nodejs (gt=0412 lt=0611)

npm config set unsafe-perm true

npm install -g --prefix=usrlocal logio

logio server start

httplocalhost8998

79

Nodejs中文電子書

80 9 Nodejs好用工具介紹

10

精選文章

101 Express

bull 使用 NodeJS + Express從 GETPOST Request取值 [邀稿中]

bull link2 [邀稿中]

bull link3 [邀稿中]

81

Nodejs中文電子書

82 10 精選文章

11

參考資源

111 Nodejs書籍

bull Programming Nodejs April 2012 (est) Travis Swicegood Pragmatic Bookshelf

bull e Little Book on CoffeeScript January 2012 Alex MacCaw OrsquoReilly Media

bull Node for Front-End Developers January 2012 Garann Means OrsquoReilly Media

bull Building Hypermedia APIs with HTML5 and Node November 2011 MikeAmundsen OrsquoReilly Media

bull Node Web Development August 2011 David Herron PacktPub

bull CoffeeScript Accelerated JavaScript Development July 2011 Trevor BurnhamPragmatic Bookshelf

bull Getting Started with GEO CouchDB and Nodejs July 2011 Mick omp-son OrsquoReilly Media

bull What Is Node July 2011 Brett McLaughlin OrsquoReilly Media

bull Node Up and Running May 2011 Tom Hughes-Croucher Mike WilsonOrsquoReilly Media

83

Nodejs中文電子書

bull Hands-on Nodejs e Nodejs introduction and API reference May 2011 Pe-dro Teixeira LeanPub

bull e Node Beginner Book Manuel Kiessling

bull Mastering Nodejs visionmedia

112 Nodejs影音教學

bull e Node Sessions e Best of OSCON 2011 August 2011 OrsquoReilly Media(Video)

bull Tom Hughes-Croucher on Node March 2011 OrsquoReilly Media (Video)

113 Nodejs教學網站

bull Node Tuts

114 Nodejs課程

bull Introduction To Nodejs Van Nguyen CodeLesson

84 11 參考資源

Page 80: Nodejs Wiki

Nodejs中文電子書

建立 OAuth 連接

consumer = new oauthOAuth(

rdquohttpwwwplurkcomOAuthrequest tokenrdquo

rdquohttpwwwplurkcomOAuthaccess tokenrdquo

key

secret

rdquo10rdquo

rdquohttpwwwplurkcomOAuthauthorizerdquo

rdquoHMAC-SHA1rdquo

)

domain = rdquowwwplurkcomrdquo

初始化取得 Comet 網址

do getChannel

else

throw new Error(rdquo 參數不足需要 Key Secret Token Token Secretrdquo)

接著來處理 request這個 method

request (method path body callback) -gt

記錄一下這次的 Request

consolelog(rdquohttpdomainpathrdquo)

Callback 這邊先不丟進去要用另一種方式處理

request = consumerget(rdquohttpdomainpathrdquo token token secret null)

requeston rdquoresponserdquo (res) -gt

reson rdquodatardquo (chunk) -gt

parseResponse(chunk+rsquorsquo callback)

reson rdquoendrdquo (data) -gt

consolelog rdquoEnd Request pathrdquo

reson rdquoerrorrdquo (data) -gt

consolelog rdquoError rdquo + data

requestend()

處理資料

parseResponse = (data callback) -gt

if datalength gt 0

用 TryCatch 避免處理 JSON 出錯導致整個中斷

try

callback null JSONparse(data)

catch err

74 8 製作一個 Hubot的 Plurk Adapter

Nodejs中文電子書

consolelog(rdquoError Parse JSONrdquo + data err)

繼續執行

callback null data ||

大致上就是這樣上面程式的架構已經將整個 Hubot Plurk Adapter完成因為在測試時竟然因為噗浪 Lag而沒讀到完整的 Comet資料然後造成程式異常為了避免這個問題發生因此需要加上為了完美呈現需要再

加上 Comet的處理所以要使用到 EventEmitter的功能

comet (server callback) -gt

在 Callback 裡面會找不到自身所以設定區域變數

self =

記錄一下這次的 Request

consolelog(rdquo[Comet] serverrdquo)

Callback 這邊先不丟進去要用另一種方式處理

request = consumerget(rdquohttpdomainpathrdquo token token secret null)

requeston rdquoresponserdquo (res) -gt

reson rdquodatardquo (chunk) -gt

parseResponse(chunk+rsquorsquo callback)

reson rdquoendrdquo (data) -gt

consolelog rdquoEnd Request pathrdquo

請求結束發出事件通知可以進行下一次請求

selfemit rdquonextPlurkrdquo

reson rdquoerrorrdquo (data) -gt

consolelog rdquoError rdquo + data

requestend()

處理資料

parseResponse = (data callback) -gt

if datalength gt 0

用 trycatch 避免失敗中斷

try

去掉 JavaScript 的 Callback

data = datamatch(CometChannelscriptCallback((+))s)

jsonData = rdquordquo

if data

83 建立 Robot跟 API 75

Nodejs中文電子書

jsonData = JSONparse(data[1])

else

如果沒有任何 Match 嘗試直接 parse

jsonData = JSONparse(data)

catch err

consolelog(rdquo[Comet] Errorrdquo data err)

用 TryCatch 避免處理 JSON 出錯導致整個中斷

try

只傳入 json 的 data 部分

callback null jsonDatadata

catch err

consolelog(rdquo[Comet]Error Parse JSONrdquo + data err)

繼續執行

callback null data ||

後面的 get跟 post就簡單多了

get (path callback) -gt

request(rdquoGETrdquo path null callback)

post (path body callback) -gt

request(rdquoPOSTrdquo path body callback)

接著處理取的 Comet網址的 getChannel

getChannel -gt

self =

get rdquoAPPRealtimegetUserChannelrdquo (error data) -gt

if error

檢查是否有 comet server

if datacomet server

selfchannel = datacomet server

如果沒有 Channel Ready 就嘗試連接會失敗

selfemit(rsquochannel readyrsquo)

那麼先來處理 Plurk Adaper好處理的部份

send (plruk id strings⋯)-gt

跟 Reply 一樣直接交給 reply 做

reply plurk id strings⋯

76 8 製作一個 Hubot的 Plurk Adapter

Nodejs中文電子書

reply (plurk id strings⋯) -gt

stringsforEach (message) =gt

botreply(plruk id message)

接著把 run處理好就可以上線運作摟

run -gt

self =

options =

key processenvHUBOT PLURK KEY

secret processenvHUBOT PLURK SECRET

token processenvHUBOT PLURK TOKEN

token secret processenvHUBOT PLURK TOKEN SECRET

創建剛剛的 API

bot = new PlurkStreaming(options)

依照 Twitter 的 new RobotTextMessage 會沒有反應所以參考 hubot-minecraft 的方式

r = robotconstructor

處理噗浪河道訊息

doPlurk = (data)-gt

檢查是否為回噗

if dataresponse

datacontent raw = dataresponsecontent raw

datauser id = dataresponseuser id

確定有噗浪 ID 跟訊息

if dataplurk id and datacontent raw

selfreceive new rTextMessage(dataplurk id datacontent raw)

取得 Comet Server 完成開始第一次 Comet 連接

boton rdquochannel readyrdquo () -gt

botplurk selfdoPlurk

上一次 Comet 完成繼續 Polling

boton rdquonextPlurkrdquo ()-gt

botplurk selfdoPlurk

定時接受好友邀請

do botacceptFriends

bot = bot

83 建立 Robot跟 API 77

Nodejs中文電子書

終於完成 AdapterHubot專案裡面的 scripts資料夾內是互動部分不需要像 Adapter如此大費周章處理只新增檔案並且設計好對白之後就會回噗了我開發用的機器人在此大家可以去跟他玩玩[httpplurkcomelct9620 bot](httpplurkcomelct9620 bot)

84 原始資料提供

bull [製 作 一 個 Hubot 的 噗 浪 Adapter](http revo-skillfrosttw blog20120318create-a-hubot-plurk-adapter)

78 8 製作一個 Hubot的 Plurk Adapter

9

Nodejs好用工具介紹

91 logio

Real-time log monitoring in your browser

httplogioorg

nodejs (gt=0412 lt=0611)

npm config set unsafe-perm true

npm install -g --prefix=usrlocal logio

logio server start

httplocalhost8998

79

Nodejs中文電子書

80 9 Nodejs好用工具介紹

10

精選文章

101 Express

bull 使用 NodeJS + Express從 GETPOST Request取值 [邀稿中]

bull link2 [邀稿中]

bull link3 [邀稿中]

81

Nodejs中文電子書

82 10 精選文章

11

參考資源

111 Nodejs書籍

bull Programming Nodejs April 2012 (est) Travis Swicegood Pragmatic Bookshelf

bull e Little Book on CoffeeScript January 2012 Alex MacCaw OrsquoReilly Media

bull Node for Front-End Developers January 2012 Garann Means OrsquoReilly Media

bull Building Hypermedia APIs with HTML5 and Node November 2011 MikeAmundsen OrsquoReilly Media

bull Node Web Development August 2011 David Herron PacktPub

bull CoffeeScript Accelerated JavaScript Development July 2011 Trevor BurnhamPragmatic Bookshelf

bull Getting Started with GEO CouchDB and Nodejs July 2011 Mick omp-son OrsquoReilly Media

bull What Is Node July 2011 Brett McLaughlin OrsquoReilly Media

bull Node Up and Running May 2011 Tom Hughes-Croucher Mike WilsonOrsquoReilly Media

83

Nodejs中文電子書

bull Hands-on Nodejs e Nodejs introduction and API reference May 2011 Pe-dro Teixeira LeanPub

bull e Node Beginner Book Manuel Kiessling

bull Mastering Nodejs visionmedia

112 Nodejs影音教學

bull e Node Sessions e Best of OSCON 2011 August 2011 OrsquoReilly Media(Video)

bull Tom Hughes-Croucher on Node March 2011 OrsquoReilly Media (Video)

113 Nodejs教學網站

bull Node Tuts

114 Nodejs課程

bull Introduction To Nodejs Van Nguyen CodeLesson

84 11 參考資源

Page 81: Nodejs Wiki

Nodejs中文電子書

consolelog(rdquoError Parse JSONrdquo + data err)

繼續執行

callback null data ||

大致上就是這樣上面程式的架構已經將整個 Hubot Plurk Adapter完成因為在測試時竟然因為噗浪 Lag而沒讀到完整的 Comet資料然後造成程式異常為了避免這個問題發生因此需要加上為了完美呈現需要再

加上 Comet的處理所以要使用到 EventEmitter的功能

comet (server callback) -gt

在 Callback 裡面會找不到自身所以設定區域變數

self =

記錄一下這次的 Request

consolelog(rdquo[Comet] serverrdquo)

Callback 這邊先不丟進去要用另一種方式處理

request = consumerget(rdquohttpdomainpathrdquo token token secret null)

requeston rdquoresponserdquo (res) -gt

reson rdquodatardquo (chunk) -gt

parseResponse(chunk+rsquorsquo callback)

reson rdquoendrdquo (data) -gt

consolelog rdquoEnd Request pathrdquo

請求結束發出事件通知可以進行下一次請求

selfemit rdquonextPlurkrdquo

reson rdquoerrorrdquo (data) -gt

consolelog rdquoError rdquo + data

requestend()

處理資料

parseResponse = (data callback) -gt

if datalength gt 0

用 trycatch 避免失敗中斷

try

去掉 JavaScript 的 Callback

data = datamatch(CometChannelscriptCallback((+))s)

jsonData = rdquordquo

if data

83 建立 Robot跟 API 75

Nodejs中文電子書

jsonData = JSONparse(data[1])

else

如果沒有任何 Match 嘗試直接 parse

jsonData = JSONparse(data)

catch err

consolelog(rdquo[Comet] Errorrdquo data err)

用 TryCatch 避免處理 JSON 出錯導致整個中斷

try

只傳入 json 的 data 部分

callback null jsonDatadata

catch err

consolelog(rdquo[Comet]Error Parse JSONrdquo + data err)

繼續執行

callback null data ||

後面的 get跟 post就簡單多了

get (path callback) -gt

request(rdquoGETrdquo path null callback)

post (path body callback) -gt

request(rdquoPOSTrdquo path body callback)

接著處理取的 Comet網址的 getChannel

getChannel -gt

self =

get rdquoAPPRealtimegetUserChannelrdquo (error data) -gt

if error

檢查是否有 comet server

if datacomet server

selfchannel = datacomet server

如果沒有 Channel Ready 就嘗試連接會失敗

selfemit(rsquochannel readyrsquo)

那麼先來處理 Plurk Adaper好處理的部份

send (plruk id strings⋯)-gt

跟 Reply 一樣直接交給 reply 做

reply plurk id strings⋯

76 8 製作一個 Hubot的 Plurk Adapter

Nodejs中文電子書

reply (plurk id strings⋯) -gt

stringsforEach (message) =gt

botreply(plruk id message)

接著把 run處理好就可以上線運作摟

run -gt

self =

options =

key processenvHUBOT PLURK KEY

secret processenvHUBOT PLURK SECRET

token processenvHUBOT PLURK TOKEN

token secret processenvHUBOT PLURK TOKEN SECRET

創建剛剛的 API

bot = new PlurkStreaming(options)

依照 Twitter 的 new RobotTextMessage 會沒有反應所以參考 hubot-minecraft 的方式

r = robotconstructor

處理噗浪河道訊息

doPlurk = (data)-gt

檢查是否為回噗

if dataresponse

datacontent raw = dataresponsecontent raw

datauser id = dataresponseuser id

確定有噗浪 ID 跟訊息

if dataplurk id and datacontent raw

selfreceive new rTextMessage(dataplurk id datacontent raw)

取得 Comet Server 完成開始第一次 Comet 連接

boton rdquochannel readyrdquo () -gt

botplurk selfdoPlurk

上一次 Comet 完成繼續 Polling

boton rdquonextPlurkrdquo ()-gt

botplurk selfdoPlurk

定時接受好友邀請

do botacceptFriends

bot = bot

83 建立 Robot跟 API 77

Nodejs中文電子書

終於完成 AdapterHubot專案裡面的 scripts資料夾內是互動部分不需要像 Adapter如此大費周章處理只新增檔案並且設計好對白之後就會回噗了我開發用的機器人在此大家可以去跟他玩玩[httpplurkcomelct9620 bot](httpplurkcomelct9620 bot)

84 原始資料提供

bull [製 作 一 個 Hubot 的 噗 浪 Adapter](http revo-skillfrosttw blog20120318create-a-hubot-plurk-adapter)

78 8 製作一個 Hubot的 Plurk Adapter

9

Nodejs好用工具介紹

91 logio

Real-time log monitoring in your browser

httplogioorg

nodejs (gt=0412 lt=0611)

npm config set unsafe-perm true

npm install -g --prefix=usrlocal logio

logio server start

httplocalhost8998

79

Nodejs中文電子書

80 9 Nodejs好用工具介紹

10

精選文章

101 Express

bull 使用 NodeJS + Express從 GETPOST Request取值 [邀稿中]

bull link2 [邀稿中]

bull link3 [邀稿中]

81

Nodejs中文電子書

82 10 精選文章

11

參考資源

111 Nodejs書籍

bull Programming Nodejs April 2012 (est) Travis Swicegood Pragmatic Bookshelf

bull e Little Book on CoffeeScript January 2012 Alex MacCaw OrsquoReilly Media

bull Node for Front-End Developers January 2012 Garann Means OrsquoReilly Media

bull Building Hypermedia APIs with HTML5 and Node November 2011 MikeAmundsen OrsquoReilly Media

bull Node Web Development August 2011 David Herron PacktPub

bull CoffeeScript Accelerated JavaScript Development July 2011 Trevor BurnhamPragmatic Bookshelf

bull Getting Started with GEO CouchDB and Nodejs July 2011 Mick omp-son OrsquoReilly Media

bull What Is Node July 2011 Brett McLaughlin OrsquoReilly Media

bull Node Up and Running May 2011 Tom Hughes-Croucher Mike WilsonOrsquoReilly Media

83

Nodejs中文電子書

bull Hands-on Nodejs e Nodejs introduction and API reference May 2011 Pe-dro Teixeira LeanPub

bull e Node Beginner Book Manuel Kiessling

bull Mastering Nodejs visionmedia

112 Nodejs影音教學

bull e Node Sessions e Best of OSCON 2011 August 2011 OrsquoReilly Media(Video)

bull Tom Hughes-Croucher on Node March 2011 OrsquoReilly Media (Video)

113 Nodejs教學網站

bull Node Tuts

114 Nodejs課程

bull Introduction To Nodejs Van Nguyen CodeLesson

84 11 參考資源

Page 82: Nodejs Wiki

Nodejs中文電子書

jsonData = JSONparse(data[1])

else

如果沒有任何 Match 嘗試直接 parse

jsonData = JSONparse(data)

catch err

consolelog(rdquo[Comet] Errorrdquo data err)

用 TryCatch 避免處理 JSON 出錯導致整個中斷

try

只傳入 json 的 data 部分

callback null jsonDatadata

catch err

consolelog(rdquo[Comet]Error Parse JSONrdquo + data err)

繼續執行

callback null data ||

後面的 get跟 post就簡單多了

get (path callback) -gt

request(rdquoGETrdquo path null callback)

post (path body callback) -gt

request(rdquoPOSTrdquo path body callback)

接著處理取的 Comet網址的 getChannel

getChannel -gt

self =

get rdquoAPPRealtimegetUserChannelrdquo (error data) -gt

if error

檢查是否有 comet server

if datacomet server

selfchannel = datacomet server

如果沒有 Channel Ready 就嘗試連接會失敗

selfemit(rsquochannel readyrsquo)

那麼先來處理 Plurk Adaper好處理的部份

send (plruk id strings⋯)-gt

跟 Reply 一樣直接交給 reply 做

reply plurk id strings⋯

76 8 製作一個 Hubot的 Plurk Adapter

Nodejs中文電子書

reply (plurk id strings⋯) -gt

stringsforEach (message) =gt

botreply(plruk id message)

接著把 run處理好就可以上線運作摟

run -gt

self =

options =

key processenvHUBOT PLURK KEY

secret processenvHUBOT PLURK SECRET

token processenvHUBOT PLURK TOKEN

token secret processenvHUBOT PLURK TOKEN SECRET

創建剛剛的 API

bot = new PlurkStreaming(options)

依照 Twitter 的 new RobotTextMessage 會沒有反應所以參考 hubot-minecraft 的方式

r = robotconstructor

處理噗浪河道訊息

doPlurk = (data)-gt

檢查是否為回噗

if dataresponse

datacontent raw = dataresponsecontent raw

datauser id = dataresponseuser id

確定有噗浪 ID 跟訊息

if dataplurk id and datacontent raw

selfreceive new rTextMessage(dataplurk id datacontent raw)

取得 Comet Server 完成開始第一次 Comet 連接

boton rdquochannel readyrdquo () -gt

botplurk selfdoPlurk

上一次 Comet 完成繼續 Polling

boton rdquonextPlurkrdquo ()-gt

botplurk selfdoPlurk

定時接受好友邀請

do botacceptFriends

bot = bot

83 建立 Robot跟 API 77

Nodejs中文電子書

終於完成 AdapterHubot專案裡面的 scripts資料夾內是互動部分不需要像 Adapter如此大費周章處理只新增檔案並且設計好對白之後就會回噗了我開發用的機器人在此大家可以去跟他玩玩[httpplurkcomelct9620 bot](httpplurkcomelct9620 bot)

84 原始資料提供

bull [製 作 一 個 Hubot 的 噗 浪 Adapter](http revo-skillfrosttw blog20120318create-a-hubot-plurk-adapter)

78 8 製作一個 Hubot的 Plurk Adapter

9

Nodejs好用工具介紹

91 logio

Real-time log monitoring in your browser

httplogioorg

nodejs (gt=0412 lt=0611)

npm config set unsafe-perm true

npm install -g --prefix=usrlocal logio

logio server start

httplocalhost8998

79

Nodejs中文電子書

80 9 Nodejs好用工具介紹

10

精選文章

101 Express

bull 使用 NodeJS + Express從 GETPOST Request取值 [邀稿中]

bull link2 [邀稿中]

bull link3 [邀稿中]

81

Nodejs中文電子書

82 10 精選文章

11

參考資源

111 Nodejs書籍

bull Programming Nodejs April 2012 (est) Travis Swicegood Pragmatic Bookshelf

bull e Little Book on CoffeeScript January 2012 Alex MacCaw OrsquoReilly Media

bull Node for Front-End Developers January 2012 Garann Means OrsquoReilly Media

bull Building Hypermedia APIs with HTML5 and Node November 2011 MikeAmundsen OrsquoReilly Media

bull Node Web Development August 2011 David Herron PacktPub

bull CoffeeScript Accelerated JavaScript Development July 2011 Trevor BurnhamPragmatic Bookshelf

bull Getting Started with GEO CouchDB and Nodejs July 2011 Mick omp-son OrsquoReilly Media

bull What Is Node July 2011 Brett McLaughlin OrsquoReilly Media

bull Node Up and Running May 2011 Tom Hughes-Croucher Mike WilsonOrsquoReilly Media

83

Nodejs中文電子書

bull Hands-on Nodejs e Nodejs introduction and API reference May 2011 Pe-dro Teixeira LeanPub

bull e Node Beginner Book Manuel Kiessling

bull Mastering Nodejs visionmedia

112 Nodejs影音教學

bull e Node Sessions e Best of OSCON 2011 August 2011 OrsquoReilly Media(Video)

bull Tom Hughes-Croucher on Node March 2011 OrsquoReilly Media (Video)

113 Nodejs教學網站

bull Node Tuts

114 Nodejs課程

bull Introduction To Nodejs Van Nguyen CodeLesson

84 11 參考資源

Page 83: Nodejs Wiki

Nodejs中文電子書

reply (plurk id strings⋯) -gt

stringsforEach (message) =gt

botreply(plruk id message)

接著把 run處理好就可以上線運作摟

run -gt

self =

options =

key processenvHUBOT PLURK KEY

secret processenvHUBOT PLURK SECRET

token processenvHUBOT PLURK TOKEN

token secret processenvHUBOT PLURK TOKEN SECRET

創建剛剛的 API

bot = new PlurkStreaming(options)

依照 Twitter 的 new RobotTextMessage 會沒有反應所以參考 hubot-minecraft 的方式

r = robotconstructor

處理噗浪河道訊息

doPlurk = (data)-gt

檢查是否為回噗

if dataresponse

datacontent raw = dataresponsecontent raw

datauser id = dataresponseuser id

確定有噗浪 ID 跟訊息

if dataplurk id and datacontent raw

selfreceive new rTextMessage(dataplurk id datacontent raw)

取得 Comet Server 完成開始第一次 Comet 連接

boton rdquochannel readyrdquo () -gt

botplurk selfdoPlurk

上一次 Comet 完成繼續 Polling

boton rdquonextPlurkrdquo ()-gt

botplurk selfdoPlurk

定時接受好友邀請

do botacceptFriends

bot = bot

83 建立 Robot跟 API 77

Nodejs中文電子書

終於完成 AdapterHubot專案裡面的 scripts資料夾內是互動部分不需要像 Adapter如此大費周章處理只新增檔案並且設計好對白之後就會回噗了我開發用的機器人在此大家可以去跟他玩玩[httpplurkcomelct9620 bot](httpplurkcomelct9620 bot)

84 原始資料提供

bull [製 作 一 個 Hubot 的 噗 浪 Adapter](http revo-skillfrosttw blog20120318create-a-hubot-plurk-adapter)

78 8 製作一個 Hubot的 Plurk Adapter

9

Nodejs好用工具介紹

91 logio

Real-time log monitoring in your browser

httplogioorg

nodejs (gt=0412 lt=0611)

npm config set unsafe-perm true

npm install -g --prefix=usrlocal logio

logio server start

httplocalhost8998

79

Nodejs中文電子書

80 9 Nodejs好用工具介紹

10

精選文章

101 Express

bull 使用 NodeJS + Express從 GETPOST Request取值 [邀稿中]

bull link2 [邀稿中]

bull link3 [邀稿中]

81

Nodejs中文電子書

82 10 精選文章

11

參考資源

111 Nodejs書籍

bull Programming Nodejs April 2012 (est) Travis Swicegood Pragmatic Bookshelf

bull e Little Book on CoffeeScript January 2012 Alex MacCaw OrsquoReilly Media

bull Node for Front-End Developers January 2012 Garann Means OrsquoReilly Media

bull Building Hypermedia APIs with HTML5 and Node November 2011 MikeAmundsen OrsquoReilly Media

bull Node Web Development August 2011 David Herron PacktPub

bull CoffeeScript Accelerated JavaScript Development July 2011 Trevor BurnhamPragmatic Bookshelf

bull Getting Started with GEO CouchDB and Nodejs July 2011 Mick omp-son OrsquoReilly Media

bull What Is Node July 2011 Brett McLaughlin OrsquoReilly Media

bull Node Up and Running May 2011 Tom Hughes-Croucher Mike WilsonOrsquoReilly Media

83

Nodejs中文電子書

bull Hands-on Nodejs e Nodejs introduction and API reference May 2011 Pe-dro Teixeira LeanPub

bull e Node Beginner Book Manuel Kiessling

bull Mastering Nodejs visionmedia

112 Nodejs影音教學

bull e Node Sessions e Best of OSCON 2011 August 2011 OrsquoReilly Media(Video)

bull Tom Hughes-Croucher on Node March 2011 OrsquoReilly Media (Video)

113 Nodejs教學網站

bull Node Tuts

114 Nodejs課程

bull Introduction To Nodejs Van Nguyen CodeLesson

84 11 參考資源

Page 84: Nodejs Wiki

Nodejs中文電子書

終於完成 AdapterHubot專案裡面的 scripts資料夾內是互動部分不需要像 Adapter如此大費周章處理只新增檔案並且設計好對白之後就會回噗了我開發用的機器人在此大家可以去跟他玩玩[httpplurkcomelct9620 bot](httpplurkcomelct9620 bot)

84 原始資料提供

bull [製 作 一 個 Hubot 的 噗 浪 Adapter](http revo-skillfrosttw blog20120318create-a-hubot-plurk-adapter)

78 8 製作一個 Hubot的 Plurk Adapter

9

Nodejs好用工具介紹

91 logio

Real-time log monitoring in your browser

httplogioorg

nodejs (gt=0412 lt=0611)

npm config set unsafe-perm true

npm install -g --prefix=usrlocal logio

logio server start

httplocalhost8998

79

Nodejs中文電子書

80 9 Nodejs好用工具介紹

10

精選文章

101 Express

bull 使用 NodeJS + Express從 GETPOST Request取值 [邀稿中]

bull link2 [邀稿中]

bull link3 [邀稿中]

81

Nodejs中文電子書

82 10 精選文章

11

參考資源

111 Nodejs書籍

bull Programming Nodejs April 2012 (est) Travis Swicegood Pragmatic Bookshelf

bull e Little Book on CoffeeScript January 2012 Alex MacCaw OrsquoReilly Media

bull Node for Front-End Developers January 2012 Garann Means OrsquoReilly Media

bull Building Hypermedia APIs with HTML5 and Node November 2011 MikeAmundsen OrsquoReilly Media

bull Node Web Development August 2011 David Herron PacktPub

bull CoffeeScript Accelerated JavaScript Development July 2011 Trevor BurnhamPragmatic Bookshelf

bull Getting Started with GEO CouchDB and Nodejs July 2011 Mick omp-son OrsquoReilly Media

bull What Is Node July 2011 Brett McLaughlin OrsquoReilly Media

bull Node Up and Running May 2011 Tom Hughes-Croucher Mike WilsonOrsquoReilly Media

83

Nodejs中文電子書

bull Hands-on Nodejs e Nodejs introduction and API reference May 2011 Pe-dro Teixeira LeanPub

bull e Node Beginner Book Manuel Kiessling

bull Mastering Nodejs visionmedia

112 Nodejs影音教學

bull e Node Sessions e Best of OSCON 2011 August 2011 OrsquoReilly Media(Video)

bull Tom Hughes-Croucher on Node March 2011 OrsquoReilly Media (Video)

113 Nodejs教學網站

bull Node Tuts

114 Nodejs課程

bull Introduction To Nodejs Van Nguyen CodeLesson

84 11 參考資源

Page 85: Nodejs Wiki

9

Nodejs好用工具介紹

91 logio

Real-time log monitoring in your browser

httplogioorg

nodejs (gt=0412 lt=0611)

npm config set unsafe-perm true

npm install -g --prefix=usrlocal logio

logio server start

httplocalhost8998

79

Nodejs中文電子書

80 9 Nodejs好用工具介紹

10

精選文章

101 Express

bull 使用 NodeJS + Express從 GETPOST Request取值 [邀稿中]

bull link2 [邀稿中]

bull link3 [邀稿中]

81

Nodejs中文電子書

82 10 精選文章

11

參考資源

111 Nodejs書籍

bull Programming Nodejs April 2012 (est) Travis Swicegood Pragmatic Bookshelf

bull e Little Book on CoffeeScript January 2012 Alex MacCaw OrsquoReilly Media

bull Node for Front-End Developers January 2012 Garann Means OrsquoReilly Media

bull Building Hypermedia APIs with HTML5 and Node November 2011 MikeAmundsen OrsquoReilly Media

bull Node Web Development August 2011 David Herron PacktPub

bull CoffeeScript Accelerated JavaScript Development July 2011 Trevor BurnhamPragmatic Bookshelf

bull Getting Started with GEO CouchDB and Nodejs July 2011 Mick omp-son OrsquoReilly Media

bull What Is Node July 2011 Brett McLaughlin OrsquoReilly Media

bull Node Up and Running May 2011 Tom Hughes-Croucher Mike WilsonOrsquoReilly Media

83

Nodejs中文電子書

bull Hands-on Nodejs e Nodejs introduction and API reference May 2011 Pe-dro Teixeira LeanPub

bull e Node Beginner Book Manuel Kiessling

bull Mastering Nodejs visionmedia

112 Nodejs影音教學

bull e Node Sessions e Best of OSCON 2011 August 2011 OrsquoReilly Media(Video)

bull Tom Hughes-Croucher on Node March 2011 OrsquoReilly Media (Video)

113 Nodejs教學網站

bull Node Tuts

114 Nodejs課程

bull Introduction To Nodejs Van Nguyen CodeLesson

84 11 參考資源

Page 86: Nodejs Wiki

Nodejs中文電子書

80 9 Nodejs好用工具介紹

10

精選文章

101 Express

bull 使用 NodeJS + Express從 GETPOST Request取值 [邀稿中]

bull link2 [邀稿中]

bull link3 [邀稿中]

81

Nodejs中文電子書

82 10 精選文章

11

參考資源

111 Nodejs書籍

bull Programming Nodejs April 2012 (est) Travis Swicegood Pragmatic Bookshelf

bull e Little Book on CoffeeScript January 2012 Alex MacCaw OrsquoReilly Media

bull Node for Front-End Developers January 2012 Garann Means OrsquoReilly Media

bull Building Hypermedia APIs with HTML5 and Node November 2011 MikeAmundsen OrsquoReilly Media

bull Node Web Development August 2011 David Herron PacktPub

bull CoffeeScript Accelerated JavaScript Development July 2011 Trevor BurnhamPragmatic Bookshelf

bull Getting Started with GEO CouchDB and Nodejs July 2011 Mick omp-son OrsquoReilly Media

bull What Is Node July 2011 Brett McLaughlin OrsquoReilly Media

bull Node Up and Running May 2011 Tom Hughes-Croucher Mike WilsonOrsquoReilly Media

83

Nodejs中文電子書

bull Hands-on Nodejs e Nodejs introduction and API reference May 2011 Pe-dro Teixeira LeanPub

bull e Node Beginner Book Manuel Kiessling

bull Mastering Nodejs visionmedia

112 Nodejs影音教學

bull e Node Sessions e Best of OSCON 2011 August 2011 OrsquoReilly Media(Video)

bull Tom Hughes-Croucher on Node March 2011 OrsquoReilly Media (Video)

113 Nodejs教學網站

bull Node Tuts

114 Nodejs課程

bull Introduction To Nodejs Van Nguyen CodeLesson

84 11 參考資源

Page 87: Nodejs Wiki

10

精選文章

101 Express

bull 使用 NodeJS + Express從 GETPOST Request取值 [邀稿中]

bull link2 [邀稿中]

bull link3 [邀稿中]

81

Nodejs中文電子書

82 10 精選文章

11

參考資源

111 Nodejs書籍

bull Programming Nodejs April 2012 (est) Travis Swicegood Pragmatic Bookshelf

bull e Little Book on CoffeeScript January 2012 Alex MacCaw OrsquoReilly Media

bull Node for Front-End Developers January 2012 Garann Means OrsquoReilly Media

bull Building Hypermedia APIs with HTML5 and Node November 2011 MikeAmundsen OrsquoReilly Media

bull Node Web Development August 2011 David Herron PacktPub

bull CoffeeScript Accelerated JavaScript Development July 2011 Trevor BurnhamPragmatic Bookshelf

bull Getting Started with GEO CouchDB and Nodejs July 2011 Mick omp-son OrsquoReilly Media

bull What Is Node July 2011 Brett McLaughlin OrsquoReilly Media

bull Node Up and Running May 2011 Tom Hughes-Croucher Mike WilsonOrsquoReilly Media

83

Nodejs中文電子書

bull Hands-on Nodejs e Nodejs introduction and API reference May 2011 Pe-dro Teixeira LeanPub

bull e Node Beginner Book Manuel Kiessling

bull Mastering Nodejs visionmedia

112 Nodejs影音教學

bull e Node Sessions e Best of OSCON 2011 August 2011 OrsquoReilly Media(Video)

bull Tom Hughes-Croucher on Node March 2011 OrsquoReilly Media (Video)

113 Nodejs教學網站

bull Node Tuts

114 Nodejs課程

bull Introduction To Nodejs Van Nguyen CodeLesson

84 11 參考資源

Page 88: Nodejs Wiki

Nodejs中文電子書

82 10 精選文章

11

參考資源

111 Nodejs書籍

bull Programming Nodejs April 2012 (est) Travis Swicegood Pragmatic Bookshelf

bull e Little Book on CoffeeScript January 2012 Alex MacCaw OrsquoReilly Media

bull Node for Front-End Developers January 2012 Garann Means OrsquoReilly Media

bull Building Hypermedia APIs with HTML5 and Node November 2011 MikeAmundsen OrsquoReilly Media

bull Node Web Development August 2011 David Herron PacktPub

bull CoffeeScript Accelerated JavaScript Development July 2011 Trevor BurnhamPragmatic Bookshelf

bull Getting Started with GEO CouchDB and Nodejs July 2011 Mick omp-son OrsquoReilly Media

bull What Is Node July 2011 Brett McLaughlin OrsquoReilly Media

bull Node Up and Running May 2011 Tom Hughes-Croucher Mike WilsonOrsquoReilly Media

83

Nodejs中文電子書

bull Hands-on Nodejs e Nodejs introduction and API reference May 2011 Pe-dro Teixeira LeanPub

bull e Node Beginner Book Manuel Kiessling

bull Mastering Nodejs visionmedia

112 Nodejs影音教學

bull e Node Sessions e Best of OSCON 2011 August 2011 OrsquoReilly Media(Video)

bull Tom Hughes-Croucher on Node March 2011 OrsquoReilly Media (Video)

113 Nodejs教學網站

bull Node Tuts

114 Nodejs課程

bull Introduction To Nodejs Van Nguyen CodeLesson

84 11 參考資源

Page 89: Nodejs Wiki

11

參考資源

111 Nodejs書籍

bull Programming Nodejs April 2012 (est) Travis Swicegood Pragmatic Bookshelf

bull e Little Book on CoffeeScript January 2012 Alex MacCaw OrsquoReilly Media

bull Node for Front-End Developers January 2012 Garann Means OrsquoReilly Media

bull Building Hypermedia APIs with HTML5 and Node November 2011 MikeAmundsen OrsquoReilly Media

bull Node Web Development August 2011 David Herron PacktPub

bull CoffeeScript Accelerated JavaScript Development July 2011 Trevor BurnhamPragmatic Bookshelf

bull Getting Started with GEO CouchDB and Nodejs July 2011 Mick omp-son OrsquoReilly Media

bull What Is Node July 2011 Brett McLaughlin OrsquoReilly Media

bull Node Up and Running May 2011 Tom Hughes-Croucher Mike WilsonOrsquoReilly Media

83

Nodejs中文電子書

bull Hands-on Nodejs e Nodejs introduction and API reference May 2011 Pe-dro Teixeira LeanPub

bull e Node Beginner Book Manuel Kiessling

bull Mastering Nodejs visionmedia

112 Nodejs影音教學

bull e Node Sessions e Best of OSCON 2011 August 2011 OrsquoReilly Media(Video)

bull Tom Hughes-Croucher on Node March 2011 OrsquoReilly Media (Video)

113 Nodejs教學網站

bull Node Tuts

114 Nodejs課程

bull Introduction To Nodejs Van Nguyen CodeLesson

84 11 參考資源

Page 90: Nodejs Wiki

Nodejs中文電子書

bull Hands-on Nodejs e Nodejs introduction and API reference May 2011 Pe-dro Teixeira LeanPub

bull e Node Beginner Book Manuel Kiessling

bull Mastering Nodejs visionmedia

112 Nodejs影音教學

bull e Node Sessions e Best of OSCON 2011 August 2011 OrsquoReilly Media(Video)

bull Tom Hughes-Croucher on Node March 2011 OrsquoReilly Media (Video)

113 Nodejs教學網站

bull Node Tuts

114 Nodejs課程

bull Introduction To Nodejs Van Nguyen CodeLesson

84 11 參考資源