Skip to content

Instantly share code, notes, and snippets.

@dlutwuwei
Created May 24, 2014 08:53
Show Gist options
  • Save dlutwuwei/8ec70d53b620c710c414 to your computer and use it in GitHub Desktop.
Save dlutwuwei/8ec70d53b620c710c414 to your computer and use it in GitHub Desktop.
app.route, app.param - express4.2api讲解

app.routeapp.param是express4.2新增的两个api,这里做一些说明:

app.route

express的官方文档时这样解释的:

Returns an instance of a single route which can then be used to handle HTTP verbs with optional middleware. Using app.route() is a recommended approach to avoiding duplicate route naming and thus typo errors.

返回一个route实例,其实就是express源码里的router/index.js里定义的组件,作用是保存一个路由路径的信息及其响应函数,使用app.route可以让你不用重复配置一条路径对应的不同http请求的路由。

比如: app.get('/example',function(){}); app.post('/example',function(){}); app.put('/example',function(){}); 就可以简写成:app.route("/example").get(function(){}).post(function(){}).put(function(){}). 这样有多少好处,就见仁见智了。

app.route的作用就是对同一路径下,有不同http请求响应处理时,代码书写更简便了。

官方给了一个这样的例子,/events路径所有类型的http请求都将在这个路由配置里响应

var app = express();

app.route('/events')
.all(function(req, res, next) {
  // runs for all HTTP verbs first
  // think of it as route specific middleware!
})
.get(function(req, res, next) {
  res.json(...);
})
.post(function(req, res, next) {
  // maybe add a new event...
})

我们先来看一下源码:

app.lazyrouter = function() {
  if (!this._router) {
    this._router = new Router({
      caseSensitive: this.enabled('case sensitive routing'),
      strict: this.enabled('strict routing')
    });

    this._router.use(query());
    this._router.use(middleware.init(this));
  }
};
app.route = function(path){
  this.lazyrouter();
  return this._router.route(path);
};

首先调用this.lazyrouter(), 生成两个中间件query,expressInit并插入,不知道的看本博客另一篇文章,然后调用router组件的route函数,生成一个路由中间件放入中间件栈中。下面详细看看route()的代码:

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.route = route;

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

很简单,就是用一个layer封装一个route中间件放入栈中。将遵循express的触发流程,和app.get,app.use不冲突。

app.param

###1.如何使用 这个api的作用是捕获路径中的通配符参数,取个例子:

app.param('id', /^\d+$/);

app.get('/user/:id', function(req, res){
  res.send('user ' + req.params.id);
});

app.param('range', /^(\w+)\.\.(\w+)?$/);

app.get('/range/:range', function(req, res){
  var range = req.params.range;
  res.send('from ' + range[1] + ' to ' + range[2]);
});

路径中通配符:经常是隐含的request参数,app.param()通过正则表达式非常方便的将其取下,放入req.params中。

app.param也可以不用正则表达式,直接匹配所有类型数据,并建立一个中间件处理:

app.param('user', function(req, res, next, id){
  User.find(id, function(err, user){
    if (err) {
      next(err);
    } else if (user) {
      req.user = user;
      next();
    } else {
      next(new Error('failed to load user'));
    }
  });
});

这样所有符合的路径都会进入中间件,注意到中间件新增了一个参数,说明其和中间件不同

别急还有一种用法:

app.param(function(name, fn){
  if (fn instanceof RegExp) {
    return function(req, res, next, val){
      var captures;
      if (captures = fn.exec(String(val))) {
        req.params[name] = captures;
        next();
      } else {
        next('route');
      }
    }
  }
});

app.param匹配了所有通配符,回调函数中name是通配名称,fn是一个正则表达式对象,这样我们可以想到第一种使用方法里也可以写成这样:

app.param(function(name, fn){
  if (fn instanceof RegExp) {
    return function(req, res, next, val){
      if(name=="user"&&fn.match(val))
        res.send('user ' + val);
      else
        next()//继续执行其他param
    }
  }
});

###2. 源码分析 下面看看源代码:

app.param = function(name, fn){
  var self = this;
  self.lazyrouter();

  if (Array.isArray(name)) {
    name.forEach(function(key) {
      self.param(key, fn);
    });
    return this;
  }

  self._router.param(name, fn);
  return this;
};

可以看到通配名称name可以指定多个,一一处理,主要的执行在router组件里面:

proto.param = function(name, fn){
  // param logic,这是第三种用法,只传入一个function
  if ('function' == typeof name) {
    this._params.push(name);
    return;
  }

  // apply param functions
  var params = this._params;
  var len = params.length;
  var ret;

  if (name[0] === ':') {
    name = name.substr(1);
  }

  // 第三种用法len==0, 获取param中间件,fn也可能为一个正则表达式
  // 或一个function(rq, res),
  // 就是`app.param(function(name,fn){ return ... })`
  // 返回的`function(req, res, next, val){}`
  for (var i = 0; i < len; ++i) {
    if (ret = params[i](name, fn)) {
      fn = ret;
    }
  }

  // ensure we end up with a
  // middleware function
  if ('function' != typeof fn) {
    throw new Error('invalid param() call for ' + name + ', got ' + fn);
  }

  //将param中间件加入param栈中,第一种用法,和第二种用法
  (this.params[name] = this.params[name] || []).push(fn);
  return this;
};

下面看看param是怎么触发执行的吧,我们所有的param,形式为function(req, res, next, val){},都储存在router.params中,触发执行更中间件一样在router.handle()中,在router递归调用next()时触发:

 function next(err) {
    if (err === 'route') {
      err = undefined;
    }

    //取出中间件
    var layer = stack[idx++];
    if (!layer) {
      return done(err);
    }

    if (slashAdded) {
      req.url = req.url.substr(1);
      slashAdded = false;
    }

    req.url = protohost + removed + req.url.substr(protohost.length);
    req.originalUrl = req.originalUrl || req.url;
    removed = '';

    try {
      var path = parseUrl(req).pathname;
      if (undefined == path) path = '/';

      if (!layer.match(path)) return next(err);

      // route object and not middleware
      var route = layer.route;

      // if final route, then we support options
      if (route) {
        // we don't run any routes with error first
        if (err) {
          return next(err);
        }

        req.route = route;

        // we can now dispatch to the route
        if (method === 'options' && !route.methods['options']) {
          options.push.apply(options, route._options());
        }
      }

      req.params = layer.params;

      // this should be done for the layer,处理params,params处理完才会执行回调`function(err)`,执行中间件。
      return self.process_params(layer, req, res, function(err) {
        if (err) {
          return next(err);
        }

        //执行中间件,中间件需要自己调用next形成递归调用
        if (route) {
          return layer.handle(req, res, next);
        }

        trim_prefix();
      });

    } catch (err) {
      next(err);
    }

self.process_params(layer, req, res, function(err) {})是代码里的关键,他的回调函数中执行了中间件,说明param处理永远在中间件前,下面看看这个函数:

proto.process_params = function(route, req, res, done) {
  var params = this.params;

  // captured parameters from the route, keys and values
  // route保存的通配值,这里route = new layer();
  var keys = route.keys;

  // fast track
  if (!keys || keys.length === 0) {
    return done();
  }

  var i = 0;
  var paramIndex = 0;
  var key;
  var paramVal;
  var paramCallbacks;

  // process params in order
  // param callbacks can be async
  function param(err) {
    if (err) {
      return done(err);
    }

    if (i >= keys.length ) {
      return done();
    }

    paramIndex = 0;
    key = keys[i++];
    paramVal = key && req.params[key.name];
    paramCallbacks = key && params[key.name];

    try {
      if (paramCallbacks && undefined !== paramVal) {
        return paramCallback();
      } else if (key) {
        return param();
      }
    } catch (err) {
      return done(err);
    }

    //执行回调,继续中间件的next链条
    done();
  }
  // single param callbacks
  function paramCallback(err) {
    var fn = paramCallbacks[paramIndex++];
    if (err || !fn) return param(err);
    fn(req, res, paramCallback, paramVal, key.name);
  }

  param();
};

处理方式跟中间件差不多,不过这里隐藏了两个递归,需要仔细看看:

一个是param(),遍历一遍params,取出所有param处理 一个是paramCallback,遍历param里的每个fn回调函数并执行,paramCallback实现递归,需要在app.param的回调函数中调用next()

 function paramCallback(err) {
    var fn = paramCallbacks[paramIndex++];
    if (err || !fn) return param(err);
    fn(req, res, paramCallback, paramVal, key.name);
  }

fn的形式为funtion(req, res, next, val, key),现在知道app.param的回调应该怎么用了吧。

###keys和params的来源 处理param时,一直出现两个数组keys和params,这里给予解答:keys在layer.js中生成,

this.regexp = pathRegexp(path, this.keys = [], options);

在layer的构造函数中使用pathRegexp模块获取路径中的通配符的值,放入keys数组中。

在调用app.param的时候:

(this.params[name] = this.params[name] || []).push(fn);

params[name] 会有一个数组保存他的回调函数,也就是说,一个通配会有多个回调,依次执行。

Written with StackEdit.

@theharveyz
Copy link

mark

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment