connect是一個web server中間件。
使用方法:
var connect = require('connect');
connect(
connect.static(__dirname + '/public', { maxAge: 0 })
, function(req, res) {
res.setHeader('Content-Type', 'text/html');
res.end('<img src="/tobi.jpeg" />')
}
).listen(3000);
思路:
通過connect創建一個http|https server,提供http server的所有功能。
connect是原型繼承於http server的,它會用use到的中間件替換掉server的requestListener。
通過connect.use(route, handle)來對每一個路由添加中間件,這些中間件handle會與route綁定保存在一個stack裡面,每次有request請求的時候,遍歷這個堆,找到對應route的handle,執行handle,如果handle最後調用了next(),就會繼續尋找並執行下一個匹配的handle。
通過封裝handle,可以很容易的在connect基礎上添加更多的middleware。
connect.js
有一個createServer方法,可以通過connect()訪問到。根據第一個參數,如果是object,就當作是https的選項,創建HTTPSServer,如果第一個參數不是object,則創建HTTPServer,所有的參數(除了https的選項)都是一個中間件handle,會在HTTPServer綁定到‘/’路徑上。
HTTPSServer是在HTTPServer的基礎上添加了一層,可以啟用HTTPS服務。
同時,connect.js會讀取middleware文件夾,把裡面的中間件讀取到,為他們創建getter,可以通過connect.static()訪問到。
而每一個中間件文件暴露在外的函數都是返回一個handle。
http.js
HTTPServer:初始化的時候會把所有的參數當作handle存放進stack,然後以handle方法為requestListener調用http.Server方法。
HTTPServer隨後會繼承http.Server的原型。
use(route, handle)
把handle去除外殼之後綁定到route上面,存入stack中。
handle(req, res, next)
遍歷整個stack,尋找到req.url與route匹配的元素,執行它的handle。當所有的元素都遍歷完還有錯誤,則輸出。
util.js
這是一個工具包,裡面包含了用到的各種工具函數。
pause(obj)
把傳遞進來的obj對象的'data'和'end‘事件都保存下來,返回兩個函數:end():不再保存事件。resume():停止保存並把之前保存的事件釋放出去給obj再次捕獲,達到暫停這個obj對象的效果。(感覺可能會有bug,如果在這裡釋放的時候又有'data'或者'end'事件觸發會不會導致順序變亂?)
parseCookie(str)
把str以;或者,為分隔符分開。每一個都是一個cookie鍵值對,然後再以=分開。去除value的引號。每個鍵只能被取得一次。
中間件:
router
connect的route使用方法和express類似。
1 connect(connect.route(function(app){
2 app.get('/:id', middle1, middle2, cb);
3 app.post('/admin', cbpost);
4 }));
route.js內有一個_methods數組,存放所有的route請求方法名稱。(get/post/put/...)。
methods對象和routes對象根據_methods內的名稱,包含著響應的元素,如:methods['get'], routes['get']。
methods對象,根據_methods數組內的方法名稱,為每一個方法調用來一個生產函數,這個函數首先把routes對象內的成員賦值[],然後返回一個函數,這個函數用來產生routes的內容。
methods還有一個元素param,調用它可以為path中出現了某個param的時候設置對應的處理方法。
例如:
app.param('id', function(req, res, next, val){}),
當path中有param id出現的時候,會先調用這個注冊的函數再進行後面的操作。
在進行完上述對象的初始化之後,route模塊會進行fn.call(this, methods)的調用,即用methods作為參數調用傳遞進來的匿名函數。所以在app.get('/:id', cb, cb1);的時候,實際調用的是methods.get('/:id', cb, cb1),而methods.get即是之前生產函數的返回函數。
這個函數的處理:cb為這條路由的handle,middle1..middle2..等中間件函數將會存放在cb.middleware數組中(這裡會產生一個bug)。然後把'/'轉化成為正則對象,然後在轉化正則的時候,可能會遇到路徑裡面有:id等key,會把這些key存放到keys裡面。
最終的routes內將會多處一條routes['GET']的記錄:
GET:
[ { fn: [Object],
path: /^\/(?:([^\/]+?))\/?$/i,
keys: [Object],
orig: '/:id',
method: 'GET' } ]
剛才說會產生一個bug,是當有兩條以上的route以cb作為handle的時候:
app.get('/:id', middle1, middle2, cb);
app.get('/:id/test', middle3, cb);
因為最終的handle都是cb,此時cb的middleware數組會在第二次處理get的時候把第一次的覆蓋掉,造成第一次的middleware被替換。
至此,所有的准備工作完成了,然後會返回一個router函數作為handle。
實際request請求觸發的時候:
作為handle的router函數被調用,先通過match(req, routes, i)函數,查找req.method對應方法的route的path,與req.pathName匹配。找到路徑匹配的把這個route內的這個對象內的fn,同時把keys params method存放到fn裡面整合稱為一個route返回。返回的route內容形式為
{ [Function]
middleware: [ [Function], [Function] ],
keys: [ 'id' ],
method: 'GET',
params: [ id: 'ca' ] }
然後函數去尋找是否通過methods.param定義了這條route中的param的處理函數,如果有,在這裡就執行完對應param的處理函數。之後執行middleware數組內的函數,最後執行這個route。即上一段中說到的fn。這之中能夠鏈式執行下去的條件是中間函數都執行了next(),繼續調用下去,當然也可以其中某個函數就結束整個處理。
bodyParser
bodyParser用來解析post方法傳遞過來的參數。
只接受mime類型為
application/x-www-form-urlencoded
application/json
multipart/form-data
三種的非GET和HEAD請求。
application/x-www-form-urlencoded通過模塊qs.parse來解析。
application/json通過JSON.parse解析。
multipart/form-data是文件上傳,通過formidable解析。
static
static是一個靜態文件服務器。
connect.static(root, options)會產生一個handle,handle設置默認的options然後調用send函數。
options內容:
root:靜態服務器的根路徑,必須由connect.static傳入。
path:訪問的文件路徑
getOnly:訪問方法限制(默認是true:只允許get方法訪問 )
maxAge:時間限制
redirect:在訪問的路徑是目錄的時候,如果允許redirect,則會redirect到這個目錄下的index.html文件,默認為true
callback:在每次靜態服務之後調用的函數(包括發生錯誤,發生錯誤之後不會再調用next)。
hidden:是否允許訪問隱藏文件(默認為false)
根據這些參數來決定訪問限制。
支持conditional和range。
最終通過
var stream = fs.createReadStream(path, opts);
stream.pipe(res);
管道的方式來傳送文件內容。