@Dale-Lin
2020-09-27T13:39:09.000000Z
字数 2748
阅读 896
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.Serversif (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) || ''; // originvar removed = ''; // 删除的挂载路径var slashAdded = false;// 最后处理var done = out || finalhandler(req, res, {env: env,onerror: logerror,});// 保存原始 req.urlreq.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);}// 调用默认的 outnext();}// 默认使用 httpproto.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)}
辅助函数和对象:
// 获取链接的协议和 hostfunction 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());}