express 路由分析

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的时候会报错端口已经被占用。

最后编辑于
©著作权归作者所有,转载或内容合作请联系作者
  • 序言:七十年代末,一起剥皮案震惊了整个滨河市,随后出现的几起案子,更是在滨河造成了极大的恐慌,老刑警刘岩,带你破解...
    沈念sama阅读 194,491评论 5 459
  • 序言:滨河连续发生了三起死亡事件,死亡现场离奇诡异,居然都是意外死亡,警方通过查阅死者的电脑和手机,发现死者居然都...
    沈念sama阅读 81,856评论 2 371
  • 文/潘晓璐 我一进店门,熙熙楼的掌柜王于贵愁眉苦脸地迎上来,“玉大人,你说我怎么就摊上这事。” “怎么了?”我有些...
    开封第一讲书人阅读 141,745评论 0 319
  • 文/不坏的土叔 我叫张陵,是天一观的道长。 经常有香客问我,道长,这世上最难降的妖魔是什么? 我笑而不...
    开封第一讲书人阅读 52,196评论 1 263
  • 正文 为了忘掉前任,我火速办了婚礼,结果婚礼上,老公的妹妹穿的比我还像新娘。我一直安慰自己,他们只是感情好,可当我...
    茶点故事阅读 61,073评论 4 355
  • 文/花漫 我一把揭开白布。 她就那样静静地躺着,像睡着了一般。 火红的嫁衣衬着肌肤如雪。 梳的纹丝不乱的头发上,一...
    开封第一讲书人阅读 46,112评论 1 272
  • 那天,我揣着相机与录音,去河边找鬼。 笑死,一个胖子当着我的面吹牛,可吹牛的内容都是我干的。 我是一名探鬼主播,决...
    沈念sama阅读 36,531评论 3 381
  • 文/苍兰香墨 我猛地睁开眼,长吁一口气:“原来是场噩梦啊……” “哼!你这毒妇竟也来了?” 一声冷哼从身侧响起,我...
    开封第一讲书人阅读 35,215评论 0 253
  • 序言:老挝万荣一对情侣失踪,失踪者是张志新(化名)和其女友刘颖,没想到半个月后,有当地人在树林里发现了一具尸体,经...
    沈念sama阅读 39,485评论 1 290
  • 正文 独居荒郊野岭守林人离奇死亡,尸身上长有42处带血的脓包…… 初始之章·张勋 以下内容为张勋视角 年9月15日...
    茶点故事阅读 34,578评论 2 309
  • 正文 我和宋清朗相恋三年,在试婚纱的时候发现自己被绿了。 大学时的朋友给我发了我未婚夫和他白月光在一起吃饭的照片。...
    茶点故事阅读 36,356评论 1 326
  • 序言:一个原本活蹦乱跳的男人离奇死亡,死状恐怖,灵堂内的尸体忽然破棺而出,到底是诈尸还是另有隐情,我是刑警宁泽,带...
    沈念sama阅读 32,215评论 3 312
  • 正文 年R本政府宣布,位于F岛的核电站,受9级特大地震影响,放射性物质发生泄漏。R本人自食恶果不足惜,却给世界环境...
    茶点故事阅读 37,583评论 3 299
  • 文/蒙蒙 一、第九天 我趴在偏房一处隐蔽的房顶上张望。 院中可真热闹,春花似锦、人声如沸。这庄子的主人今日做“春日...
    开封第一讲书人阅读 28,898评论 0 17
  • 文/苍兰香墨 我抬头看了看天上的太阳。三九已至,却和暖如春,着一层夹袄步出监牢的瞬间,已是汗流浃背。 一阵脚步声响...
    开封第一讲书人阅读 30,174评论 1 250
  • 我被黑心中介骗来泰国打工, 没想到刚下飞机就差点儿被人妖公主榨干…… 1. 我叫王不留,地道东北人。 一个月前我还...
    沈念sama阅读 41,497评论 2 341
  • 正文 我出身青楼,却偏偏与公主长得像,于是被迫代替她去往敌国和亲。 传闻我的和亲对象是个残疾皇子,可洞房花烛夜当晚...
    茶点故事阅读 40,697评论 2 335

推荐阅读更多精彩内容