#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
Pre-Request 一般用來改寫request的原始數據;
Request/Response 大部分中間件都在這裏,功能各異;
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實例進程中轉移到第三方成熟的緩存中去便可
。這能夠保證:
緩存內容不冗餘。
集中式緩存,減小不一致性的發生。
緩存的算法更優秀以保持較高的命中率。
讓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)