初步瞭解CoffeeScript,第4部分: 在伺服器端使用CoffeeScript

發表於2012-10-22

來源:developerworks

簡介

CoffeeScript 是構建在 JavaScript 基礎之上的一種全新程式語言,提供了能夠吸引 Python 或 Ruby 愛好者的整潔的語法。此外還提供了受 Haskell 和 Lisp 等語言啟發得出的許多函數語言程式設計特性。

在本 系列文章 的 第 1 部分 中,我們瞭解了使用 CoffeeScript 的優勢。此外還設定了開發環境,執行了指令碼。在 第 2 部分 中,我們在嘗試解決數學問題的過程中嘗試了許多 CoffeeScript 特性,探索了 CoffeeScript 程式語言。在 第 3 部分 中,為一個 Web 應用程式編寫了客戶端程式碼。

在最後的這篇文章中,您將編寫伺服器端元件,並完成應用程式 — 所有一切都是使用 CoffeeScript 完成的。

下載 本文中使用的原始碼。

coffeescript logo

呼叫所有 Web 服務

第 3 部分 中的 Web 應用程式使用一個關鍵字執行了 Google 和 Twitter 搜尋。對於應用程式的客戶端,您模擬了來自伺服器的結果。為了實際實現此類功能,您需要應用程式的伺服器端呼叫 Google 和 Twitter 提供的 Web 服務。兩家公司均提供了非常簡單的搜尋服務。您只需對搜尋服務發出 HTTP GET 請求即可。清單 1 展示了發出 HTTP GET 請求的一般函式。

清單 1. 獲取 Web 資源

 

require 語句是指令碼的第一條語句,在本系列的 第 1 部分 中已經對此進行了簡單的介紹。這是一種 Node.js 模組匯入語法,或者至少應該說是這種語法的 CoffeeScript 版本。“原生” 版本應該是 var http = require("http");。在這篇文章中,您將使用多個 Node.js 核心模組。(這些模組的工作原理不在本文討論範圍之內。)如果您安裝了 Node.js,那麼就應該能使用本文中使用的所有模組(請參見 第 1 部分)。對於 清單 1 中的示例,您使用的是 http 模組,它為發出和接收 HTTP 請求提供了一些非常有用的類和函式。

清單 1 隨後定義了一個 fetchPage 函式,可以接受以下四個引數:

1. 資源的 host 名稱。

2. 資源的 port

3. 資源的 path

4. 一個 callback 函式。

Node.js 中任何型別的 I/O 函式在本質上都是非同步的,因此在完成時需要通過一個 callback 函式進行呼叫。fetchPage 函式接受一個 callback 函式作為第四個引數。隨後使用前三個引數,通過 http 模組的 get 函式發出一條 HTTP GET 請求。

fetchPage 函式也獲取一個 callback 函式,將有一個 ClientResponse 例項傳遞給後一個函式。ClientResponse 是 http 模組中定義的一個物件,它實現了 ReadableStream 介面(Node.js 中的核心介面)。這是一個非同步介面,接受兩個事件:data 和 end。其惟一的函式用於為這些事件註冊回撥。在從您發出 HTTP GET 請求的資源接收到資料時,將發生資料事件。

資源將一次性返回所有資料,但更常見的做法是分塊傳送資料。接收到各塊時,資料事件將被觸發,回撥將被呼叫。您建立了一個名為 contents 的變數;每次接收到另一個塊時,都會將其附加到 contents。接收了所有資料之後,即觸發 end 事件。現在,您獲得了全部資料,因此可以將 contents 傳遞給傳入 fetchPage 函式的 callback 函式。定義了這個多用途函式之後,下面我們將為 Google 和 Twitter 搜尋 API 建立一些專用函式,如 清單 2 所示。

清單 2. Google 與 Twitter 搜尋函式

 

清單 2 中定義了兩個函式:

1. googleSearch,用於獲取一個 keyword 和一個 callback 函式。它將固定主機,並使用 CoffeeScript 的字串插值建立路徑,隨後使用 fetchPage

2. twitterSearch,該函式與 googleSearch 極為相似,但使用了不同的主機和路徑值。

對於兩個路徑值,您都要使用字串插值和 JavaScript 提供的便捷的 encodeURI 函式來處理任何空格或其他特殊字元。現在您已經擁有了這些搜尋函式,下面即可為合併搜尋場景建立特殊函式。

合併非同步函式

您可以通過多種方法在 Google 和 Twitter 上執行合併搜尋。您可以呼叫 googleSearch,隨後在 callback 中呼叫 twitterSearch,或者相反。然而,Node.js 的非同步/回撥架構使您能夠更優雅、更高效地完成任務。清單 3 展示了合併搜尋。

清單 3. 同時搜尋 Google 和 Twitter

 

combinedSearch 函式有一項現在已經廣為人知的特徵:接受一個關鍵字和一個回撥。隨後它為合併搜尋結果建立一個資料結構,名為datadata 物件擁有一個 google 欄位和一個 twitter 欄位,兩者均初始化為空字串。下一步是呼叫 googleSearch 函式。在回撥中,您將使用標準 JSON.parse 函式解析來自 Google 的結果。Google 返回的 JSON 文字將解析為 JavaScript 物件。這種它來設定data.google 欄位的值。呼叫 googleSearch 之後,再呼叫 twitterSearch。其 callback 函式與 googleSearch 的回撥函式極為相似。

有必要理解,在兩個回撥中,您都要檢查是否有來自另一個回撥的資料。您無法確知哪個回撥先完成。因此需要檢視是否有來自 Google 和 Twitter 的資料。確認之後,即可呼叫之前傳入 combinedSearch 函式的 callback 函式。您現在得到了一個同時搜尋 Google 和 Twitter 並提供合併結果的函式。下一個任務就是將這樣的結果公開到您在本系列的 第 3 部分

CoffeeScript Web 伺服器

至此,您已經得到了:

1. 一個能夠傳送關鍵字、顯示搜尋結果的網頁。

2.一個能夠接受關鍵字並生成 Google 和 Twitter 搜尋結果的函式。

怎樣將這一切關聯起來?您可以將該伺服器稱為 Web 伺服器、應用伺服器,甚至是中介軟體。無論怎樣稱呼,在 CoffeeScript 為它編寫程式碼都非常容易。

Web 伺服器需要滿足兩個目的。顯然,它需要接受合併搜尋的請求。此外還需要提供您在 第 3 部分 中建立的靜態資源。您要建立的是一個 Web 應用程式,因此必須密切注意同源策略。搜尋呼叫必須發往生成網頁的相同位置。我們首先來處理靜態資源。清單 4 展示了一個處理靜態資源的函式。

清單 4. 處理靜態資源

 

serveStatic 函式處理 Web 應用程式中對靜態資源的請求。請注意,您還需要使用兩個 Node.js 模組:

1. path 是一個處理檔案路徑的實用工具庫。

2. 檔案系統或 fs 提供了 Node.js 中的所有檔案 I/O,大體上就是基於標準 POSIX 函式的一個包裝器。

serveStatic 函式接受兩個引數:

1. uri 實際上是 Web 瀏覽器所請求的靜態檔案的相對路徑。

2. ServerResponse 物件,是 http模組中定義的另外一種型別。它的功能之一就是使您能夠寫入 HTTP GET 向資源請求的資料。

在 serveStatic 中,使用 process.cwd 將檔案的相對路徑轉為絕對路徑。process 物件是一個全域性物件,指示正在執行 Node.js 的系統程式。它的 cwd 方法提供了當前工作目錄。使用 path 模組,整合當前工作目錄和您需要的檔案的相對目錄,結果將得到一個絕對路徑。有了絕對路徑,您就可以再次使用 path 模組,檢查檔案是否存在。檢查一個檔案是否存在時,需要涉及到 I/O,因此這是一個非同步函式。為其傳遞 fileName 和回撥函式。回撥函式將提供一個布林值,使您瞭解檔案是否存在。如果不存在,那麼您就就需要寫出一條 HTTP 404 “檔案未找到”訊息。

如果檔案確實存在,那麼就需要使用 fs 模組和它的 readFile 方法(非同步方法)來讀取檔案的內容。該方法將獲取 fileName、一個型別和一個回撥函式。回撥函式獲取兩個引數:

1. 一個表明在從檔案系統中讀取資源時遇到的任何問題的錯誤引數。如果存在問題,系統會向客戶端返回一個 HTTP 500 錯誤訊息。

2. 如果沒有問題,則會顯示 HTTP 200 OK 訊息,並將檔案的內容回發給客戶端。

此函式能對靜態檔案進行相對較為簡單的處理。下一部分將討論您希望動態響應一個搜尋請求的更為困難的場景。

動態響應與伺服器

示例 Web 伺服器主要處理對靜態資源的請求和動態搜尋請求。我們的戰略是使用特定 URL 來處理搜尋請求,隨後將其他請求分載到serveStatic 函式。為搜尋請求使用 /doSearch 的相對 URL。清單 5 展示了 Web 伺服器程式碼。

清單 5. CoffeeScript Web 伺服器

 

這個指令碼同樣從載入一個 Node.js 模組開始。url 模組是解析 URL 時的一個有用的庫。下一步是使用 清單 1 中載入的 http 模組建立 Web 伺服器。使用該模組的 createServer 方法,該方法將獲取一個回撥函式,每次對 Web 伺服器發出一條請求時,都會呼叫這個回撥函式。該回撥函式接受兩個引數:一個 ServerRequest 例項和一個 ServerResponse 例項。兩種型別都是在 http 模組中定義的。在回撥函式中,使用 url 模組的 parse 方法,解析對伺服器發出的請求的 URL。這將為您提供一個 URL 物件,您可以使用它的pathname 屬性獲取相對路徑。如果 pathname 是 /doSearch,則應呼叫 doSearch 函式(詳見下文討論)。否則就應該呼叫 清單 5 中的serveStatic 函式。清單 6 展示了 doSearch 的工作方式。

清單 6. 處理搜尋請求

 

doSearch 函式將解析 URL 的查詢字串,可以在 uri 物件的查詢屬性中找到這個字串。根據 “&” 字元拆分字串。隨後根據等號字元拆分各子字串,獲得查詢字串中的名稱值對。將各名稱值對儲存在 params 物件中。獲取 "q" 引數,以便獲得您希望搜尋的關鍵字。將此傳遞給 清單 3 中的 combinedSearch 函式。您必須為其傳遞一個回撥函式。示例回撥函式直接寫出一條 HTTP 200 OK 訊息,並使用標準函式 JSON.stringify 將結果轉為字串。

這就是伺服器所需的一切。在下一節中,我們將介紹如何將這樣的伺服器程式碼與本系列 第 3 部分 中的客戶端程式碼掛接起來。

呼叫搜尋伺服器

在 第 3 部分 中,您編寫了一個使用模擬資料提供搜尋結果的 MockSearch 類。現在,您將定義一個新類,呼叫搜尋伺服器來執行真正的搜尋。清單 7 顯示了新的搜尋類。

清單 7. 實際搜尋類

 

CombinedSearch 類擁有單獨一個方法,即 search 方法,它與 MockSearch 的 search 方法具有相同的特徵。也就是接受一個關鍵字和一個回撥函式。在函式內:

1. 使用 XMLHttpRequest(所有 Web 開發人員的 “老朋友”),通過傳遞到函式中的 /doSearch 路徑和關鍵字向伺服器發出 HTTP 請求。

2. 獲得響應之後,使用 JSON.parse 來解析它。

3. 建立一個包含 google 和 twitter 欄位的結果物件。使用 第 3 部分 中的 GoogleSearchResult 和 TwitterSearchResult 類來建立這些欄位。

4. 將結果傳遞迴 callback 函式。

現在,您需要的只是在 Web 頁面的 doSearch 方法中使用這些類,而不是在 MockSearch 中使用。清單 8 展示瞭如何使用CombinedSearch 類。

清單 8. 使用 CombinedSearch 類

 

將 清單 8 與 第 3 部分 中的 doSearch 對比,您不會發現很多的差異。惟一不同的就是第七行。這裡例項化的不再是 MockSearch 例項,而是一個 CombinedSearch 例項。其他所有部分都是完全相同的。您從網頁獲取關鍵字,呼叫搜尋,隨後通過呼叫各SearchResult 物件的 toHtml 方法來附加結果。圖 1 展示了包含來自伺服器的 “實時” 搜尋結果的 Web 應用程式。

圖 1. 執行示例 Web 應用程式

初步瞭解 CoffeeScript,第 4 部分: 在伺服器端使用 CoffeeScript

為了實現客戶端程式碼的更改,您需要使用 coffee -c search.coffee 進行重新編譯。如需執行應用程式,請使用 coffee search-server.coffee。隨後即可開啟瀏覽器,轉到 http://127.0.0.1:8080,並嘗試執行各種查詢。

在這篇文章中,您完成了 Web 應用程式,構建了伺服器端元件來補充 第 3 部分 中的客戶端程式碼。現在,在本 系列 結束時,您獲得了一個完全在 CoffeeScript 中編寫的完整應用程式。您使用了 Node.js 中的許多特性,這使您能夠將 CoffeeScript 用作伺服器端技術。

人們對 Node.js 的普遍異議就是其非阻塞式風格會導致多層回撥函式。這可能導致您難以理清頭緒,而 JavaScript 繁冗的語法進一步加大了複雜度。CoffeeScript 並未改變使用所有這些回撥的需求,但其優雅的語法確實使您能夠更加輕鬆地編寫和理解此類程式碼

 

 

相關文章