understanding express(2)之expressJS接收和处理http请求过程

Have the courage to follow your heart and intuition. They somehow already know what you truly want to become. Everything else is secondary

今天来了解整个接收和处理请求的过程。

上一章说了当接收到一个请求的时候是通过这个函数 app.handle(req, res, next); 来处理请求的。今天来说一下具体一个请求的处理过程。

收到请求会执行app.handle(req, res, next),具体代码如下:

//application.js
app.handle = function(req, res, done) {
  var router = this._router;

  // final handler
  done = done || 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);
};

这个函数接收三个参数req,res,next。 其中next就是上面的done, 默认是finalhandler。具体可以看这里。然后调用router.handle()开始链式调用。

router.handler 代码比较长,先看结构:

// router/index.js
proto.handle = function(req, res, done) {
  ...... //定义一些基本变量

  next();

  function next(err) {
    ......
  }

  function trim_prefix(layer, layerError, layerPath, path) {
    ...... //对路径做些处理再调用layer.handle_request
  }
};

其中通过递归调用next()来遍历stack(存放中间件的栈,是一个数组)看是否有匹配的中间件。在proto.handle的开头就定义了idx=0,表示当前中间件索引。在next()中首先判断当前中间件索引是否大于stack.length。 如果是说明已遍历完,直接执行done()。

// function next()
if (idx >= stack.length) {
  setImmediate(done, layerError);
  return;
}

否则的话执行while循环找到第一个可以处理http请求的中间件。 来看一下while的主要代码:

// function next()
var layer;
var match;
var route;

while (match !== true && idx < stack.length) {
  layer = stack[idx++];
  match = matchLayer(layer, path);  //调用了layer.match 方法
  route = layer.route;

  if (typeof match !== 'boolean') {
    layerError = layerError || match;
  }

  if (match !== true) {
    continue; // 不匹配,退出本次循环。
  }

  if (!route) {
    continue;
  }

  if (layerError) {
    match = false;
    continue;
  }

  var method = req.method;
  var has_method = route._handles_method(method);

  if (!has_method && method === 'OPTIONS') {
    appendMethods(options, route._options());
  }

  if (!has_method && method !== 'HEAD') {
    match = false;
    continue;
  }
}

通过matchLayer确定当前layer是否与当前路径匹配。不匹配则直接结束本次循环进入下一个中间件的判断。match为true但是layer.route不存在,说明不是路由中间件,结束本次循环,由于此时match为true会结束while循环直接进入下面的步骤(这个稍后说)。 如果route存在,即是路由中间件,再判断该路由可处理的http方法是否与请求的http方法一致。如果不一致则根据其method是否为options或head做相应处理,一致则不再对match做修改,这样执行下次while循环的时候由于match为true会结束循环进入下面的步骤。

// function next() 结束while循环后的部分

if (match !== true) {
   //结束循环后match仍然为false, 说明是遍历完stack, 没有匹配的中间件
  return done(layerError);
}

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) {
    //next作为回调函数传入handle_request,形成一个闭包,再在handle_request中调用next()的时候idx的值仍然是上一次退出while循环的值
    return layer.handle_request(req, res, next);
  }

  //对路径做些处理再调用layer.handle_request
  trim_prefix(layer, layerError, layerPath, path);
});

这部分首先进行一些参数预处理,如果为路由中间件就直接调用layer.handle_request,否则先对路径做些处理再调用layer.handle_request。下面是layer.handle_request的代码:

// router/layer.js
Layer.prototype.handle_request = function handle(req, res, next) {
  var fn = this.handle;

  if (fn.length > 3) {
    // not a standard request handler
    return next();
  }

  try {
    fn(req, res, next);
  } catch (err) {
    next(err);
  }
};

layer.handle_request会在Router.handle和route.dispatch中调用。注意若是从Router.handle调用的,next是Router.handle里定义的next,若是从route.dispatch调用的,next是dispatch里定义的next。 不同的next遍历的对象有所不同。举个🌰:

//app.js
    var user = require('user');
    var app = express();

    app.use('/index', function(){
        //do something
    })
    app.use('/user', user);


//user.js
    var userRouter = require('express').Router();
    var checkAuth = function(req, res, next){
        //... check auth
        next()
    };
    var getList = function(req, res, next) {
        //... get user list
    }

    userRouter.get('/list', checkAuth, getList);
    module.exports = userRouter;

app.js中的两个app.use其实是调用router.use创建了两个layer对象并将其添加到app._router.stack中,以下简称Router。Router.stack应该是类似这样的[ layer1, layer2 ]。 其中layer2实际上是express.Router()的导出对象, 在app.use方法中会视为一个子app封装一层。像这样:

//application.js app.use()
function mounted_app(req, res, next) {
  var orig = req.app;
  fn.handle(req, res, function (err) {
    req.__proto__ = orig.request;
    res.__proto__ = orig.response;
    next(err);
});

这里的fn实际上就是app.use('/user',user)中的user(即user.js中导出的Router)。完整代码可查看application.js 的app.use。

上面user.js中调用userRouter.get方法创建一个新的route和layer(这里暂时叫newLayer, 这个layer会被push到userRouter.stack中), 再调用route.get方法把checkAuth和getList添加到route.stack中,所以该route的stack应该类似这样 [ layer3, layer4 ],userRouter.stack是这样的[ newLayer ]。 这里附上Router.route和 Router.VERB的代码。

// router/index.js
proto.route = function(path){
  var route = new Route(path);

  var layer = new Layer(path, {
    sensitive: this.caseSensitive,
    strict: this.strict,
    end: true
  }, route.dispatch.bind(route)); //layer.handle=route.dispatch

  layer.route = route;

  this.stack.push(layer);
  return route;
};

// 创建 Router#VERB functions
methods.concat('all').forEach(function(method){
  proto[method] = function(path){

    // 调用proto.route new 一个route对象。
    var route = this.route(path)

    //在route.VERB或route.all中会把传入的fn添加到该route的stack中
    route[method].apply(route, slice.call(arguments, 1));
    return this;
  };
});

所以当收到一个http请求(eg. http://host.com/user/list), 首先执行app.handle(), 在 [layer1, layer2]中查找匹配,当查找的layer2的时候,路径匹配且layer2不是一个路由中间件,进入到layer.handle_request调用layer.handle(这里的layer.handle就是mounted_app方法),里面又调用fn.handle就是执行userRouter.handle方法,又继续在userRouter.stack([ newLayer ])中查找匹配, newLayer路径与请求路径匹配且是路由中间件,进入layer.handle_request执行layer.handle,注意这里的layer.handle是route.dispatch,因为在Router.VERB中创建layer时是new Layer(path, option, route.dispatch.bind(route))。所以就会在route.stack([ layer3, layer4 ])中查找匹配。附上route.dispatch代码:

Route.prototype.dispatch = function(req, res, done){
  var idx = 0;
  var stack = this.stack;
  if (stack.length === 0) {
    return done();
  }

  var method = req.method.toLowerCase();
  if (method === 'head' && !this.methods['head']) {
    method = 'get';
  }

  req.route = this;

  next();

  function next(err) {
    if (err && err === 'route') {
      return done();
    }

    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);
    }
  }
};

可以看到这里的idx是0,与Router.handle()中的idx不同,route.dispatch是在当前route.stack中遍历匹配。在上面的🌰中route.stack就是[ layer3, layer4 ]。处理逻辑与Router.handle类似,idx是当前下表,stack是当前路由处理函数列表,递归调用next。 这里next方法判断当前layer是否存在,不存在或方法不匹配调用done, 否则根据err值执行handle_request或handle_error。例子中的layer3路径和method都匹配,执行layer3.handle_request调用this.handle即checkauth方法,如果通过验证就会执行next(),注意这时的next是dispatch中定义的next方法。此时idx指向layer4, 依然匹配,继续执行getList方法。 getList执行后若不再调用next(), 则处理请求的过程结束, 否则继续执行next, 此时route.stack遍历完再找不到layer会执行done(err) 返回到[newLayer]层面,userRouter.stack也遍历完(userRouter.handle中的idx此时为1,大于[newLayer].length)于是又调用done, 回到上层的Router.stack中([layer1, layer2])依次类推。当热由于getList处理完请求后直接reponse所以不会再调用next()。 这里只是分析下如果继续调用next的行为。

表述得可能不太清楚,看图:

http请求处理过程

Reference