美文网首页前端开发
express 路由分析

express 路由分析

作者: 209bd3bc6844 | 来源:发表于2017-10-16 10:41 被阅读394次

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

    相关文章

      网友评论

        本文标题:express 路由分析

        本文链接:https://www.haomeiwen.com/subject/tsazyxtx.html