• express源码解析


    express源码解析(转)

    express是nodejs平台上一个非常流行的框架,4.2.0是最新的版本,相比3.x版本优化了代码和api,去除了connect模块,自己实现了一个router组件,实现http请求的顺序流程处理,去除了很多绑定的中间件,使代码更清晰。

    1.使用express

    如何使用express在官网有很好的讲解,只用experssjs实例app的几个函数,就可以构建构建web程序。

    var express = require('express');
    var logger = require('morgan');
    
    var app = express();
    
    //app.engine('html', require('ejs').renderFile);
    app.use('/public',express.static(__dirname + '/public'));
    app.use(logger());
    app.get('/', function(req, res){
    	res.send('Hello World');
    });
    
    var server = app.listen(3000, function() {
    	console.log('Listening on port %d', server.address().port);
    });
    

    上面是一个简单的web程序,返回浏览器hello world,就几个步骤,获取express实例对象,加入需要的中间件,加入路由响应,启动服务器,很简单吧,相比java,.net的框架轻量了很多,而且不需要单独架设web服务器,利用nodejs的异步非阻塞机制,可以大大提高网站的并发量。

    1.1中间件

    app.use 加入中间件,所谓中间件其实就是java,.net平台MVC框架都会有的filter。
    app.use(["path"],function(req,res,next){}) 有两个参数,path代表route路径,可选,为空表示匹配所有路径,后面是回调函数,需要添加一个next参数,执行时,框架将传入一个next函数,调用它启动下一个中间件,下面是一个中间件的示例

    app.use('/public',express.static(__dirname + '/public'));
    app.use(logger());
    app.use(function(req, res, next){
        console.log('hello middleware');
        next();
    });
    app.get('/', function(req, res){
    res.send('Hello World');
    });
    

    我们添加了一个自定义的中间件,打印出 hello middleware 从上面我们可以看出app.use把中间件加入一个栈中,http request将触发整个中间件链条,并依次执行(通过 next() 函数实现),功能类似于filter,但其作用却大于filter,它可以动态地给req,res添加内容。margon是一个日志记录的包,记录每个request的信息。express.static()是express保留的唯一个内置中间件,对/pulic路径下的route导向静态资源文件,不调用next。这样中间件就可以实现对指定或所有路径request和response的处理。

    1.2 app.get()/app.VERB()

    app.get有两个功能,第一次看express文档时都会很疑惑,app.get可以获取app.set设置的全局变量,也可以设置路由的处理函数,下面是get实现的源码,对js不是很熟悉的人会很纠结,代码里找不到get函数啊,app.get和app['get']的方式都可以定义对象的函数,下面是其实现的源码。

    >application.js
    /**
     * Delegate `.VERB(...)` calls to `router.VERB(...)`.
     */
    methods.forEach(function(method){
      app[method] = function(path){
        if ('get' == method && 1 == arguments.length) return this.set(path);
    
        this.lazyrouter();
    
        var route = this._router.route(path);
        route[method].apply(route, [].slice.call(arguments, 1));
        return this;
      };
    });
    

    methods是一个数组,存储了http所有请求的类型,在method模块里定义,除了基本的get、port请求外,还有多达十几种请求,可能是为了兼容新的http标准吧。app[method]中,method=='get'且只有一个参数,则执行set,执行的是获取变量的功能,否则,执行app.get('path',function(req,res){})中path对应的回调函数,执行route组件的get方法(实现方式和这里一样),将route和回调存储进一个栈中。http请求触发执行,app.get也将产生一条路由中间件,执行后返回浏览器html页面。

    module.exports = [
      'get',
      'post',
      'put',
      'head',
      'delete',
      'options',
      'trace',
      'copy',
      'lock',
      'mkcol',
      'move',
      'purge',
      'propfind',
      'proppatch',
      'unlock',
      'report',
      'mkactivity',
      'checkout',
      'merge',
      'm-search',
      'notify',
      'subscribe',
      'unsubscribe',
      'patch',
      'search'
    ];
    

    2.了解express4.2的结构

    下面是express4.2的文件结构图:

    img

    1. express.js和application.js是主要的框架文件,暴露了express的api。
    2. router文件夹下为router组件,负责中间件的插入和链式执行,具体讲解在下一章节。
    3. middleware下的init.js和query.js为两个中间件,init.js的作用是初始化request,response,看一下代码就能明白:
        exports.init = function(app){
            return function expressInit(req, res, next){
                if (app.enabled('x-powered-by')) res.setHeader('X-Powered-By', 'Express');
                req.res = res;
                res.req = req;
                req.next = next;
                
                req.__proto__ = app.request;
                res.__proto__ = app.response;
                
                res.locals = res.locals || Object.create(null);
                
                next();
            };
        };
    

    query.js中间件的作用是格式化url,将url中的rquest参数剥离,储存到req.query中:

    module.exports = function query(options){
      return function query(req, res, next){
          if (!req.query) {
            req.query = ~req.url.indexOf('?')
              ? qs.parse(parseUrl(req).query, options)
              : {};
          }
                
          next();
        };
      };
    
    1. request.js和response.js, 提供了一些方法丰富request和response实例的功能,在init.js中初始化了http的req和res实例。 req.\__proto__ = app.request;res.\__proto__ = app.response;
    2. view.js提供模板渲染引擎的封装,通过res.render()调用引擎渲染网页,具体请看第五章

    3.Router组件

    Router组件由三个文件组成,index.js为主文件,route.js主要功能是路由处理,layer保存中间件的数据结构,Router组件实例化后的对象如下图所示,stack为中间件栈:这是第一章里代码执行时的对象结构图,我们可以看到route存储了五个中间件,包含两个默认的query和expressInit组件:

    { [Function: router]
      params: {},
      _params: [],
      caseSensitive: false,
      strict: false,
      stack: 
       [ { keys: [], regexp: /^/?(?=/|$)/i, handle: [Function: query] },
         { keys: [],
           regexp: /^/?(?=/|$)/i,
           handle: [Function: expressInit] },
         { keys: [],
           regexp: /^/public/?(?=/|$)/i,
           handle: [Function: staticMiddleware] },
         { keys: [], regexp: /^/?(?=/|$)/i, handle: [Function: logger] },
         { keys: [], regexp: /^/?(?=/|$)/i, handle: [Function] } ]
    }
    

    下面是Route的实例,stack为其http.verbmethod和响应函数对, 如下图所示,”/"为一条路由的路径,接受method为get的http请求。

    { path: '/',
      stack: [ { method: 'get', handle: [Function] } ],
      methods: { get: true } 
    }
    
    • index.js主要处理中间件的执行,包括中间的插入,错误处理,执行(handle)等
    • route.js主要处理路由信息,每条路由都会生成一个Route实例,通过index.js里的proto.route(path)方法可以创建一个path对应的Route实例,并封装在layer中,加入中间件栈。另外Route.get (Route['get']) 方法也是在这里动态生成的。
    • layer.js是中间件的存储结构。

    router.stack的最后一条,发现它的handle是一个无名的function,看了源码你就会知道,这个无名funtion就是路由'/'对应的处理函数,每条路由都会作为一个中间件加入栈中。

    我们每次调用app.get()就新建了一个Route实例(见1.2节代码),调用链条为app['get']=>router.Route['get']

    如下代码,调用Route['get'],Route中将会将get标示加入self.methods中,防止重复定义,然后生成一个数据项加入self.stack,数据项{ method: 'get', handle: [Function] }含method标示和路由处理函数fn。

    在1.2节代码中,app.get()函数将Route实例封装在layer中,作为一个中间件加入栈中,当触发执行时,会将处理函数fn取出执行。

    >route.js
    methods.forEach(function(method){
      Route.prototype[method] = function(){
        var self = this;
        var callbacks = utils.flatten([].slice.call(arguments));
    
        callbacks.forEach(function(fn) {
          if (typeof fn !== 'function') {
            var type = {}.toString.call(fn);
            var msg = 'Route.' + method + '() requires callback functions but got a ' + type;
            throw new Error(msg);
          }
    
          debug('%s %s', method, self.path);
    
          if (!self.methods[method]) {
            self.methods[method] = true;
          }
    
          if (!self.stack) {
            self.stack = [];
          }
          else if (typeof self.stack === 'function') {
            self.stack = [{ handle: self.stack }];
          }
    
          self.stack.push({ method: method, handle: fn });
        });
        return self;
      };
    

    4.中间件触发流程

    4.1主要过程

    中间件触发通过以下代码:

    >express.js
    function createApplication() {
      var app = function(req, res, next) {
        app.handle(req, res, next);
      };
    
      mixin(app, proto);
      mixin(app, EventEmitter.prototype);
    
      app.request = { __proto__: req, app: app };
      app.response = { __proto__: res, app: app };
      app.init();
      return app;
    }
    

    express模块返回一个app作为http.createServer()的回调函数,这样一个http请求将触发执行app.handle()执行中间件,下面我们看看app.handle()的代码:

    app.handle = function(req, res, done) {
      var env = this.get('env');
    
      this._router.handle(req, res, function(err) {
        if (done) {
          return done(err);
        }
    
        // unhandled error
        if (err) {
          // default to 500
          .........
          return;
        }
    
        // 404
        debug('default 404');
        res.statusCode = 404;
        res.setHeader('Content-Type', 'text/html');
        if ('HEAD' == req.method) return res.end();
        res.end('Cannot ' + escapeHtml(req.method) + ' ' + escapeHtml(req.originalUrl) + '
    ');
      });
    };
    

    app.handle()调用了router组件的handle(req,res,fn)函数执行中间件,链式执行完所有中间件后,done函数是定义的错误处理函数,在htpp.createServer(function(res,req,done)中传入,下面将讲述express的核心route组件。

    4.2 router组件

    router组件主要有三个文件组成,index.js和route.js是其主要逻辑部分,layer.js作为中间件封装的数据结构。

    下面的代码是route生成http.verb的函数:

     methods.forEach(function(method){
      Route.prototype[method] = function(){
        var self = this;
        var callbacks = utils.flatten([].slice.call(arguments));
    
        callbacks.forEach(function(fn) {
          if (typeof fn !== 'function') {
            var type = {}.toString.call(fn);
            var msg = 'Route.' + method + '() requires callback functions but got a ' + type;
            throw new Error(msg);
          }
    
          debug('%s %s', method, self.path);
    
          if (!self.methods[method]) {
            self.methods[method] = true;
          }
    
          if (!self.stack) {
            self.stack = [];
          }
          else if (typeof self.stack === 'function') {
            self.stack = [{ handle: self.stack }];
          }
    
          self.stack.push({ method: method, handle: fn });
        });
    	  //console.log(self)
        return self;
      };
    

    1.2节中,application.js里的methods.each调用的就是这里生成http.verb处理函数,Reoute实例化的时候就生成了对应http.verb的处理函数(Route['method'])。

    代码里可以看出,http.verb可以一次添加多个处理函数,形式为function(req,res,next)或者function(req,res).

    下面是router组件构建的函数:

      var proto = module.exports = function(options) {
      options = options || {};
    
      function router(req, res, next) {
        router.handle(req, res, next);
      }
    
      // mixin Router class functions
      router.__proto__ = proto;
    
      router.params = {};
      router._params = [];
      router.caseSensitive = options.caseSensitive;
      router.strict = options.strict;
      router.stack = [];
    
      return router;
    };
    

    router是一个对象构造函数,router.__proto__ = proto引入了整个proto的所有函数,包括use,handle等待相关中间件操作函数,定义了中间件储存的数组,配置等。

    router完全可以作为一个对象定义为 var router ={},源码里,发现router没有进行实例化,所以这个构造函数式没有必要的。

    下面我们看看router是如何触发链式执行的:

     proto.handle = function(req, res, done) {
      .............
      
      // middleware and routes
      var stack = self.stack;
    
      // request-level next
      var parent = req.next;
      done = wrap(done, function(old, err) {
        req.next = parent;
        old(err);
      });
      req.next = next;
    
      .............
    
      next();
    
      function next(err) {
        .........
      }
    }
    

    这里主要展示了中间件执行的过程,每调用一次next()就会有一个中间件触发,并再一次调用next(),看next里的代码:

    if (route) {
      return layer.handle(req, res, next);
    }
    

    layer是一个保存中间件路径,处理函数的数据结构,想详细了解请看源码,上面的代码表示如果路径和这个中间件配置的路径匹配,则执行其回调,next就是我们第一章开头讲的,下一个中间件触发的函数,现在应该知道这个函数从哪来了吧?

    递归执行next,直到执行完为止,执行done,但是我们会发现done一直就是null,不知道这是不是老的nodejs遗留下来的问题,createServer的回调现在不会传入第三个参数了。

    var layer = stack[idx++];
    if (!layer) {
      return done(err);
    }
    

    下面看看route.js 是如何运行的吧

    #router/index.js
     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;
    };
    

    这个函数把一条路由和它的route.dispatch作为一个中间件加入了栈中,并返回一个Route实例,Route实例包含了路由处理的各种方法和信息,其中route.dipatch也是其原型函数,用来处理相同路由的不同http.verb,下面我们看看这个函数。

     Route.prototype.dispatch = function(req, res, done){
      var self = this;
      var method = req.method.toLowerCase();
    
      if (method === 'head' && !this.methods['head']) {
        method = 'get';
      }
    
      req.route = self;
    
      // single middleware route case
      if (typeof this.stack === 'function') {
        this.stack(req, res, done);
        return;
      }
    
      var stack = self.stack;
      if (!stack) {
        return done();
      }
      //这里进行递归地链式调用,遍历所有的处理函数
      var idx = 0;
      (function next_layer(err) {
        if (err && err === 'route') {
          return done();
        }
    
        var layer = stack[idx++];
        if (!layer) {
          return done(err);
        }
    
        if (layer.method && layer.method !== method) {
          return next_layer(err);
        }
    
        var arity = layer.handle.length;
        if (err) {
          if (arity < 4) {
            return next_layer(err);
          }
    
          try {
            layer.handle(err, req, res, next_layer);
          } catch (err) {
            next_layer(err);
          }
          return;
        }
    
        if (arity > 3) {
          return next_layer();
        }
    
        try {
          layer.handle(req, res, next_layer);
        } catch (err) {
          next_layer(err);
        }
      })();
    };
    

    这个函数很长,主要过程可以简单叙述一下,也是通过next_layer函数,链式访问一条路由的post,get等方法的回调函数,根据req.method来判断请求类型,执行相应处理函数,不同http.verb可以执行不同回调,也就是说,express一条路由可以响应多种类型的请求。但是注意到,这样回调函数应该写成function(req, res, next){ ..... next()}
    讲了很多,估计大家都昏头了,下面的流程图会很清晰的让大家知道整个过程。

    5.View的实现

    4.x版本的render和3.x版本不一样,这里以回调的方式进行render,而不在内部调用res.send()示例:

    res.render('index', function(err, html){
      ....
      res.send(html);
    });
    
    res.render('user', { name: 'Tobi' }, function(err, html){
      // ...
    });
    

    res.render()的实现比中间件简单很多,总体来说,经过三次封装,进行了一些配置,调用链条为res.render() => app.render() =>view.render()=> require("jade")/reqiure("ejs").render(),首先看app.engine,将jade或ejs模板引擎的render函数存入了engines数组中

    app.engine = function(ext, fn){
      if ('function' != typeof fn) throw new Error('callback function required');
      if ('.' != ext[0]) ext = '.' + ext;
      this.engines[ext] = fn;
      return this;
    };
    

    app.defaultConfiguration()(app初始化的一个函数),把View的构造函数保存。

      // default configuration
      this.set('view', View);
    

    app.render()将其取出并调用,初始化一个View实例,并执行‘view.render()’渲染模板,注意初始化函数将engines传入了View实例,里面保存了模板引擎的render函数。

      view = new (this.get('view'))(name, {
          defaultEngine: this.get('view engine'),
          root: this.get('views'),
          engines: engines
        });
      .....
      try {
        view.render(opts, fn);
      } catch (err) {
        fn(err);
      }
    

    view.render()执行的便是模板引擎的render函数,fn为渲染完成后的回调函数。

    View.prototype.render = function(options, fn){
    	this.engine(this.path, options, fn);
    };
    

    转自: https://gist.github.com/dlutwuwei/3faf88d535ac81c4e263

  • 相关阅读:
    Clean Docker <none>:<none>
    Conservation Vs Non-conservation Forms of conservation Equations
    What are the differences between an LES-SGS model and a RANS based turbulence model?
    How to permanently set $PATH on Linux/Unix?
    tar解压命令
    C++ (P199—P211)多态 虚函数 抽象类
    C++ (P160—)多继承 二义性 虚基类 “向上转型”
    java与c++的访问权限的问题
    由strupr,strlwr体会如果将字符常量转换为变量进行修改,体会常量的静态存储
    C++ (P103—P154)
  • 原文地址:https://www.cnblogs.com/yongjz/p/5641085.html
Copyright © 2020-2023  润新知