@Dale-Lin
2020-09-27T21:39:09.000000Z
字数 2748
阅读 584
Node.js实战
createServer 通过闭包返回增强的 app 对象:
function createServer() {
function app(req, res, next) {
app.handle(req, res, next)
}
merge(app, proto)
merge(app, EventEmitter.prototype)
app.route = '/'
app.stack = []
return app
}
proto 实现了 app 的核心方法 use 和 handle。
// 这里的 handle 是中间件
proto.use = function use(route, fn) {
// 获取挂载路径和中间件
var handle = fn;
var path = route;
if (typeof route !== 'string') {
handle = route;
path = '/'
}
// 传入的是另一个 app 类的情况
if (typeof handle.handle === 'function') {
// 外层挂载
var server = handle;
server.route = path;
// 将外层 app 的 handle 参传给内层
handle = function (req, res, next) {
server.handle(req, res, next);
}
}
// 包装原生的 http.Servers
if (handle instanceof http.Server) {
handle = handle.listeners('request')[0];
}
// 除去挂载路径最后的 '/'
if (path[path.length - 1] === '/') {
path = path.slice(0, -1);
}
// 挂载中间件
this.stack.push({ route: path, handle: handle });
return this;
}
handle 方法处理服务器请求,将它们传给中间件队列:
proto.handle = function handle(req, res, out) {
var stack = this.stack;
var index = 0; // 中间件队列指针
var photohost = getProtohost(req.url) || ''; // origin
var removed = ''; // 删除的挂载路径
var slashAdded = false;
// 最后处理
var done = out || finalhandler(req, res, {
env: env,
onerror: logerror,
});
// 保存原始 req.url
req.originalUrl = req.originalUrl || req.url;
// next 调用下一个中间件
function next(err) {
// 如果手动添加了 '/'
if (slashAdded) {
req.url = req.url.substr(1);
slashAdded = false;
}
// 加上上一个中间件删除了的挂载路径
if (removed.length !== 0) {
req.url = protohost + removed + req.url.substr(protohost.length);
removed = '';
}
// 下个中间件
var layer = stack[index++]
// 中间件队列结束
if (!layer) {
defer(done, err);
return;
}
// 路由解析
var path = parseUrl(req).pathname || '/';
var route = layer.route;
// req 路径和挂载路径不能匹配
if (path.toLowerCase().substr(0, route.length) !== route.toLowerCase()) {
return next(err)
}
// req 路径是 route 路径的子目录
// 但没有紧接 "/" 或 "." 或结束
var c = path.length > route.length && path[route.length];
if (c && c !== '/' && c !== '.') {
return next(err)
}
// 删除 req 路径前匹配该 route 的路径
if (route.length !== 0 && route !== '/') {
removed = route;
req.url = protohost + req.url.substr(photohost.length + removed.length);
// 保证以 "/" 开头
if (!protohost && req.url[0] !== '/') {
req.url = '/' + req.url;
slashAdded = true;
}
}
// 调用当前层的中间件
call(layer.handle, route, err, req, res, next);
}
// 调用默认的 out
next();
}
// 默认使用 http
proto.listen = function listen() {
var server = http.createServer(this);
return server.listen.apply(server, arguments);
}
call 函数是决定调用或跳过中间件的地方:
function call(handle, route, err, req, res, next) {
var arity = handle.length; // 中间件接受的参数的个数
var error = err;
var hasError = Boolean(err);
try {
if (hasError && arity === 4) {
// 有错误,且是错误处理中间件
// 调用错误处理中间件
handle(err, req, res, next);
return;
} else if (!hasError && arity < 4) {
// 无错误,且当前不是错误处理中间件
// 调用当前中间件
handle(req, res, next);
return;
}
} catch (e) {
error = e;
}
// 跳过当前中间件
next(error)
}
辅助函数和对象:
// 获取链接的协议和 host
function getProtohost(url) {
if (url.length === 0 || url[0] === '/') {
return undefined;
}
var fqdnIndex = url.indexOf('://');
return fqdnIndex !== -1 && url.lastIndexOf('?', fqdnIndex) === -1
? url.substr(0, url.indexOf('/', 3 + fqdnIndex) === -1
: undefined;
}
// 打印错误
function logerror(err) {
if (env !== 'test') console.error(err.stack || err.toString());
}