Skip to content

Instantly share code, notes, and snippets.

@haocong
Forked from dlutwuwei/express.md
Created August 18, 2016 16:43
Show Gist options
  • Star 0 You must be signed in to star a gist
  • Fork 0 You must be signed in to fork a gist
  • Save haocong/32468f588742821cb222323dd4eb7964 to your computer and use it in GitHub Desktop.
Save haocong/32468f588742821cb222323dd4eb7964 to your computer and use it in GitHub Desktop.
express4.2源码解析

title: express4.2源码解析 date: 2014-05-18 15:00:50 categories: express tags: [nodejs, node, js, 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');
});

运行结果为:

"C:\Program Files\nodejs\node.exe" index.js Listening on port 3000 hello middleware 127.0.0.1 - - [Fri, 16 May 2014 05:16:27 GMT] "GET / HTTP/1.1" 200 11 "-" "Mozilla/5.0 (Windows NT 6.1; WOW64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/34.0.1847.131 Safari/537.36"

我们添加了一个自定义的中间件,打印出 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的文件结构图:

file

  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中:

```js
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) + '\n');
  });
};

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);
};
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment