前端需要去了解的nodejs知识(express源码分析)

前言

express是目前常用的nodejs框架之一,适用express开发项目可以节省我们
大量项目架构的时间,因为express的路由为核心的设计方便了我们适用传统的MVC来架构我们的项目。

  • 路由和中间件的功能扩展了nodejs在web服务的能力。
  • express封装了request和response更加方便我们对http请求和响应的处理
  • 方便nodejs在web服务器领域的开发,提速了nodejs在服务端的开发

express源码分析 (1).png

express整体架构

目录结构

express的相关模块处理主要放在./lib文件夹下,主要有入口文件,路由模块,中间件模块,请求/返回扩展

  • ./lib/express: 入口文件
  • ./lib/request,./lib/response:request,response的扩展方法
  • ./lib/view:页面渲染
  • ./lib/application:应用入口页面
  • ./lib/router/**:路由的注册,挂载

抽象设计

  • express根对象上绑定以下对象
    • application(app)
      • Middleware
      • Router:use、route、param ….
    • Request: cookie、body、path、params、query、http信息 ….
    • Response:cookie、sendStatus、send、json ….
    • Router: use、route、param ….

运行流程

开始请求 –> application层 –> Router –> layer(也可称为模块) –> route/callback handler –> 结束
时间应用中我们经常按以下来拆分应用:

  • application层:整个应用层
  • Router:应用之下是入口路由负责处理各个请求
  • layer(也可称为模块):入口路由之下一般是我们每个业务模块(业务层),这里改业务下所有对应的路由
  • route/callback handler:每个模块可以拆分单独的功能每个功能都可以对应一个路由并且具备回调处理函数
  • Middleware:中间件贯穿在整个业务流的任意一个流程中。

初始化

从源码中可以看出当应用启动时express主要做了三件事:1.应用对象实例化 2.request/reponse扩展 3.路由和对应的中间件(callback)放入stack,下面详细看下代码中的处理

应用对象实例化

  • 端口监听: 入口函数createApplication很有意思,不仅初始化了app,此处为后面扩展request和response打下了了基础,首先将自定义的request和reponse模块添加到app对象上。
function createApplication() {
  var app = function(req, res, next) {
    app.handle(req, res, next);
  };
  *****
  app.request = Object.create(req, {
    app: { configurable: true, enumerable: true, writable: true, value: app }
  })
  app.response = Object.create(res, {
    app: { configurable: true, enumerable: true, writable: true, value: app }
  })
  app.init();
  return app;
}

请求和返回初始化

  • request/reponse扩展:找到init函数可以看到如下代码,对应request和reponse的文件后面再做分析
var req = require('./request');
var res = require('./response');
exports.init = function(app){
  return function expressInit(req, res, next){
   ****
   //此处将扩展的requset和reponse属性添加到请求的req和res上
    setPrototypeOf(req, app.request)
    setPrototypeOf(res, app.response)
   ***
  };
};

路由和中间件初始化

  • 入口方法:
# 初始化路由方法
 if (!this._router) {
    this._router = new Router({
      caseSensitive: this.enabled('case sensitive routing'),
      strict: this.enabled('strict routing')
    });
    this._router.use(query(this.get('query parser fn')));
    this._router.use(middleware.init(this));
  }
  • 路由和对应的中间件(callback)放入stack:处理方法在lib/router/index.js文件中,分别是对应use方法 route[method]方法

    • use方法的初始化
    proto.use = function use(fn) {
      var offset = 0;
      var path = '/';
    
      ***** 省略一些判断 *****
    
      var callbacks = flatten(slice.call(arguments, offset));
    
      if (callbacks.length === 0) {
        throw new TypeError('Router.use() requires a middleware function')
      }
    
      for (var i = 0; i < callbacks.length; i++) {
        var fn = callbacks[i];
    
        if (typeof fn !== 'function') {
          throw new TypeError('Router.use() requires a middleware function but got a ' + gettype(fn))
        }
        // 通过定义的路由生成layer对象并存入堆站中
        var layer = new Layer(path, {
          sensitive: this.caseSensitive,
          strict: false,
          end: false
        }, fn);
    
        layer.route = undefined;
    
        this.stack.push(layer);
      }
    
    };
    
    • app[method]定义的路由处理
    # app[method]处理的入口
    methods.forEach(function(method){
      app[method] = function(path){
        *****
        this.lazyrouter();
        // 调用/route/index.js中的方法来存入对战
        var route = this._router.route(path);
        route[method].apply(route, slice.call(arguments, 1));
        return this;
      };
    });
    

    app[method]中实例化为layer并存入栈内

    proto.route = function route(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;
    }
    

功能分析

按照原生的nodejs的request和reponse类来处理请求和响应是非常麻烦的,所以express扩展了原生的两个类,下面我们来看看express是如何处理的

路由和中间件

  • 关键方法-layer,用来处理路由和中间件
function Layer(path, options, fn) {
  if (!(this instanceof Layer)) {
    return new Layer(path, options, fn);
  }

  var opts = options || {};
  this.handle = fn;
  this.name = fn.name || '<anonymous>';
  this.params = undefined;
  this.path = undefined;
  this.regexp = pathRegexp(path, this.keys = [], opts);
  ******
}

# 处理请求过来的layer
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);
  }
};

Request

  • 扩展方式1:使用defineProperty在request对象上新增相关只读属性
function defineGetter(obj, name, getter) {
  Object.defineProperty(obj, name, {
    configurable: true,
    enumerable: true,
    get: getter
  });
}
  • 扩展方式2:在request新增属性或方法,eg:
req.get =  =
req.header = function header(name) {
  console.log('======= add request get method =====')
  *****
};

# 源码中使用demo
******
app.get('/user/:id', loadUser, function(req, res){
  console.log(req.host);
  console.log(req.get('cookie'));
  res.send('Viewing user ' + req.user.name);
});
******
# 控制台输出如下:
======= add request get method =====
securityMark=3079084b-cb28-4ca5-86cc-d75d856b5e8b

Response

  • 扩展方式:直接在reponse上新增属性或方法 eg:
res.status = function status(code) {
  console.log("====== add status method ======");
  ****
  this.statusCode = code;
  return this;
};
app.get('/user/:id', loadUser, function(req, res){
  res.status(400)
  res.send('Viewing user 2' + req.user.name);
});
******
# 请求该接口可以看到控制台输出如下
====== add status method ======
# postman请求status为400

总结

© 版权声明
THE END
喜欢就支持一下吧
点赞9 分享
评论 抢沙发
头像
欢迎您留下宝贵的见解!
提交
头像

昵称

取消
昵称表情代码图片

    暂无评论内容