nodejs模塊學(xué)習(xí)之connect解析
nodejs 發(fā)展很快,從 npm 上面的包托管數(shù)量就可以看出來。不過從另一方面來看,也是反映了 nodejs 的基礎(chǔ)不穩(wěn)固,需要開發(fā)者創(chuàng)造大量的輪子來解決現(xiàn)實(shí)的問題。
知其然,并知其所以然這是程序員的天性。所以把常用的模塊拿出來看看,看看高手怎么寫的,學(xué)習(xí)其想法,讓自己的技術(shù)能更近一步。
引言
express 是 nodejs 中最流行的 web 框架。express 中對(duì) http 中的 request 和 response 的處理,還有以中間件為核心的處理流程,非常靈活,足以應(yīng)對(duì)任何業(yè)務(wù)的需求。
而 connect 曾經(jīng)是 express 3.x 之前的核心,而 express 4.x 已經(jīng)把 connect 移除,在 express 中自己實(shí)現(xiàn)了 connect 的接口??梢哉f connect 造就了 express 的靈活性。
因此,我很好奇,connect 是怎么寫的。
爭(zhēng)取把每一行代碼都弄懂。
connect 解析
我們要先從 connect 的官方例子開始
var connect = require( 'connect' );
var http = require( 'http' );
var app = connect();
// gzip/deflate outgoing responses
var compression = require( 'compression' );
app.use(compression());
// store session state in browser cookie
var cookieSession = require( 'cookie-session' );
app.use(cookieSession({
keys: [ 'secret1' , 'secret2' ]
}));
// parse urlencoded request bodies into req.body
var bodyParser = require( 'body-parser' );
app.use(bodyParser.urlencoded({extended: false }));
// respond to all requests
app.use( function (req, res){
res.end( 'Hello from Connect!\n' );
});
//create node.js http server and listen on port
http.createServer(app).listen(3000);
從示例中可以看到一個(gè)典型的 connect 的使用:
var app = connect() // 初始化
app.use( function (req, res, next) {
// do something
})
// http 服務(wù)器,使用
http.createServer(app).listen(3000);
先倒著看,從調(diào)用的地方更能看出來,模塊怎么使用的。我們就先從 http.createServer(app) 來看看。
從 nodejs doc 的官方文檔中可以知, createServer 函數(shù)的參數(shù)是一個(gè)回調(diào)函數(shù),這個(gè)回調(diào)函數(shù)是用來響應(yīng) request 事件的。從這里看出,示例代碼中 app 中函數(shù)簽就是 (req, res) ,也就是說 app 的接口為 function (req, res) 。
但是從示例代碼中,我們也可以看出 app 還有一個(gè) use 方法。是不是覺得很奇怪,js 中函數(shù)實(shí)例上,還以帶方法,這在 js 中就叫 函數(shù)對(duì)象,不僅能調(diào)用,還可以帶實(shí)例變量。給個(gè)例子可以看得更清楚:
function handle () {
function app(req, res, next) { app.handle(req, res, next)}
app.handle = function (req, res, next) {
console.log( this );
}
app.statck = [];
return app;
}
var app = handle();
app() // ==> { [Function: app] handle: [Function], stack: [] }
app.apply({}) // ==>{ [Function: app] handle: [Function], stack: [] }
可以看出:函數(shù)中的實(shí)例函數(shù)中的 this 就是指當(dāng)前的實(shí)例,不會(huì)因?yàn)槟闶褂?apply 進(jìn)行環(huán)境改變。
其他就跟對(duì)象沒有什么區(qū)別。
再次回到示例代碼,因該可以看懂了, connect 方法返回了一個(gè)函數(shù),這個(gè)函數(shù)能直接調(diào)用,有 use 方法,用來響應(yīng) http 的 request 事件。
到此為此,示例代碼就講完了。 我們開始進(jìn)入到 connect 模塊的內(nèi)部。
connect 只有一個(gè)導(dǎo)出方法。就是如下:
var merge = require( 'utils-merge' );
module.exports = createServer;
var proto = {};
function createServer() {
// 函數(shù)對(duì)象,這個(gè)對(duì)象能調(diào)用,能加屬性
function app(req, res, next){ app.handle(req, res, next); }
merge(app, proto); // ===等于調(diào)用 Object.assign
merge(app, EventEmitter.prototype); // === 等于調(diào)用 Object.assign
app.route = '/' ;
app.stack = [];
return app;
}
從代碼中可以看出,createServer 函數(shù)把 app 函數(shù)返回了,app 函數(shù)有三個(gè)參數(shù),多了一個(gè) next (這個(gè)后面講),app函數(shù)把 proto 的方法合并了。還有 EventEmitter 的方法也合并了,還增加了 route 和 stack 的屬性。
從前面代碼來看,響應(yīng) request 的事件的函數(shù),是 app.handle 方法。這個(gè)方法如下:
proto.handle = function handle(req, res, out) {
var index = 0;
var protohost = getProtohost(req.url) || '' ; //獲得 http://www.baidu.com
var removed = '' ;
var slashAdded = false ;
var stack = this .stack;
// final function handler
var done = out || finalhandler(req, res, {
env: env,
onerror: logerror
}); // 接口 done(err);
// store the original URL
req.originalUrl = req.originalUrl || req.url;
function next(err) {
if (slashAdded) {
req.url = req.url.substr(1); // 除掉 / 之后的字符串
slashAdded = false ; // 已經(jīng)拿掉
}
if (removed.length !== 0) {
req.url = protohost + removed + req.url.substr(protohost.length);
removed = '' ;
}
// next callback
var layer = stack[index++];
// all done
if (!layer) {
defer(done, err); // 沒有中間件,調(diào)用 finalhandler 進(jìn)行處理,如果 err 有值,就返回 404 進(jìn)行處理
return ;
}
// route data
var path = parseUrl(req).pathname || '/' ;
var route = layer.route;
// skip this layer if the route doesn't match
if (path.toLowerCase().substr(0, route.length) !== route.toLowerCase()) {
return next(err); // 執(zhí)行下一個(gè)
}
// skip if route match does not border "/", ".", or end
var c = path[route.length];
if (c !== undefined && '/ ' !== c && ' . ' !== c) {
return next(err); // 執(zhí)行下一個(gè)
}
// trim off the part of the url that matches the route
if (route.length !== 0 && route !== ' / ') {
removed = route;
req.url = protohost + req.url.substr(protohost.length + removed.length);
// ensure leading slash
if (!protohost && req.url[0] !== ' / ') {
req.url = ' /' + req.url;
slashAdded = true ;
}
}
// call the layer handle
call(layer.handle, route, err, req, res, next);
}
next();
};
代碼中有相應(yīng)的注釋,可以看出,next 方法就是一個(gè)遞歸調(diào)用,不斷的對(duì)比 route 是否匹配,如果匹配則調(diào)用 handle, 如果不匹配,則調(diào)用下一個(gè) handle.
call 函數(shù)的代碼如下:
function call(handle, route, err, req, res, next) {
var arity = handle.length;
var error = err;
var hasError = Boolean(err);
debug( '%s %s : %s' , handle.name || '<anonymous>' , route, req.originalUrl);
try {
if (hasError && arity === 4) {
// error-handling middleware
handle(err, req, res, next);
return ;
} else if (!hasError && arity < 4) {
// request-handling middleware
handle(req, res, next);
return ;
}
} catch (e) {
// replace the error
error = e;
}
// continue
next(error);
}
可以看出一個(gè)重點(diǎn):對(duì)錯(cuò)誤處理,connect 的要求 是函數(shù)必須是 四個(gè)參數(shù),而 express 也是如此。如果有錯(cuò)誤, 中間件沒有一個(gè)參數(shù)的個(gè)數(shù)是 4, 就會(huì)錯(cuò)誤一直傳下去,直到后面的 defer(done, err); 進(jìn)行處理。
還有 app.use 添加中間件:
proto.use = function use(route, fn) {
var handle = fn; // fn 只是一個(gè)函數(shù)的話 三種接口 // 1. err, req, res, next 2. req, res, 3, req, res, next
var path = route;
// default route to '/'
if ( typeof route !== 'string' ) {
handle = route;
path = '/' ;
}
// wrap sub-apps
if ( typeof handle.handle === 'function' ) { // 自定義中的函數(shù)對(duì)象
var server = handle;
server.route = path;
handle = function (req, res, next) { // req, res, next 中間件
server.handle(req, res, next);
};
}
// wrap vanilla http.Servers
if (handle instanceof http.Server) {
handle = handle.listeners( 'request' )[0]; // (req, res) // 最后的函數(shù)
}
// strip trailing slash
if (path[path.length - 1] === '/' ) {
path = path.slice(0, -1);
}
// add the middleware
debug( 'use %s %s' , path || '/' , handle.name || 'anonymous' );
this .stack.push({ route: path, handle: handle });
return this ;
};
從代碼中,可以看出,use 方法添加中間件到 this.stack 中,其中 fn 中間件的形式有兩種: function (req, res, next) 和 handle.handle(req, res, next) 這兩種都可以。還有對(duì) fn 情況進(jìn)行特殊處理。
總的處理流程就是這樣,用 use 方法添加中間件,用 next 編歷中間件,用 finalHandle 進(jìn)行最后的處理工作。
在代碼中還有一個(gè)函數(shù)非常奇怪:
/* istanbul ignore next */
var defer = typeof setImmediate === 'function'
? setImmediate
: function (fn){ process.nextTick(fn.bind.apply(fn, arguments)) }
defer 函數(shù)中的 fn.bind.apply(fn, arguments) ,這個(gè)方法主要解決了,一個(gè)問題,不定參的情況下,第一個(gè)參數(shù)函數(shù),怎樣拿到的問題,為什么這樣說呢?如果中我們要達(dá)到以上的效果,需要多多少行代碼?
function () {
var cb = Array.from(arguments)[0];
var args = Array.from(arguments).splice(1);
process.nextTick( function () {
cb.apply( null ,args);
})
}
這還是 connect 兼容以前的 es5 之類的方法。如果在 es6 下面,方法可以再次簡(jiǎn)化
function (..args){ process.nextTick(fn.bind(...args)) }
總結(jié)
connect 做為 http 中間件模塊,很好地解決對(duì) http 請(qǐng)求的插件化處理的需求,把中間件組織成請(qǐng)求上的一個(gè)處理器,挨個(gè)調(diào)用中間件對(duì) http 請(qǐng)求進(jìn)行處理。
其中 connect 的遞歸調(diào)用,和對(duì) js 的函數(shù)對(duì)象的使用,讓值得學(xué)習(xí),如果讓我來寫,就第一個(gè)調(diào)個(gè)的地方,就想不到使用 函數(shù)對(duì)象 來進(jìn)行處理。
而且 next 的設(shè)計(jì)如此精妙,整個(gè)框架的使用和概念上,對(duì)程序員基本上沒有認(rèn)知負(fù)擔(dān),這才是最重要的地方。這也是為什么 express 框架最受歡迎。koa 相比之下,多幾個(gè)概念,還使用了不常用的 yield 方法。
connect 的設(shè)計(jì)理念可以用在,類似 http 請(qǐng)求模式上, 如 rpc, tcp 處理等。
我把 connect 的設(shè)計(jì)方法叫做 中間件模式,對(duì)處理 流式模式,會(huì)有較好的效果。
以上就是本文的全部?jī)?nèi)容,希望對(duì)大家的學(xué)習(xí)有所幫助,也希望大家多多支持腳本之家。
- 從零開始學(xué)習(xí)Node.js系列教程之基于connect和express框架的多頁面實(shí)現(xiàn)數(shù)學(xué)運(yùn)算示例
- 解決Node.js使用MySQL出現(xiàn)connect ECONNREFUSED 127.0.0.1:3306的問題
- Node.js connect ECONNREFUSED錯(cuò)誤解決辦法
- NodeJS學(xué)習(xí)筆記之Connect中間件應(yīng)用實(shí)例
- NodeJS學(xué)習(xí)筆記之Connect中間件模塊(二)
- NodeJS學(xué)習(xí)筆記之Connect中間件模塊(一)
- 分析Node.js connect ECONNREFUSED錯(cuò)誤
相關(guān)文章
輕松創(chuàng)建nodejs服務(wù)器(1):一個(gè)簡(jiǎn)單nodejs服務(wù)器例子
這篇文章主要介紹了一個(gè)簡(jiǎn)單nodejs服務(wù)器例子,本文實(shí)現(xiàn)了一個(gè)簡(jiǎn)單的hello world例子,并展示如何運(yùn)行這個(gè)服務(wù)器,需要的朋友可以參考下2014-12-12
詳解通過源碼解析Node.js中cluster模塊的主要功能實(shí)現(xiàn)
這篇文章主要介紹了詳解通過源碼解析Node.js中cluster模塊的主要功能實(shí)現(xiàn),小編覺得挺不錯(cuò)的,現(xiàn)在分享給大家,也給大家做個(gè)參考。一起跟隨小編過來看看吧2018-05-05
async/await與promise(nodejs中的異步操作問題)
這篇文章主要介紹了async/await與promise(nodejs中的異步操作問題),非常不錯(cuò),具有參考借鑒價(jià)值,需要的朋友可以參考下2017-03-03
node.js 基于cheerio的爬蟲工具的實(shí)現(xiàn)(需要登錄權(quán)限的爬蟲工具)
這篇文章主要介紹了node.js 基于cheerio的爬蟲工具的實(shí)現(xiàn)(需要登錄權(quán)限的爬蟲工具) ,需要的朋友可以參考下2019-04-04
基于Node.js實(shí)現(xiàn)一鍵生成個(gè)性化二維碼
這篇文章主要為大家詳細(xì)介紹了如何使用Node.js、Jimp和QRCode庫,結(jié)合一個(gè)簡(jiǎn)單的腳本,通過命令行命令來快速給二維碼加上指定的背景,打造更有個(gè)性化的二維碼,感興趣的可以了解下2024-03-03
深入理解Node.js中CORS的三個(gè)重要響應(yīng)頭
CORS是一種安全機(jī)制,通過配置適當(dāng)?shù)捻憫?yīng)頭,服務(wù)器可以允許或限制外部域?qū)Y源的訪問,本文主要介紹了Node.js中CORS的三個(gè)重要響應(yīng)頭,感興趣的可以了解一下2024-12-12

