Node.js的愿望是成為一個能構建高速,可伸縮的網(wǎng)絡應用的平臺,它本身具有基于事件,異步,非阻塞,回調(diào)等特性,這在前幾篇專欄中有過描述。正是基于這樣的一些特性,Node.js平臺上的Web框架也具有不同于其他平臺的一些特性,其中Connect是眾多Web框架中的佼佼者。
Connect在它的官方介紹中,它是Node的一個中間件框架。超過18個捆綁的中間件和一些精選第三方中間件。盡管Connect可能不是性能最好的Node.jsWeb框架,但它卻幾乎是最為流行的Web框架。為何Connect能在眾多框架中勝出,其原因不外乎有如下幾個:
Connect自身十分簡單,其作用是基于Web服務器做中間件管理。至于如何如何處理網(wǎng)絡請求,這些任務通過路由分派給管理的中間件們進行處理。它的處理模型僅僅只是一個中間隊列,進行流式處理而已,流式處理可能性能不是最優(yōu),但是卻是最易于被理解和接受。基于中間件可以自由組合和插拔的情況,優(yōu)化它十分容易。
Connect模塊目前在NPM倉庫的MDO(被依賴最多的模塊)排行第八位。但這并沒有真實反映出它的價值,因為排行第五位的Express框架實際上是依賴Connect創(chuàng)建而成的。關于Express的介紹,將會在后續(xù)的專欄中一一為你講解。
讓我們回顧一下Node.js最簡單的Web服務器是如何編寫的:
var http = require('http');
http.createServer(function (req, res) {
res.writeHead(200, {'Content-Type': 'text/plain'});
res.end('Hello World\n');
}).listen(1337, '127.0.0.1');
我們從最樸素的Web服務器處理流程開始,可以看到HTTP模塊基于事件處理網(wǎng)絡訪問無外乎兩個主要的因素,請求和響應。同理的是Connect的中間件也是扮演這樣一個角色,處理請求,然后響應客戶端或是讓下一個中間件繼續(xù)處理。如下是一個中間件最樸素的原型:
function (req, res, next) {
// 中間件
}
在中間件的上下文中,有著三個變量。分別代表請求對象、響應對象、下一個中間件。如果當前中間件調(diào)用了res.end()結束了響應,執(zhí)行下一個中間件就顯得沒有必要。
為了演示中間件的流式處理,我們可以看看中間件的使用形式:
var app = connect();
// Middleware
app.use(connect.staticCache());
app.use(connect.static(__dirname + '/public'));
app.use(connect.cookieParser());
app.use(connect.session());
app.use(connect.query());
app.use(connect.bodyParser());
app.use(connect.csrf());
app.use(function (req, res, next) {
// 中間件
});
app.listen(3001);
Conncet提供use方法用于注冊中間件到一個Connect對象的隊列中,我們稱該隊列叫做中間件隊列。
Conncet的部分核心代碼如下,它通過use方法來維護一個中間件隊列。然后在請求來臨的時候,依次調(diào)用隊列中的中間件,直到某個中間件不再調(diào)用下一個中間件為止。
app.stack = [];
app.use = function(route, fn){
// …
// add the middleware
debug('use %s %s', route || '/', fn.name || 'anonymous');
this.stack.push({ route: route, handle: fn });
return this;
};
值得注意的是,必須要有一個中間件調(diào)用res.end()方法來告知客戶端請求已被處理完成,否則客戶端將一直處于等待狀態(tài)。
流式處理也是Node.js中用于流程控制的經(jīng)典模式,Connect模塊是典型的應用了它。流式處理的好處在于,每一個中間層的職責都是單一的,開發(fā)者通過這個模式可以將復雜的業(yè)務邏輯進行分解。
從前文可以看到其實app.use()方法接受兩個參數(shù),route和fn,既路由信息和中間件函數(shù),一個完整的中間件,其實包含路由信息和中間件函數(shù)。路由信息的作用是過濾不匹配的URL。請求在遇見路由信息不匹配時,直接傳遞給下一個中間件處理。
通常在調(diào)用app.use()注冊中間件時,只需要傳遞一個中間件函數(shù)即可。實際上這個過程中,Connect會將/作為該中間件的默認路由,它表示所有的請求都會被該中間件處理。
中間件的優(yōu)勢類似于Java中的過濾器,能夠全局性地處理一些事務,使得業(yè)務邏輯保持簡單。
任何事物均有兩面性,當你調(diào)用app.use()添加中間件的時候,需要考慮的是中間件隊列是否太長,因為每一層中間件的調(diào)用都是會降低性能的。為了提高性能,在添加中間件的時候,如非全局需求的,盡量附帶上精確的路由信息。
以multipart中間件為例,它用于處理表單提交的文件信息,相對而言較為耗費資源。它存在潛在的問題,那就是有可能被人在客戶端惡意提交文件,造成服務器資源的浪費。如果不采用路由信息加以限制,那么任何URL都可以被攻擊。
app.use("/upload", connect.multipart({ uploadDir: path }));
加上精確的路由信息后,可以將問題減小。
借助Connect可以自由定制中間件的優(yōu)勢,可以自行提升性能或是設計出適合自己需要的項目。Connect自身提供了路由功能,在此基礎上,可以輕松搭建MVC模式的框架,以達到開發(fā)效率和執(zhí)行效率的平衡。以下是筆者項目中采用的目錄結構,清晰地劃分目錄結構可以幫助劃分代碼的職責,此處僅供參考。
├── Makefile // 構建文件,通常用于啟動單元測試運行等操作
├── app.js // 應用文件
├── automation // 自動化測試目錄
├── bin // 存放啟動應用相關腳本的目錄
├── conf // 配置文件目錄
├── controllers // 控制層目錄
├── helpers // 幫助類庫
├── middlewares // 自定義中間件目錄
├── models // 數(shù)據(jù)層目錄
├── node_modules // 第三方模塊目錄
├── package.json // 項目包描述文件
├── public // 靜態(tài)文件目錄
│?? ├── images // 圖片目錄
│?? ├── libs // 第三方前端JavaScript庫目錄
│?? ├── scripts // 前端JavaScript腳本目錄
│?? └── styles // 樣式表目錄
├── test // 單元測試目錄
└── views // 視圖層目錄
更多建議: