本篇文章從express源碼上來理解中間件,同時能夠對express有更深層的理解javascript
中間件函數能夠執行哪些任務?java
- 執行任何代碼。
- 對請求和響應對象進行更改。
- 結束請求/響應循環。
- 調用堆棧中的下一個中間件函數。
咱們從一個app.use
開始,逐步分析到下一個中間件函數的執行。git
首先從github上下載express
源碼。github
創建一個文件test.js
文件,引入根目錄的index.js
文件,實例化express
,啓動服務器。正則表達式
let express = require('../index.js');
let app = express()
function middlewareA(req, res, next) {
console.log('A1');
next();
console.log('A2');
}
function middlewareB(req, res, next) {
console.log('B1');
next();
console.log('B2');
}
function middlewareC(req, res, next) {
console.log('C1');
next();
console.log('C2');
}
app.use(middlewareA);
app.use(middlewareB);
app.use(middlewareC);
app.listen(8888, () => {
console.log("服務器已經啓動訪問http://127.0.0.1:8888");
})
複製代碼
啓動服務器,經過訪問http://127.0.0.1:8888
服務,打開終端,看看終端日誌執行順序。express
從日誌咱們能夠看出,每次next()
以後,都會按照順序依次調用下中間件函數,而後按照執行順序依次打印A1,B1,C1
,此時中間件已經調用完成,再依次打印C2,B2,A2
。npm
--lib
|__ middleware
|__ init.js
|__ query.js
|__ router
|__ index.js
|__ layer.js
|__ route.js
|__ application.js
|__ express.js
|__ request.js
|__ response.js
|__ utils.js
|__ view.js
複製代碼
經過實例化的express,咱們能夠看到,index.js
文件其實是暴露出lib/express
的文件。json
express
,經過mixin
繼承appLication,同時初始化application。數組
function createApplication() {
var app = function(req, res, next) {
app.handle(req, res, next);
};
mixin(app, EventEmitter.prototype, false);
mixin(app, proto, false);
// expose the prototype that will get set on requests
app.request = Object.create(req, {
app: { configurable: true, enumerable: true, writable: true, value: app }
})
// expose the prototype that will get set on responses
app.response = Object.create(res, {
app: { configurable: true, enumerable: true, writable: true, value: app }
})
app.init();
return app;
}
複製代碼
而mixin是merge-descriptors
npm模塊。Merge objects using descriptors.。bash
打開application.js
文件,發現express
的實例化源自var app = exports = module.exports = {}
。
進一步搜索app.use
,找到app.use
,而app.use
又只是嚮應用程序路由器添加中間件的Proxy
。
/** * Proxy `Router#use()` to add middleware to the app router. * See Router#use() documentation for details. * * If the _fn_ parameter is an express app, then it will be * mounted at the _route_ specified. * * @public */
app.use = function use(fn) {
var offset = 0;
var path = '/';
// 默認path 爲 '/'
// app.use([fn])
//判斷app.use傳進來的是不是函數
if (typeof fn !== 'function') {
var arg = fn;
while (Array.isArray(arg) && arg.length !== 0) {
arg = arg[0];
}
// 第一個參數是路徑
//取出第一個參數,將第一個參數賦值給path。
if (typeof arg !== 'function') {
offset = 1;
path = fn;
}
}
//slice.call(arguments,offset),經過slice轉爲數據,slice能夠改變具備length的類數組。
//arguments是一個類數組對象。
//處理多種中間件使用方式。
// app.use(r1, r2);
// app.use('/', [r1, r2]);
// app.use(mw1, [mw2, r1, r2], subApp);
var fns = flatten(slice.call(arguments, offset));//[funtion]
//拋出錯誤
if (fns.length === 0) {
throw new TypeError('app.use() requires a middleware function')
}
//設置router
this.lazyrouter();
var router = this._router;
fns.forEach(function (fn) {
// 處理不是express的APP應用的狀況,直接調用route.use。
if (!fn || !fn.handle || !fn.set) {
//path default to '/'
return router.use(path, fn);
}
debug('.use app under %s', path);
fn.mountpath = path;
fn.parent = this;
router.use(path, function mounted_app(req, res, next) {
var orig = req.app;
fn.handle(req, res, function (err) {
setPrototypeOf(req, orig.request)
setPrototypeOf(res, orig.response)
next(err);
});
});
// app mounted 觸發emit
fn.emit('mount', this);
}, this);
return this;
};
複製代碼
定義默認參數offer
和path
。而後處理fn
形參不一樣類型的狀況。將不一樣類型的中間件使用方式的形參轉爲扁平化數組,賦值給fns
。
forEach
遍歷fns,判斷若是fn
、fn.handle
、fn.set
參數不存在,return出去router.use(path, fn)
。
不然繼續執行router.use
。
handle
函數,執行中間件。代碼以下:
/** *將一個req, res對分派到應用程序中。中間件執行開始。 *若是沒有提供回調,則默認錯誤處理程序將做出響應 *在堆棧中冒泡出現錯誤時。 */
app.handle = function handle(req, res, callback) {
var router = this._router;
// 最後報錯處理error。
var done = callback || finalhandler(req, res, {
env: this.get('env'),
onerror: logerror.bind(this)
});
// no routes
if (!router) {
debug('no routes defined on app');
done();
return;
}
router.handle(req, res, done);
};
複製代碼
從上述代碼中能夠知道,app.use
的做用其實是將各類應用函數傳遞給router
的一箇中間層代理。
並且,在app.use中有調用this.lazyrouter()
函數,惰性的添加默認router
。
app.lazyrouter = function lazyrouter() {
if (!this._router) {
this._router = new Router({
caseSensitive: this.enabled('case sensitive routing'),
strict: this.enabled('strict routing')
});
this._router.use(query(this.get('query parser fn')));
//初始化router
this._router.use(middleware.init(this));
}
};
複製代碼
這裏對Router
進行了實例化,同時設置基本的option
,caseSensitive
是否區分大小寫,strict
是否設置嚴格模式。
Router
初始化以下:
/**
* 用給定的「選項」初始化一個新的「路由器」。
*
* @param {Object} [options] [{ caseSensitive: false, strict: false }]
* @return {Router} which is an callable function
* @public
*/
var proto = module.exports = function(options) {
var opts = options || {};
function router(req, res, next) {
router.handle(req, res, next);
}
// 混合路由器類函數
setPrototypeOf(router, proto)
router.params = {};
router._params = [];
router.caseSensitive = opts.caseSensitive;
router.mergeParams = opts.mergeParams;
router.strict = opts.strict;
router.stack = [];
return router;
};
複製代碼
調用app.use
時,參數都會傳遞給router.use
,所以,打開router/index.js
文件,查找router.use
。
/** *使用給定的中間件函數,具備可選路徑,默認爲「/」。 * Use(如' .all ')將用於任何http方法,但不會添加 *這些方法的處理程序,因此選項請求不會考慮「。use」 *函數,即便它們能夠響應。 *另外一個區別是_route_ path被剝離,不可見 *處處理程序函數。這個特性的主要做用是安裝 *不管「前綴」是什麼,處理程序均可以在不更改任何代碼的狀況下操做 *路徑名。 * * @public */
proto.use = function use(fn) {
var offset = 0;
var path = '/';
// 默認路徑 '/'
// 消除歧義 router.use([fn])
// 判斷是不是函數
if (typeof fn !== 'function') {
var arg = fn;
while (Array.isArray(arg) && arg.length !== 0) {
arg = arg[0];
}
// 第一個參數是函數
if (typeof arg !== 'function') {
offset = 1;
path = fn;
}
}
//將arguments轉爲數組,而後扁平化多維數組
var callbacks = flatten(slice.call(arguments, offset));
//若是callbacks內沒有傳遞函數,拋錯
if (callbacks.length === 0) {
throw new TypeError('Router.use() requires a middleware function')
}
//循環callbacks數組
for (var i = 0; i < callbacks.length; i++) {
var fn = callbacks[i];
if (typeof fn !== 'function') {
throw new TypeError('Router.use() requires a middleware function but got a ' + gettype(fn))
}
//解析下query和expressInit的含義
// 添加中間件
//匿名 anonymous 函數
debug('use %o %s', path, fn.name || '<anonymous>')
var layer = new Layer(path, {
sensitive: this.caseSensitive, //敏感區分大小寫 //默認爲false
strict: false, //嚴格
end: false //結束
}, fn);
layer.route = undefined;
this.stack.push(layer);
}
return this;
}
複製代碼
router.use
的主要做用就是將從app.use
中傳遞過來的函數,經過Layer
實例化的處理,添加一些處理錯誤、處理請求的方法,以便後續調用處理。同時將傳遞過來的path
,經過path-to-regexp
模塊把路徑轉爲正則表達式(this.regexp
),調用this.regexp.exec(path)
,將參數提取出來。
Layer
代碼較多,這裏不貼代碼了,能夠參考express/lib/router/layer.js。
處理中間件就是將放入this,stack
的new Layout([options],fn)
,拿出來依次執行。
proto.handle = function handle(req, res, out) {
var self = this;
debug('dispatching %s %s', req.method, req.url);
var idx = 0;
//獲取協議與URL地址
var protohost = getProtohost(req.url) || ''
var removed = '';
//是否添加斜槓
var slashAdded = false;
var paramcalled = {};
//存儲選項請求的選項
//僅在選項請求時使用
var options = [];
// 中間件和路由
var stack = self.stack;
// 管理inter-router變量
//req.params 請求參數
var parentParams = req.params;
var parentUrl = req.baseUrl || '';
var done = restore(out, req, 'baseUrl', 'next', 'params');
// 設置下一層
req.next = next;
// 對於選項請求,若是沒有其餘響應,則使用默認響應
if (req.method === 'OPTIONS') {
done = wrap(done, function(old, err) {
if (err || options.length === 0) return old(err);
sendOptionsResponse(res, options, old);
});
}
// 設置基本的req值
req.baseUrl = parentUrl;
req.originalUrl = req.originalUrl || req.url;
next();
function next(err) {
var layerError = err === 'route'
? null
: err;
//是否添加斜線 默認false
if (slashAdded) {
req.url = req.url.substr(1);
slashAdded = false;
}
// 恢復改變req.url
if (removed.length !== 0) {
req.baseUrl = parentUrl;
req.url = protohost + removed + req.url.substr(protohost.length);
removed = '';
}
// 出口路由器信號
if (layerError === 'router') {
setImmediate(done, null)
return
}
// 再也不匹配圖層
if (idx >= stack.length) {
setImmediate(done, layerError);
return;
}
// 獲取路徑pathname
var path = getPathname(req);
if (path == null) {
return done(layerError);
}
// 找到下一個匹配層
var layer;
var match;
var route;
while (match !== true && idx < stack.length) {
layer = stack[idx++];
//try layer.match(path) catch err
//搜索 path matchLayer有兩種狀態一種是boolean,一種是string。
match = matchLayer(layer, path);
route = layer.route;
if (typeof match !== 'boolean') {
layerError = layerError || match;
}
if (match !== true) {
continue;
}
if (!route) {
//正常處理非路由處理程序
continue;
}
if (layerError) {
// routes do not match with a pending error
match = false;
continue;
}
var method = req.method;
var has_method = route._handles_method(method);
// build up automatic options response
if (!has_method && method === 'OPTIONS') {
appendMethods(options, route._options());
}
// don't even bother matching route
if (!has_method && method !== 'HEAD') {
match = false;
continue;
}
}
// no match
if (match !== true) {
return done(layerError);
}
//從新賦值router。
if (route) {
req.route = route;
}
// 合併參數
req.params = self.mergeParams
? mergeParams(layer.params, parentParams)
: layer.params;
var layerPath = layer.path;
// 處理參數
self.process_params(layer, paramcalled, req, res, function (err) {
if (err) {
return next(layerError || err);
}
if (route) {
return layer.handle_request(req, res, next);
}
// 處理req.url和layerPath,同時對layer中的請求error和handle_error加tryCatch處理。
trim_prefix(layer, layerError, layerPath, path);
});
}
複製代碼
執行proto.handle
中間件也就的while
循環中的一些核心代碼,每次調用app.use
中的回調函數中的next()
都會讓idx
加一,將stack[idx++];
賦值給layer
,調用一開始說到的layer.handle_request
,而後調用trim_prefix(layer, layerError, layerPath, path)
,添加一些報錯處理。
trim_prefix
函數以下:
function trim_prefix(layer, layerError, layerPath, path) {
if (layerPath.length !== 0) {
// Validate path breaks on a path separator
var c = path[layerPath.length]
if (c && c !== '/' && c !== '.') return next(layerError)
// //刪除url中與路由匹配的部分
// middleware (.use stuff) needs to have the path stripped
debug('trim prefix (%s) from url %s', layerPath, req.url);
removed = layerPath;
req.url = protohost + req.url.substr(protohost.length + removed.length);
// Ensure leading slash
if (!protohost && req.url[0] !== '/') {
req.url = '/' + req.url;
slashAdded = true;
}
// 設置 base URL (no trailing slash)
req.baseUrl = parentUrl + (removed[removed.length - 1] === '/'
? removed.substring(0, removed.length - 1)
: removed);
}
debug('%s %s : %s', layer.name, layerPath, req.originalUrl);
if (layerError) {
layer.handle_error(layerError, req, res, next);
} else {
layer.handle_request(req, res, next);
}
}
};
複製代碼
以上就是經過app.use
調用以後,一步步執行中間件函數router.handle
。
next
核心代碼很簡單,可是須要考慮的場景倒是不少,經過此次源碼閱讀,更能進一步的理解express的核心功能。
雖然說日常作項目用到express
框架不多,或者能夠說基本不用,通常都是用Koa
或者Egg
,能夠說基本上些規模的場景的項目用的都是Egg
。
可是不能否認得是,express
框架仍是一款很是經典的框架。
以上代碼純屬我的理解,若有不合適的地方,望在評論區留言。