前端开发

express 路由分析

2017-10-16  本文已影响394人  209bd3bc6844

express极简的 web 开发框架。
这里创建是一个最最简单的 Express 应用

var express = require('express');
var app = express();

app.get('/', function(req, res) {
  res.send('hello, express');
});

app.listen(3000);

以上代码的意思是:生成一个 express 实例 app,挂载了一个根路由控制器,然后监听3000 端口并启动程序。运行 node index,打开浏览器访问 localhost:3000 时,页面应显示 hello, express

路由

// 对网站首页的访问返回 "Hello World!" 字样
app.get('/', function (req, res) {
  res.send('Hello World!');
});

// 网站首页接受 POST 请求
app.post('/', function (req, res) {
  res.send('Got a POST request');
});

以上就是基本的express路由使用

express.Router

但是我们也经常看到这样的路由定义
eg:在 创建名为 birds.js 的文件,内容如下:

var express = require('express');
var router = express.Router();

// 该路由使用的中间件
router.use(function timeLog(req, res, next) {
  console.log('Time: ', Date.now());
  next();
});
// 定义网站主页的路由
router.get('/', function(req, res) {
  res.send('Birds home page');
});
// 定义 about 页面的路由
router.get('/about', function(req, res) {
  res.send('About birds');
});

module.exports = router;

然后index.js为

var birds = require('./birds');
...
app.use('/birds', birds);

那么这两种方式有什么区别呢?

使用第一种方式的路由,每增加一个路由就会在index.js中进行修改增加。这样如果大型项目会有相当多的路由写在index.js中。这当然是不现实的。这时可以使用 express.Router 实现更优雅的路由解决方案。

那为什么index.js不能这么改呢?

var birds = require('./birds');
...
app.get('/birds', birds);

app.get 和app.use有什么区别的?

结论:
app.use(path,callback)中的callback既可以是router对象又可以是函数
app.get(path,callback)中的callback只能是函数

var express = require('express');
var app = express();

var index = require('./routes/index');

//1⃣️
app.use('/test1',function(req,res,next){
    res.send('hello test1');

});

//2⃣️
app.get('/test2',function(req,res,next){
    res.send('hello test2');

});

//3⃣️
app.get('/test3',index);

//4⃣️
app.use('/test4',index);

index是一个路由对象,结果,例1、2、4结果都能正确显示,而例3却报404。index.js很简单,如下:

var express = require('express');
var router = express.Router();

router.get('/', function(req, res, next) {
  res.send('hello world!');
});

module.exports = router;

app.use和app.get的区别
我打印router实例吗,发现也是个函数。那为什么就3不行了呢?回调的router也是个函数但却不能执行呢?
这时候就需要看源码了
express路由源码解析
express路由源码解析
express源码解析

express 源码分析

我们首先来看app.ues为什么可以是router对象。
我们的index.js为

// eg1
var express = require('express');
var app = express();
app.use('/1', require('./a.js'));
app.listen(1111);
// a.js
var express = require('express');
var router = express.Router();
router.get('/', function(req, res, next) {
    res.send('hello, cexpresss');
});
module.exports = router;

我们根据express的源码来分析index.js的执行。

目录

var express = require('express');这一句话引用的是express.js

exports = module.exports = createApplication;
function createApplication() {
  var app = function(req, res, next) {
    app.handle(req, res, next);
  };
  mixin(app, proto, false); // proto 是application.js
  app.init();
  return app;
}

所以require('express')最后返回一个createApplication函数,var app = express();的值为createApplication函数中的app函数。接着分析你写的代码。app.use()。这个方法就是proto里的方法所以去查看application.js

app.use = function use(fn) {
 ...判断第一个参数是不是字符串,是的话赋值给path,不是的话path为'/'。fns为fn去除字符串。
  // setup router
  this.lazyrouter(); // 这是重点
  var router = this._router;
  fns.forEach(function (fn) { 
    if (!fn || !fn.handle || !fn.set) {  // 如果fn不是express。我们这里分析的fn都不是express
      return router.use(path, fn);
    }
  }, this);

  return this;
};

上面这段代码我们先执行this.lazyrouter()。预算看看lazyrouter函数是什么

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

上面代码的this为 我们实例化出来的app=express()的app。所以只有第一次调用这个函数时候才会进入if。才会new Router。所以我们的this._router也只有一个。这里的RouterRouter下的index.js

var proto = module.exports = function(options) {
  var opts = options || {};
  function router(req, res, next) {
    router.handle(req, res, next);
  }
  return router;
};

接下来我们返回到app.use里继续执行router.use(path, fn)。我们去Router下的index.jsrouter.use

//添加非路由中间件
proto.use = function use(fn) {
  /* 此处略去部分代码 */
  callbacks.forEach(function (fn) {
    ////实例化layer对象并进行初始化
    var layer = new Layer(path, {
      sensitive: this.caseSensitive,
      strict: false,
      end: false
    }, fn);
    //非路由中间件,该字段赋值为undefined
    layer.route = undefined;
    this.stack.push(layer);
  }, this);
  return this;
};

上述是Router添加非路由中间件。接下来我们看看Router添加路由中间件(就是我们的app.get(),app,post()之类的)

//添加路由中间件
proto.route = function(path){
  //实例化路由对象
  var route = new Route(path);
  //实例化layer对象并进行初始化
  var layer = new Layer(path, {
    sensitive: this.caseSensitive,
    strict: this.strict,
    end: true
  }, route.dispatch.bind(route));
  layer.route = route; //指向刚实例化的路由对象(非常重要),通过该字段将Router和Route关联来起来
  this.stack.push(layer);
  return route;
};

app.getapp.use主要区别在于layer.route。
接下来我们继续看看这些函数如何执行的。

app.listen = function listen() {
  var server = http.createServer(this);
  return server.listen.apply(server, arguments);
};

http.createServer(this)这句代码可以看出,一旦有请求访问3000这个端口,就会调用this,this指的是app , 程序会执行app()调用eg1里的handle 方法,这里就是整个app的函数入口了。层层跟跟踪代码,发现app.handle调用了router.handle(req, res, done);,这时候我们进入routerhandle看看里面写了什么。
routerhandle代码比较长主要实现的逻辑为
每当有请求到来,都会调用routerhandle方法,首先直接遍历routerstack(app.use, app.get之类的都会被添加到stack),取出每一个layer看是否有一个与请求的path匹配,如果没有匹配,调用done函数结束request请求,如果有匹配的layer查看layerroute不等于undefined,就执行layerhandle_request函数,如果layerroute等于undefined,那么就执行trim_prefix函数。我们先来看看layerhandler_request是什么,代码如下

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

可以看出这个方法只是单纯的执行layerhandle函数,前面已经讲到当layerroute不等于undefined时,他的handle函数是route.dispach.bind(route),这个函数会去遍历routestack里面的layer,然后再调用layerhandle_request函数进一步执行回调函数。
trim_prefix只是执行那些没有routelayerhandle函数。
所以现在最主要的区别就是layer的handle函数。
app.use('/' ,require('./a.js'))
这个layer的handle为就是a.js的输出就是module.exports = router; 就是Router下的index.js

 function router(req, res, next) {
    router.handle(req, res, next);
  }

app.get('/' ,require('./a.js'))
这个前面已经讲到当layerroute不等于undefined时,他的handle函数是route.dispach.bind(route)。执行handle函数时候回发现layer的正则表达式跟当前的url不匹配。layer的正则只匹配app.get('')里的路由,那app.use的正则为什么就可以匹配当前路由呢?

function Layer(path, options, fn) {
  var opts = options || {};
  this.path = undefined;
  this.regexp = pathRegexp(path, this.keys = [], opts);
  this.regexp.fast_star = path === '*'
  this.regexp.fast_slash = path === '/' && opts.end === false
}

主要是由于第二个参数options的不同。

遇到的问题

使用supervisor index.js 启动node。更改文件可以自动刷新。但是不能在控制台开启进程后,关掉控制台,再开启。这样可能没有杀死进程,再次启动的话会报错,就是端口被占用的意思。
处理僵尸进程
ps 查看进行。
kill 进程ID
如果kill 进程ID 不行就 kill -9 进程ID
如果已经知道被占用的端口就lsof -i:“端口”查询进程ID 然后kill掉
以上问题应该supervisor的问题是换成 nodemon 来监听文件更改。
vscode中,debug的时候需要把启动的服务关了,因为debug就会启动服务。不然debug的时候会报错端口已经被占用。

上一篇下一篇

猜你喜欢

热点阅读