【深刻淺出Node.js系列七】Connect模塊解析

#0 系列目錄#javascript

#1 Connect模塊背景# Node.js的願望是成爲一個能構建高速,可伸縮的網絡應用的平臺,它自己具備基於事件,異步,非阻塞,回調等特性,正是基於這樣的一些特性,Node.js平臺上的Web框架也具備不一樣於其餘平臺的一些特性,其中Connect是衆多Web框架中的佼佼者。html

Connect在它的官方介紹中,它是Node的一箇中間件框架。超過18個捆綁的中間件和一些精選第三方中間件。儘管Connect可能不是性能最好的Node.jsWeb框架,但它卻幾乎是最爲流行的Web框架。爲什麼Connect能在衆多框架中勝出,其緣由不外乎有以下幾個:前端

模型簡單java

中間件易於組合和插拔node

中間件易於定製和優化web

豐富的中間件算法

Connect自身十分簡單,其做用是基於Web服務器作中間件管理。至於如何處理網絡請求,這些任務經過路由分派給管理的中間件們進行處理它的處理模型僅僅只是一箇中間隊列,進行流式處理而已,流式處理可能性能不是最優,可是倒是最易於被理解和接受。基於中間件能夠自由組合和插拔的狀況, 優化它十分容易。Connect模塊目前在NPM倉庫的MDO(被依賴最多的模塊)排行第八位。但這並無真實反映出它的價值,由於排行第五位的Express框架其實是依賴Connect建立而成的apache

Connect是一個node中間件(middleware)框架。若是把一個http處理過程比做是污水處理,中間件就像是一層層的過濾網。每一箇中間件在http處理過程當中經過改寫request或(和)response的數據、狀態,實現了特定的功能。這些功能很是普遍,下圖列出了connect全部內置中間件和部分第三方中間件。npm

下圖根據中間件在整個http處理流程的位置,將中間件大體分爲3類:json

  1. Pre-Request 一般用來改寫request的原始數據;

  2. Request/Response 大部分中間件都在這裏,功能各異;

  3. Post-Response 全局異常處理,改寫response數據等;

輸入圖片說明

#2 中間件# 讓咱們回顧一下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模塊基於事件處理網絡訪問無外乎兩個主要的因素,請求和響應。同理的是Connect的中間件也是扮演這樣一個角色,處理請求,而後響應客戶端或是讓下一個中間件繼續處理。以下是一箇中間件最樸素的原型:

function (req, res, next) { 
    // 中間件
}

在中間件的上下文中,有着三個變量。分別表明請求對象、響應對象、下一個中間件。若是當前中間件調用了res.end()結束了響應,執行下一個中間件就顯得沒有必要。

#3 流式處理# 爲了演示中間件的流式處理,咱們能夠看看中間件的使用形式:

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方法來維護一箇中間件隊列。而後在請求來臨的時候,依次調用隊列中的中間件,直到某個中間件再也不調用下一個中間件爲止

app.stack = [];
app.use = function(route, fn) {
    // ...
    // add the middleware
    debug('use %s %s', route || '/', fn.name || 'an onymous');
    this.stack.push({ 
        route: route,
        handle: fn
    });
    return this;
};

值得注意的是,必需要有一箇中間件調用res.end()方法來告知客戶端請求已被處理完成,不然客戶端將一直處於等待狀態流式處理也是Node.js中用於流程控制的經典模式,Connect模塊是典型的應用了它。流式處理的好處在於,每個中間層的職責都是單一的,開發者經過這個模式能夠將複雜的業務邏輯進行分解

#4 Connect安裝# 經過nodejs安裝Connect:

~ D:\workspace\javascript>mkdir nodejs-connect && cd nodejs-connect
~ D:\workspace\javascript\nodejs-connect> npm install connect
connect@2.9.0 node_modules\connect
├── methods@0.0.1
├── uid2@0.0.2
├── pause@0.0.1
├── cookie-signature@1.0.1
├── fresh@0.2.0
├── qs@0.6.5
├── bytes@0.2.0
├── buffer-crc32@0.2.1
├── cookie@0.1.0
├── debug@0.7.2
├── send@0.1.4 (range-parser@0.0.4, mime@1.2.11)
└── multiparty@2.1.8 (stream-counter@0.1.0, readable-stream@1.0.17)

嘗試作一個最簡單的web服務器,增長一個文件:app.js

var connect = require('connect');
var app = connect()
    .use(connect.logger('dev'))
    .use(function (req, res) {
        res.end('hello world\n');
    })
    .listen(3000);

啓動node:

~ D:\workspace\javascript\nodejs-connect>node app.js
GET / 200 5ms
GET /favicon.ico 200 0ms

打開瀏覽器:http://localhost:3000/

輸入圖片說明

#5 路由# 從前文能夠看到其實app.use()方法接受兩個參數,route和fn,既路由信息和中間件函數,一個完整的中間件,其實包含路由信息和中間件函數路由信息的做用是過濾不匹配的URL請求在碰見路由信息不匹配時,直接傳遞給下一個中間件處理。一般在調用app.use()註冊中間件時,只須要傳遞一箇中間件函數便可。實際上這個過程當中,Connect會將/做爲該中間件的默認路由,它表示全部的請求都會被該中間件處理。中間件的優點相似於Java中的過濾器,可以全局性地處理一些事務,使得業務邏輯保持簡單。任何事物均有兩面性,當你調用app.use()添加中間件的時候,須要考慮的是中間件隊列是否太長,由於每一層中間件的調用都是會下降性能的。爲了提升性能,在添加中間件的時候,如非全局需求的,儘可能附帶上精確的路由信息。以multipart中間件爲例,它用於處理表單提交的文件信息,相對而言較爲耗費資源。它存在潛在的問題,那就是有可能被人在客戶端惡意提交文件,形成服務器資源的浪費。若是不採用路由信息加以限制,那麼任何URL均可以被攻擊。

app.use("/upload", connect.multipart({ 
    uploadDir: path 
}));

加上精確的路由信息後,能夠將問題減小。

#6 MVC目錄# 藉助Connect能夠自由定製中間件的優點,能夠自行提高性能或是設計出適合本身須要的項目。Connect自身提供了路由功能,在此基礎上,能夠輕鬆搭建MVC模式的框架,以達到開發效率和執行效率的平衡。如下是筆者項目中採用的目錄結構,清晰地劃分目錄結構能夠幫助劃分代碼的職責,此處僅供參考。

├── Makefile // 構建文件,一般用於啓動單元測試運行等操做

├── app.js // 應用文件

├── automation // 自動化測試目錄

├── bin // 存放啓動應用相關腳本的目錄

├── conf // 配置文件目錄

├── controllers // 控制層目錄

├── helpers // 幫助類庫

├── middlewares // 自定義中間件目錄

├── models // 數據層目錄

├── node_modules // 第三方模塊目錄

├── package.json // 項目包描述文件

├── public // 靜態文件目錄

│ ├── images // 圖片目錄

│ ├── libs // 第三方前端JavaScript庫目錄

│ ├── scripts // 前端JavaScript腳本目錄

│ └── styles // 樣式表目錄

├── test // 單元測試目錄

└── views // 視圖層目錄

#7 靜態文件中間件# 如何利用Node.js實現一個靜態文件服務器的許多技術細節,包括路由實現,MIME,緩存控制,傳輸壓縮,安全、歡迎頁、斷點續傳等。可是這裏咱們不須要去親自處理細節,Connect的static中間件爲咱們提供上述全部功能。代碼只需寥寥3行便可:

var connect = require('connect');
var app = connect(); 
app.use(connect.static(__dirname + '/public'));

在項目中須要臨時搭建靜態服務器,也無需安裝apache之類的服務器,經過NPM安裝Connect以後,三行代碼便可解決需求。這裏須要說起的是在使用該模塊的一點性能相關的細節。

##7.1 動靜分離## 前一章說起,app.use()方法在沒有指定路由信息時,至關於app.use("/", middleware)。這意味着靜態文件中間件將會在處理全部路徑的請求。在動靜態請求混雜的場景下,靜態中間件會在動態請求時也調用fs.stat來檢測文件系統是否存在靜態文件。這形成了沒必要要的系統調用,使得性能下降。解決影響性能的方法既是動靜分離。利用路由檢測,避免沒必要要的系統調用, 能夠有效下降對動態請求的性能影響。

app.use('/public', connect.static(__dirname + '/public'));

在大型的應用中,動靜分離一般無需到一個Node.js實例中進行,CDN的方式直接在域名上將請求分離。小型應用中,適當的進行動靜分離便可避免沒必要要的性能損耗。

##7.2 緩存策略## 緩存策略包含客戶端和服務端兩個部分客戶端的緩存,主要是利用瀏覽器對HTTP協議響應頭中cache-control和expires字段的支持。瀏覽器在獲得明確的相應頭後,會將文件緩存在本地,依據cache-control和expires的值進行相應的過時策略。這使得重複訪問的過程當中,瀏覽器能夠從本地緩存中讀取文件,而無需從網絡讀取文件,提高加載速度,也能夠下降對服務器的壓力。默認狀況下靜態中間件的最大緩存時設置爲0,意味着它在瀏覽器關閉後就被清除。這顯然不是咱們所指望的結果。除非是在開發環境能夠無視maxAge的設 置外,生產環境請務必設置緩存,由於它能有效節省網絡帶寬。

app.use('/public', connect.static(__dirname + '/p ublic', {maxAge: 86400000}));

maxAge選項的單位爲毫秒。YUI3的CDN服務器設置過時時間爲10年,是一個值得參考的值。靜態文件若是在客戶端被緩存,在須要清除緩存的時候,又該如何清除呢?這裏的實現方法較多,一種較爲推薦的作法是爲文件進行md5處理

http://some.url/some.js?md5

當文件內容產生改變時,md5值也將發生改變,瀏覽器根據URL的不一樣會從新獲取靜態文件。md5的方式能夠避免沒必要要的緩存清除,也能精確清除緩存。因爲瀏覽器自己緩存容量的限制,儘管咱們可能設置了10年的過時時間,可是也許兩天以後就被新的靜態文件擠出了本地緩存。這將持續引發靜態服務器的響應,也即意味着,客戶端緩存並不能徹底解決下降服務器壓力的問題。爲了解決靜態服務器重複讀取磁盤形成的壓力,這裏須要引出第二個相關的中間件:staticCache。

app.use(connect.staticCache());
app.use(「/public」, connect.static(__dirname + '/public', {maxAge: 86400000}));

這是一個提供上層緩存功能的中間件,可以將磁盤中的文件加載到內存中,以提升響應速度和提升性能

另外一個專門用於靜態文件託管的模塊叫node-static,其性能是Connect靜態文件中間件的效率的兩倍。可是在緩存中間件的協助下,能夠彌補性能損失。

事實上,這個中間件在生產環境下並不推薦被使用,並且它將在Connect 3.0版本中被移除。可是它的實現中有值得玩味的地方,這有助於咱們認識Node.js模型的優缺點。

staticCache中間件有兩個主要的選項:maxObjects和maxLength。表明的是能存儲多少個文件和單個文件的最大尺寸,其默認值爲128和256kb。爲什麼會有這兩個選項的設定,緣由在於V8有內存限制的緣由,做爲緩存,若是沒有良好的過時策略,緩存將會無限增長,直到內存溢出。設置存儲數量和單個文件大小後,能夠有效抑制緩存區的大小。事實上,該緩存還存在的缺陷是單機狀況下,一般爲了有效利用CPU,Node.js實例並不僅有一個,多個實例進程之間將會存在冗餘的緩存佔用,這對於內存使用而言是浪費的。

除此以外,V8的垃圾回收機制是暫停JavaScript線程執行,經過掃描的方式決定是否回收對象。若是緩存對象過大,鍵太多,則掃描的時間會增長,會引發JavaScript響應業務邏輯的速度變慢。可是這個模塊並不是沒有存在的意義,上述說起的缺陷大多都是V8內存限制和Node.js單線程的緣由。解決該問題的方式則變得明瞭。

風險轉移是Node.js中經常使用於解決資源不足問題的方式,尤爲是內存方面的問題。將緩存點,從Node.js實例進程中轉移到第三方成熟的緩存中去便可。這能夠保證:

  1. 緩存內容不冗餘。

  2. 集中式緩存,減小不一致性的發生。

  3. 緩存的算法更優秀以保持較高的命中率。

  4. 讓Node.js保持輕量,以解決它更擅長的問題。

Connect推薦服務器端緩存採用varnish這樣的成熟緩存代理。而筆者目前的項目則是經過Redis來完成後端緩存的任務。

#8 Connect內置中間件介紹#

logger: 用戶請求日誌中間件

csrf: 跨域請求僞造保護中間件

compress: gzip壓縮中間件

basicAuth: basic認證中間件

bodyParser: 請求內容解析中間件

json: JSON解析中間件

urlencoded: application/x-www-form-urlencode請求解析中間件

multipart: multipart/form-data請求解析中間件

timeout: 請求超時中間件

cookieParser: cookie解析中間件

session: 會話管理中間件

cookieSession: 基於cookies的會話中間件

methodOverride: HTTP僞造中間件

reponseTime: 計算響應時間中間件

staticCache: 緩存中間件

static: 靜態文件處理中間件

directory: 目錄列表中間件

vhost: 虛擬二級域名映射中間件

favicon: 網頁圖標中間件

limit: 請求內容大小限制中間件

query: URL解析中間件

errorHadnler: 錯誤處理中間件

##8.1 logger## 描述:用來輸出用戶請求日誌。參數:options或者format字符串。

options:

format:參考下面的tokens

stream:輸出流,默認是stdout

buffer:緩衝時間,默認是1000ms

immediate:馬上打印日誌

tokens: format格式

:req[header] ex: :req[Accept]

:res[header] ex: :res[Content-Length]

:http-version

:response-time

:remote-addr

:date

:method

:url

:referrer

:user-agent

:status

Formats:縮寫

default ‘:remote-addr – – [:date] 「:method :url HTTP/:http-version」 :status :res[content-length] 「:referrer」 「:user-agent」‘

short ‘:remote-addr – :method :url HTTP/:http-version :status :res[content-length] – :response-time ms’

tiny ‘:method :url :status :res[content-length] – :response-time ms’

dev concise output colored by response status for development use

例子:新建logger.js

var connect = require('connect');
var app = connect()
    .use(connect.logger())
    .use(function (req, res) {
        res.end('hello world\n');
    })
    .listen(3000);

connect.logger()

127.0.0.1 - - [Mon, 23 Sep 2013 05:14:18 GMT] "GET / HTTP/1.1" 200 - "-" "Mozilla/5.0 (Windows NT 6.1; WOW64) AppleWebKi
t/537.36 (KHTML, like Gecko) Chrome/28.0.1500.95 Safari/537.36"
127.0.0.1 - - [Mon, 23 Sep 2013 05:14:18 GMT] "GET /favicon.ico HTTP/1.1" 200 - "-" "Mozilla/5.0 (Windows NT 6.1; WOW64)
 AppleWebKit/537.36 (KHTML, like Gecko) Chrome/28.0.1500.95 Safari/537.36"

connect.logger(‘short’)

127.0.0.1 - GET / HTTP/1.1 200 - - 9 ms
127.0.0.1 - GET /favicon.ico HTTP/1.1 200 - - 1 ms

connect.logger(‘dev’)

GET / 200 5ms
GET /favicon.ico 200 1ms

connect.logger(function(tokens, req, res){ return ‘some format string’ })

some format string
some format string

因此在開發環境,咱們日誌設置成dev就行了。

##8.2 cookieParser## 描述:cookie解析中間件,解析Cookies的頭經過req.cookies獲得cookies。還能夠經過req.secret加密cookies。

例子:新建cookieParser.js

var connect = require('connect');
var app = connect()
    .use(connect.logger('dev'))
    .use(connect.cookieParser('secret string'))
    .use(function (req, res, next) {
        req.cookies.website="blog.fens.me";
        res.end(JSON.stringify(req.cookies));
    })
    .listen(3000);

輸入圖片說明

##8.3 session## 描述:會話管理中間件

依賴:cookieParser

參數:options

key:Cookies名,默認值爲connect.sid

store: session存儲實例

secret: session的cookie加密

cookie: session的cookie配置,默認值爲{path: ‘/’, httpOnly: true, maxAge: null}

proxy:安全cookie的反向代理,經過x-forwarded-proto實現

Cookie option:

cookie.maxAge: 默認值null,表示當瀏覽器關閉後cookie被刪除。

例子:新建session.js

var connect = require('connect');
var app = connect()
    .use(connect.logger('dev'))
    .use(connect.cookieParser())
    .use(connect.session({secret: '123', cookie: { maxAge: 60000 }}))
    .use(function (req, res, next) {
        if(req.session.pv){
            res.setHeader('Content-Type', 'text/html');
            res.write('views: ' + req.session.pv);
            req.session.pv++;
            res.end();
        }else{
            req.session.pv = 1;
            res.end('Refresh');
        }

    })
    .listen(3000);

輸入圖片說明

##8.4 cookieSession## 描述:基於cookies的會話中間件

參數:options:

key:Cookies名,默認值爲connect.sess

secret: 防止cookie竊取

cookie: session的cookie配置,默認值爲{path: ‘/’, httpOnly: true, maxAge: null}

proxy:安全cookie的反向代理,經過x-forwarded-proto實現

Clearing sessions:

req.session = null;

例子:新建cookieSession.js

var connect = require('connect');
var app = connect()
    .use(connect.logger('dev'))
    .use(connect.cookieParser())
    .use(connect.cookieSession({ secret: 'adddaa!', cookie: { maxAge: 60 * 60 * 1000 }}))
    .use(function (req, res, next) {
        if(req.session.pv){
            res.setHeader('Content-Type', 'text/html');
            res.write('views: ' + req.session.pv );
            req.session.pv++;
            console.log(req.session.pv);
            res.end();
        }else{
            req.session.pv = 1;
            res.end('Refresh');
        }

    })
    .listen(3000);

輸入圖片說明

咱們發現,此次無論刷新多少次頁面,req.session.pv始終是1。

##8.5 compress## 描述:gzip壓縮中間件,經過filter函數設置,須要壓縮的文件類型。壓縮算法爲gzip/deflate。

filter函數:

exports.filter = function(req, res){
    return /json|text|javascript|dart|image\/svg\+xml|application\/x-font-ttf|application\/vnd\.ms-opentype|application\/vnd\.ms-fontobject/.test(res.getHeader('Content-Type'));
};

Threshold壓縮閾值:當響應請求大於閾值,則壓縮響應請求。

參數:options

chunkSize (default: 16*1024)

windowBits

level: 0-9 where 0 is no compression, and 9 is slow but best compression

memLevel: 1-9 low is slower but uses less memory, high is fast but uses more

strategy: compression strategy

例子:新建compress.js

var connect = require('connect');
var app = connect()
    .use(connect.logger('dev'))
    .use(connect.compress({level:9}))
    .use(function (req, res) {
        res.setHeader('Content-Type', 'text/html');
        res.write(res);
        res.end('hello world\n');
    })
    .listen(3000);

輸入圖片說明

##8.6 basicAuth## 描述:basic認證中間件,實現簡單的用戶密碼登錄,當用戶和密碼驗證經過後,經過一個callback方法繼續執行。

同步驗證:

connect()
    .use(connect.basicAuth(function(user, pass){
        return 'tj' == user && 'wahoo' == pass;
    }))

異步驗證:

connect()
    .use(connect.basicAuth(function(user, pass, fn){
        User.authenticate({ user: user, pass: pass }, fn);
    }))

例子:新建basicAuth.js

var connect = require('connect');
var app = connect();
app.use(connect.logger('dev'));

//  基本用法
//  app.use(connect.basicAuth('fens','fens'))

//  同步驗證
app.use(connect.basicAuth(function (user, pass) {
    var isLogin = 'fens' == user && 'fens' == pass;
    console.log("Login:" + isLogin);
    return isLogin;
}))
app.use(function (req, res) {
    res.end('hello world\n');
})
app.listen(3000);

驗證彈出框:

輸入圖片說明

正確輸入用戶和密碼後,訪問頁面:

輸入圖片說明

##8.7 bodyParser## 描述:請求內容解析中間件,支持多種類型application/json,application/x-www-form-urlencoded, multipart/form-data.

與其餘的3箇中間件相同:

app.use(connect.json());
app.use(connect.urlencoded());
app.use(connect.multipart());

例子:新建bodyParser.js

var connect = require('connect');
var app = connect()
    .use(connect.logger('dev'))
    .use(connect.bodyParser())
    .use(function(req, res) {
        res.end('req.body=>' + JSON.stringify(req.body));
    })
    .listen(3000);

POST方法:

~ curl -d 'user[name]=tj' http://localhost:3000/
req.body=>{"'user":{"name":"tj'"}}

GET方法:

~ curl http://localhost:3000/?user=123
req.body=>{}

##8.8 json## 描述:JSON解析中間件,req.body傳值

參數:options

strict: when false anything JSON.parse() accepts will be parsed

reviver: used as the second 「reviver」 argument for JSON.parse

limit: byte limit [1mb]

同bodyParser。

##8.9 urlencoded## 描述:application/x-www-form-urlencode請求解析中間件

參數:options

limit: byte limit [1mb]

同bodyParser。

##8.10 multipart## 描述:multipart/form-data請求解析中間件,解析req.body和req.files.

上傳文件:uploadDir

app.use(connect.multipart({uploadDir: path }))

參數:options

limit: byte limit defaulting to [100mb]

defer: 在不等待「結束」事件,經過調用req.form.next()函數,能夠緩衝並處理多個表單對象,還能夠綁定到「progress」或「events」的事件。

Temporary Files:臨時文件

默認狀況下,臨時文件會被保存在os.tmpDir()目錄,但不會自動迴歸,咱們必須手動處理。若是沒有使用defer選項時,你能夠經過req.files來得到對象的使用。

req.files.images.forEach(function(file){
    console.log('  uploaded : %s %skb : %s', file.originalFilename, file.size / 1024 | 0, file.path);
});

Streaming:流式處理

當使用defer選項時,文件在上傳過程當中,你能夠經過」part」事件和流控制訪問文件。

req.form.on('part', function(part){
    // transfer to s3 etc
    console.log('upload %s %s', part.name, part.filename);
    var out = fs.createWriteStream('/tmp/' + part.filename);
    part.pipe(out);
});

req.form.on('close', function(){
    res.end('uploaded!');
});

例子:新建multipart.js

var connect = require('connect');
var app = connect()
            .use(connect.logger('dev'))
            .use(connect.multipart({ uploadDir: 'd:\\tmp' }))
            .use(function (req, res) {
                if(req.method=='POST'){
                    console.log(req.files);
                    res.end('Upload ==>'+ req.files.file.path);    
                }
                res.setHeader('Content-Type', 'text/html');
                res.write('<form enctype="multipart/form-data" method="POST"><input type="file" name="file">');
                res.write('<input type="submit" value="submit"/>');
                res.write('</form>');
                res.end('hello world\n');
            }).listen(3000);

經過form表單選擇文件:

輸入圖片說明

POST請求解析:

輸入圖片說明

##8.11 timeout## 描述:請求超時中間件,默認超時時間是5000ms,能夠清除這個時間經過req.clearTimeout()函數。超時的錯誤,能夠經過next()函數傳遞。咱們也能夠設置超時的響應錯誤狀態碼:.timeout=503

例子:新建timeout.js,模擬超時

var connect = require('connect');
var app = connect()
    .use(connect.logger('dev'))
    .use(connect.timeout(1000))
    .use(function (req, res) {
        setTimeout(function(){
            res.end('hello world\n');
        },5000)
    })
    .listen(3000);

控制檯輸出:

Error: Response timeout
    at IncomingMessage. (D:\workspace\javascript\nodejs-connect\node_modules\connect\lib\middleware\timeout.j
s:39:17)
    at IncomingMessage.EventEmitter.emit (events.js:95:17)
    at null._onTimeout (D:\workspace\javascript\nodejs-connect\node_modules\connect\lib\middleware\timeout.js:34:11)
    at Timer.listOnTimeout [as ontimeout] (timers.js:110:15)
GET / 503 1030ms - 389b
Error: Response timeout
    at IncomingMessage. (D:\workspace\javascript\nodejs-connect\node_modules\connect\lib\middleware\timeout.j
s:39:17)
    at IncomingMessage.EventEmitter.emit (events.js:95:17)
    at null._onTimeout (D:\workspace\javascript\nodejs-connect\node_modules\connect\lib\middleware\timeout.js:34:11)
    at Timer.listOnTimeout [as ontimeout] (timers.js:110:15)
GET /favicon.ico 503 1006ms - 389b

##8.12 reponseTime## 描述:計算響應時間中間件,在response的header增長X-Response-Time,計算響應時間。

例子:新建reponseTime.js

var connect = require('connect');
var app = connect()
    .use(connect.logger('dev'))
    .use(connect.responseTime())
    .use(function (req, res) {
        res.end('hello world\n');
    })
    .listen(3000);

輸入圖片說明

##8.13 methodOverride## 沒法模擬出效果,暫時先跳過

描述: 提供僞造HTTP中間件,檢查一個method是否被重寫,經過傳遞一個key,獲得_method,原始的方法經過req.originalMethod得到。

例子:新建methodOverride.js

var connect = require('connect');
var app = connect()
    .use(connect.logger('dev'))
    .use(connect.methodOverride('KEY'))
    .use(function (req, res) {
        res.end(JSON.stringify(req.headers));
    })
    .listen(3000);

##8.14 csrf## 描述:跨域請求csrf保護中間件,經過req.csrfToken()令牌函數綁定到請求的表單字段。這個令牌會對訪客會話進行驗證。默認狀況會檢查經過bodyParser()產生的req.body,query()函數產生的req.query,和X-CSRF-Token的header。

依賴:session(), cookieParser()

參數:options:

value: 一個函數接受請求,返回的令牌

例子:新建csrf.js

var connect = require('connect');
var app = connect()
    .use(connect.logger('dev'))
    .use(connect.cookieParser())
    .use(connect.session({secret: '123', cookie: { maxAge: 60000 }}))
    .use(connect.csrf({value:'admin'}))
    .use(function (req, res) {
        res.setHeader('Content-Type', 'text/html');
        res.end('hello world\n');
        console.log(req.csrfToken());
    })
    .listen(3000);

生成的csrf Token:

1YZ72JuGRTOh/mzqByktPoyz2C+Dk1E5wXyj0=
GET / 200 356ms
bItSjAAXydK4jkYYLDnc1c0+7AGDFwGX6r8ns=
GET /favicon.ico 200 3ms

##8.15 static## 描述: 靜態文件處理中間件,設置root路徑做爲靜態文件服務器

參數:options:

maxAge:瀏覽器緩存存活時間(毫秒),默認值0

hidden:是否容許傳遞隱藏類型的文件,默認值false

redirect:是否容許當訪問名是一個目錄,結尾增長」/」,默認值true

index:設置默認的文件名,默認值index.html

例子:新建static.js

var connect = require('connect');
var app = connect()
    .use(connect.logger('dev'))
    .use(connect.static(__dirname+'/static',{maxAge:60*60*1000,hidden:false}))
    .use(function (req, res) {
        res.setHeader('Content-Type', 'text/html');
        res.write('static:');
        res.write('<img src="static.png" width="100px"/>');
        res.write('hidden:');
        res.end('<img src=".png" width="100px"/>');
    })
    .listen(3000);

隱藏類型的文件,沒有被顯示出來。

輸入圖片說明

##8.16 staticCache## 描述: 靜態文件緩存中間件,最大能夠緩存128個對象,每一個對象最大256K,總加起來32mb。緩存算法採用LRU(最近最少使用)算法,讓最活躍的對象保存在緩存中,從而增長命中。

依賴:static()

參數:options

maxObjects:最多能緩存的對象,默認值128個

maxLength:最大緩存的對象,默認值256kb

例子:新建staticCache.js

var connect = require('connect');
var app = connect()
            .use(connect.logger('dev'))
            .use(connect.static(__dirname+'/static',{maxAge:60*60*1000,hidden:false}))
            .use(connect.staticCache())
            .use(function (req, res) {
                res.setHeader('Content-Type', 'text/html');
                res.write('static:');
                res.write('<img src="static.png" width="100px"/>');
                res.write('hidden:');
                res.end('<img src=".png" width="100px"/>');
            }).listen(3000);

控制檯日誌:

connect.staticCache() is deprecated and will be removed in 3.0
use varnish or similar reverse proxy caches.
GET / 200 11ms
GET /.png 200 0ms

建議用varnish或專門的緩存工具,來代替staticCache()。

##8.17 directory## 描述: 目錄列表中間件,列出指定目錄下的文件

參數:options:

hidden:是否顯示隱藏文件,默認值false.

icons:是否顯示網站圖標,默認值false.

filter:是否過濾文件,默認值false.

例子:新建directory.js

var connect = require('connect');
var app = connect()
    .use(connect.logger('dev'))
    .use(connect.directory(__dirname+'/static',{hidden:true}))
    .use(function (req, res) {
        res.end();
    })
    .listen(3000);

輸入圖片說明

##8.18 vhost## 沒法模擬出效果,暫時先跳過

描述: 虛擬二級域名映射中間件,設置hostname和server。

例子:新建vhost.js

var connect = require('connect');
var app = connect();
app.use(connect.logger('dev'));
app.use(function (req, res) {
    res.end(JSON.stringify(req.headers.host));
});

var fooApp = connect();
fooApp.use(connect.logger('dev'));
fooApp.use(function (req, res) {
    res.end('hello fooApp\n');
});

var barApp = connect();
barApp.use(connect.logger('dev'));
barApp.use(function (req, res) {
    res.end('hello barApp\n');
});

app.use(connect.vhost('foo.com', fooApp));
app.use(connect.vhost('bar.com', barApp));

app.listen(3000);

##8.19 favicon## 描述:網頁圖標中間件,指定favicon的路徑

參數:options:

maxAge:緩存存活時間(毫秒),默認值1天

例子:新建favicon.js

var connect = require('connect');
var app = connect()
    .use(connect.favicon('static/favicon.ico'))
    .use(connect.logger('dev'))
    .use(function (req, res) {
        res.end('hello world\n');
    })
    .listen(3000);

網站圖標

輸入圖片說明

##8.20 limit## 描述: 請求內容大小限制中間件,能夠用mb,kb,gb表示單位。

例子:新建limit.js

var connect = require('connect');
var app = connect()
            .use(connect.logger('dev'))
            .use(connect.limit('1mb'))
            .use(connect.multipart({ uploadDir: 'd:\\tmp' }))
            .use(function (req, res) {
                if(req.method=='POST'){
                    console.log(req.files);
                    res.end('Upload ==>'+ req.files.file.path);
                }
                res.setHeader('Content-Type', 'text/html');
                res.write('<form enctype="multipart/form-data" method="POST"><input type="file" name="file">');
                res.write('<input type="submit" value="submit"/>');
                res.write('</form>');
                res.end('hello world\n');
            }).listen(3000);

控制檯日誌,上傳大於1mb的文件。

GET / 200 8ms
Error: Request Entity Too Large
    at Object.exports.error (D:\workspace\javascript\nodejs-connect\node_modules\connect\lib\utils.js:62:13)
    at Object.limit [as handle] (D:\workspace\javascript\nodejs-connect\node_modules\connect\lib\middleware\limit.js:46:
47)
    at next (D:\workspace\javascript\nodejs-connect\node_modules\connect\lib\proto.js:190:15)
    at Object.logger (D:\workspace\javascript\nodejs-connect\node_modules\connect\lib\middleware\logger.js:156:5)
    at next (D:\workspace\javascript\nodejs-connect\node_modules\connect\lib\proto.js:190:15)
    at Function.app.handle (D:\workspace\javascript\nodejs-connect\node_modules\connect\lib\proto.js:198:3)
    at Server.app (D:\workspace\javascript\nodejs-connect\node_modules\connect\lib\connect.js:65:37)
    at Server.EventEmitter.emit (events.js:98:17)
    at HTTPParser.parser.onIncoming (http.js:2027:12)
    at HTTPParser.parserOnHeadersComplete [as onHeadersComplete] (http.js:119:23)
POST / 413 8ms - 961b

##8.21 query## 描述:URL解析中間件,自動解析URL查詢參數req.query

參數:options,qs.parse函數

例子:新建query.js

var connect = require('connect');
var app = connect()
    .use(connect.query())
    .use(connect.logger('dev'))
    .use(function (req, res) {
        res.end(JSON.stringify(req.query));
    })
    .listen(3000);

經過CURL測試:

curl -d '{name:'aad'}' http://localhost:3000?pass=did
{"pass":"did"}

req.query會自動解析URL的查詢參數,不解析POST的數據。

##8.22 errorHadnler## 沒法模擬出效果,暫時先跳過

描述:錯誤處理中間件,對於開發過程當中的錯誤,提供棧跟蹤和錯誤響應,接受3種類型text,html,json。

Text: text/plain是默認類型,返回一個簡單的棧跟蹤和錯誤消息

JSON:application/json,返回{‘error’:error}對象

HTML: 返回一個HTML錯誤頁面

參數:options:

showStack:返回錯誤信息和錯誤棧.默認值false

showMessage,只返回錯誤信息,默認值false

dumpExceptions, 輸出異常日誌,默認值false

logErrors,輸出錯誤日誌到文件,默認值false

例子:新建errorHadnler.js

var connect = require('connect');
var app = connect()
    .use(connect.logger())
    .use(connect.errorHandler({dumpExceptions: true, showStack: true }))
    .use(function (req, res) {
        req.headers.accept='html';
        res.write(JSON.stringify(req.headers.accept));
        throw new Error('my errorHandler!!!');
        res.end('Hello');
    })
    .listen(3000);

控制檯輸出:

rror: my errorHandler!!!
    at Object.handle (D:\workspace\javascript\nodejs-connect\errorHadnler.js:8:15)
    at next (D:\workspace\javascript\nodejs-connect\node_modules\connect\lib\proto.js:190:15)
    at next (D:\workspace\javascript\nodejs-connect\node_modules\connect\lib\proto.js:192:9)
    at Object.logger (D:\workspace\javascript\nodejs-connect\node_modules\connect\lib\middleware\logger.js:156:5)
    at next (D:\workspace\javascript\nodejs-connect\node_modules\connect\lib\proto.js:190:15)
    at Function.app.handle (D:\workspace\javascript\nodejs-connect\node_modules\connect\lib\proto.js:198:3)
    at Server.app (D:\workspace\javascript\nodejs-connect\node_modules\connect\lib\connect.js:65:37)
    at Server.EventEmitter.emit (events.js:98:17)
    at HTTPParser.parser.onIncoming (http.js:2027:12)
    at HTTPParser.parserOnHeadersComplete [as onHeadersComplete] (http.js:119:23)
相關文章
相關標籤/搜索