Express源码阅读笔记
使用了三年多的express,今天坐下来读一读源码
实例化
function createApplication() {
// 对应http.createServer(requestListener?: RequestListener)入参
// (req: IncomingMessage, res: ServerResponse) => void
// 也就是说所有的api请求,都从这里开始进入express
var app = function(req, res, next) {
app.handle(req, res, next);
};
// 继承EventEmitter
mixin(app, EventEmitter.prototype, false);
// 继承application 方法
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
// res 对应response.js, 常用的res.send(),res.json()等方法就在这里,后面会把这些方法加到请求的res中,参见lib/middleware/init.js expressInit 中间件
app.response = Object.create(res, {
app: { configurable: true, enumerable: true, writable: true, value: app }
})
app.init();
return app;
}
添加中间件
app.use() 和 router.use()
// 删除了部分不那么重要的代码
app.use = function use(fn) {
var offset = 0;
var path = '/';
if (typeof fn !== 'function') {
var arg = fn;
// 这行没懂,没遇到过这样use方式
while (Array.isArray(arg) && arg.length !== 0) {
arg = arg[0];
}
// first arg is the path
if (typeof arg !== 'function') {
offset = 1;
path = fn;
}
}
var fns = flatten(slice.call(arguments, offset));
// 初始化路由,加了两个系统自带的路由 expressInit 和 query(解析url,获取query参数)
this.lazyrouter();
var router = this._router;
fns.forEach(function (fn) {
// non-express app
if (!fn || !fn.handle || !fn.set) {
// 最终调用这个方法添加中间件
return router.use(path, fn);
}
// 以下什么时候会执行,不懂?
debug('.use app under %s', path);
fn.mountpath = path;
fn.parent = this;
// restore .app property on req and res
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);
});
});
// mounted an app
fn.emit('mount', this);
}, this);
return this;
};
疑惑点:1、为什么这样用slice.call(arguments, offset),
// router/index.js router.use()
proto.use = function use(fn) {
var offset = 0;
var path = '/';
// 删除一段代码,因为跟app.use()一样
var callbacks = flatten(slice.call(arguments, offset));
for (var i = 0; i < callbacks.length; i++) {
var fn = callbacks[i];
// 删除一段代码,判断fn是否是函数
var layer = new Layer(path, {
sensitive: this.caseSensitive,
strict: false,
end: false
}, fn);
// 关键代码,与router.route的区别所在
layer.route = undefined;
// 所有中间件都将加入到stack中
this.stack.push(layer);
}
return this;
};
添加路由
实例化Router
var proto = module.exports = function(options) {
var opts = options || {};
function router(req, res, next) {
router.handle(req, res, next); // 进入路由第一步执行
}
// mixin Router class functions
setPrototypeOf(router, proto)
router.params = {};
router._params = [];
router.caseSensitive = opts.caseSensitive;
router.mergeParams = opts.mergeParams;
router.strict = opts.strict;
router.stack = []; // 初始化了个stack哟
return router;
};
router.route()
有时候想知道router.get方法是怎么实现的时,你会发现源码中找不到get和post方法,囧
// create Router#VERB functions
// methods 中包含get、post等常用方法名,这就是为啥源码里面找不到post和get方法的原因,原来藏这了
// 由此可见,这些方法实现完全一模一样,只是名字不同而已,那get和post的区别是在哪里处理的呢?现在我也不知道,继续往下看
// router/index.js
methods.concat('all').forEach(function(method){
proto[method] = function(path){
var route = this.route(path)
// 这行代码在干啥呢?调用Route类下面的[mehtod]方法
route[method].apply(route, slice.call(arguments, 1));
return this;
};
});
/*
* Create a new Route for the given path.
* Each route contains a separate middleware stack and VERB handlers.
*/
proto.route = function route(path) {
var route = new Route(path);
var layer = new Layer(path, {
sensitive: this.caseSensitive,
strict: this.strict,
end: true
}, route.dispatch.bind(route));
// 与router.use()不同的地方,route绑定到layer
layer.route = route;
this.stack.push(layer);
return route;
};
// 实例化Route
function Route(path) {
this.path = path;
this.stack = []; // 这里又初始化了个 stack
debug('new %o', path)
// route handlers for various http methods
this.methods = {};
}
// 综合Router 和 Route,每个Router 和 Route 都有个stack数组
// router/route.js
// 这里才是真正的router.get/post方法的实现
methods.forEach(function(method){
Route.prototype[method] = function(){
var handles = flatten(slice.call(arguments));
// 一个路由有多个处理方法,依次加到当前route的stack里面去,也就意味着只能依次调用了
for (var i = 0; i < handles.length; i++) {
var handle = handles[i];
// 删除handle是否是函数的判断
var layer = Layer('/', {}, handle);
layer.method = method;
this.methods[method] = true;
this.stack.push(layer);
}
return this;
};
});
以上就是中间件和路由的添加过程了,常见的还有app.all() 等方法,其实都是router.use() 的封装
接下来,就是最重要
的API请求的处理流程
了!!!
API请求处理流程
回到Express实例化的地方,express实例就是http.createServer()的参数,自然,API请求最先调用的就是app.handle()了
// application.js
app.handle = function handle(req, res, callback) {
var router = this._router;
// final handler
// callback是undefined,
// finalhandler 就是匹配不到
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);
};
// 最核心的代码,next的实现,也是中间件的执行过程
proto.handle = function handle(req, res, out) {
var self = this;
var idx = 0;
var protohost = getProtohost(req.url) || ''
var removed = '';
var slashAdded = false;
var paramcalled = {};
// middleware and routes
var stack = self.stack;
// manage inter-router variables
var parentParams = req.params;
var parentUrl = req.baseUrl || '';
var done = restore(out, req, 'baseUrl', 'next', 'params');
// setup next layer
req.next = next;
// 删除一段options请求的处理
// setup basic req values
req.baseUrl = parentUrl;
req.originalUrl = req.originalUrl || req.url;
next();
// next方法原来在这,害我找半天
// next() 可以传入报错信息
function next(err) {
var layerError = err === 'route'
? null
: err;
// remove added slash
if (slashAdded) {
req.url = req.url.substr(1);
slashAdded = false;
}
// restore altered req.url
if (removed.length !== 0) {
req.baseUrl = parentUrl;
req.url = protohost + removed + req.url.substr(protohost.length);
removed = '';
}
// signal to exit router
// 这个信号从哪来?这个是开发者调用next方法的时候传进来的,意味着告诉express直接退出这级router,不再往下匹配了
if (layerError === 'router') {
setImmediate(done, null)
return
}
// no more matching layers
if (idx >= stack.length) {
setImmediate(done, layerError);
return;
}
// get pathname of request
var path = getPathname(req);
if (path == null) {
return done(layerError);
}
// find next matching layer
var layer;
var match;
var route;
// 查找匹配的中间件
while (match !== true && idx < stack.length) {
layer = stack[idx++];
match = matchLayer(layer, path); // 路由匹配方法,待下面详解,先略过
route = layer.route;
if (typeof match !== 'boolean') {
// hold on to layerError
layerError = layerError || match;
}
if (match !== true) {
continue;
}
if (!route) {
// process non-route handlers normally
continue;
}
if (layerError) {
// routes do not match with a pending error
match = false;
continue;
}
// 删除method === options和head时的处理
}
// no match
if (match !== true) {
return done(layerError);
}
// store route for dispatch on change
if (route) {
req.route = route;
}
// Capture one-time layer values
req.params = self.mergeParams
? mergeParams(layer.params, parentParams)
: layer.params;
var layerPath = layer.path;
// this should be done for the layer
// 读取url里面的params
self.process_params(layer, paramcalled, req, res, function (err) {
if (err) {
return next(layerError || err);
}
// route不为空,说明是单个route,直接将该route下面的handles执行完
if (route) {
return layer.handle_request(req, res, next);
}
// route为null,说明下面可能还有子路由,所以得剔除掉这一级匹配到的路径,继续往下匹配
trim_prefix(layer, layerError, layerPath, path);
});
}
};
// 然后就到了 layer.handle_request方法了,继续挖
// router/layer.js
Layer.prototype.handle_request = function handle(req, res, next) {
// 这个handle 是啥呢,就是实例化Layer的回调函数,往上面看中间件和路由的添加代码
// handle函数 或者 dispatch函数
var fn = this.handle;
if (fn.length > 3) {
// not a standard request handler
return next();
}
try {
fn(req, res, next);
} catch (err) {
// 处理出错了,就抛出去了,但是因为fn里面可能有异步,这里可能捕获不到
next(err);
}
};
/**
* dispatch req, res into this route
* @private
*/
// router/route.js
Route.prototype.dispatch = function dispatch(req, res, done) {
var idx = 0;
var stack = this.stack;
if (stack.length === 0) {
return done();
}
var method = req.method.toLowerCase();
req.route = this;
next();
function next(err) {
// signal to exit route
// 这个是express 埋的一个点,可以提前终止后续handles的执行
if (err && err === 'route') {
return done();
}
// signal to exit router
if (err && err === 'router') {
return done(err)
}
var layer = stack[idx++];
if (!layer) {
return done(err);
}
if (layer.method && layer.method !== method) {
return next(err);
}
if (err) {
layer.handle_error(err, req, res, next);
} else {
layer.handle_request(req, res, next);
}
}
};
以上就是中间件和路由的匹配和执行过程,一直往下匹配(前提是一直next了,如果那级没next那就停了),知道中间件匹配完了,或者res.send/json等方法返回结果
API请求结果返回
常见的send、json等方法都在 response.js文件中,在expressInit中间件中会挂载到res中
res.send = function send(body) {
var chunk = body;
var encoding;
var req = this.req;
var type;
// settings
var app = this.app;
// 删除一段deprecate代码
switch (typeof chunk) {
// string defaulting to html
case 'string':
if (!this.get('Content-Type')) {
this.type('html');
}
break;
case 'boolean':
case 'number':
case 'object':
if (chunk === null) {
chunk = '';
} else if (Buffer.isBuffer(chunk)) {
if (!this.get('Content-Type')) {
this.type('bin');
}
} else {
return this.json(chunk);
}
break;
}
// write strings in utf-8
// 略
// determine if ETag should be generated
var etagFn = app.get('etag fn')
var generateETag = !this.get('ETag') && typeof etagFn === 'function'
// populate Content-Length
var len
if (chunk !== undefined) {
if (Buffer.isBuffer(chunk)) {
// get length of Buffer
len = chunk.length
} else if (!generateETag && chunk.length < 1000) {
// just calculate length when no ETag + small chunk
len = Buffer.byteLength(chunk, encoding)
} else {
// convert chunk to Buffer and calculate
chunk = Buffer.from(chunk, encoding)
encoding = undefined;
len = chunk.length
}
this.set('Content-Length', len);
}
// populate ETag
var etag;
if (generateETag && len !== undefined) {
if ((etag = etagFn(chunk, encoding))) {
this.set('ETag', etag);
}
}
// freshness
if (req.fresh) this.statusCode = 304;
// strip irrelevant headers
if (204 === this.statusCode || 304 === this.statusCode) {
this.removeHeader('Content-Type');
this.removeHeader('Content-Length');
this.removeHeader('Transfer-Encoding');
chunk = '';
}
if (req.method === 'HEAD') {
// skip body for HEAD
this.end();
} else {
// respond
// 返回数据,这个方法是 来自于ServerResponse-> OutgoingMessage -> stream.Writable
this.end(chunk, encoding);
}
return this;
};
至此,一个API请求就结束了!!!
但是如果是返回文件呢?res.sendFile方法什么实现的呢?这个还是比较复杂的,等看完stream相关内容之后再看。
最后在看下中间件handles执行顺序的示例
router.get('/test1',
(req, res, next) => {
console.log('handle 1 start');
next('route'); // handle 2 不会执行了
console.log('handles 1 finished');
},
async (req, res, next) => {
try {
console.log('handle 2 start');
next();
console.log('handle 2 finished');
} catch (error) {
return res.json({ error: 'Internal Server Error' });
}
}
);
router.get('/test1',
(req, res, next) => {
console.log('handle 3 start');
next('route'); // handle 4 不会执行了
console.log('handle 3 finished');
},
async (req, res, next) => {
try {
console.log('handle 4 start');
console.log('handle 4 finished');
res.json({ data: '' });
} catch (error) {
return res.json({ error: 'Internal Server Error' });
}
}
);
router.get('/test1', (req, res, next) => {
res.json('结束了');
});
GET /test1时,输出结果如下
handle 1 start
handle 3 start
handles 3 finished
handles 1 finished
next(‘route’) 阻止了handle 2、4 的调用,但是并不会阻止3的调用
再看一个next(‘router’)的示例
let router2 = express.Router();
router2.get('/demo',
(req, res, next) => {
console.log('handle 5 start');
next();
console.log('handles 5 finished');
},
async (req, res, next) => {
try {
console.log('handle 6 start');
next('router'); // handle 7 不会执行了
console.log('handles 6 finished');
} catch (error) {
return res.json({ error: 'Internal Server Error' });
}
}
);
router2.get('/demo',(req, res, next) => {
console.log('handle 7 start');
next();
console.log('handles 7 finished');
});
router.use('/test2', router2);
router.use('/test2', (req, res, next) => {
console.log('handle 9 start');
next();
console.log('handles 9 finished');
});
GET /test2/demo时,输出结果如下
handle 5 start
handle 6 start
handles 6 finished
handles 5 finished
handle 9 start
handles 9 finished
next(‘router’) 阻止了handle 7 以及router2 下面所有的route调用,相当于跳出了router2,但不影响handle 9的执行。
express的问题
由于中间件串联执行的关键方法next(),没有封装成了promise,也就意味着,一旦handles里面有异步方法,next() 之后的方法并不会等待这些异步方法执行完毕再执行!!!
这就破坏了“洋葱圈”模型了,如果你想等next()执行完再做一些操作就没办法搞了。。。
最常用的打印api log的中间件morgan就只能采用监听scoket断开的方式打印log。
这时候,Koa 就该登场了,简单来说,koa把中间件执行过程全部promise化了,只要await一下next(),就完美了。
好了,express的源码解析就到这了,算是理清流程了。