Skip to content

Instantly share code, notes, and snippets.

Embed
What would you like to do?
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);
};
@zhouaini528

This comment has been minimized.

Copy link

commented Jun 7, 2014

mk

@stepli1010

This comment has been minimized.

Copy link

commented Jul 27, 2014

dlut,mark.

@youwenda

This comment has been minimized.

Copy link

commented Aug 6, 2014

学习了 非常好的教程

@nt66

This comment has been minimized.

Copy link

commented Apr 23, 2015

nice!

@Rainboylvx

This comment has been minimized.

Copy link

commented Feb 2, 2016

MK

@GHKyle

This comment has been minimized.

Copy link

commented May 19, 2016

mark

@leijianning

This comment has been minimized.

Copy link

commented May 24, 2016

thx

@ipengyo

This comment has been minimized.

Copy link

commented Sep 4, 2017

mark

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment
You can’t perform that action at this time.