express源码剖析(3)

express/appliction.js

application.js对外的方法可以分成四类:

  • 设置/初始化成员变量,主要是对成员变量settings进行设置,这样的方法有:enabled、enabled、disabled、 disable和set方法。
  • 设置路由和对每个路由设置中间件:这样的方法有:use.router,all,param和methods方法。
  • 设置模板引擎的方法:engine和render。
  • 启动node服务器对res和req操作的方法:listen和handler。

1.解析app.engine方法

express的api中有app.engine方法,例如:

app.engine("handlebars",handlebars.engine);

看下这个函数的的核心代码就是:

 // get file extension,ext为文件扩展名
  var extension = ext[0] !== ‘.‘
    ? ‘.‘ + ext
    : ext;

  // store engine
  this.engines[extension] = fn;

 1.2 解析app.use方法

var express = require(‘express‘);
var app = express();
function handlerWrap(){
    console.log("1");
}
app.use("/", handlerWrap);

代码直接进入app.user中的下列代码:

//lazyrouter函数不对外提供
this.lazyrouter();  

它可以说是整个express的核心代码:

app.lazyrouter = function lazyrouter() {
    //给app这个对象定义私有变量_router,它是一个对象Router。
    if (!this._router) {
       this._router = new Router({
        caseSensitive: this.enabled(‘case sensitive routing‘), //false
        strict: this.enabled(‘strict routing‘)                 //false
      });
      this._router.use(query(this.get(‘query parser fn‘)));
       //this.get(‘query parser fn‘)为一个函数:       //middleware.init(this)也是一个处理函数
      this._router.use(middleware.init(this));
   }
 };    

尤其是给app.router赋值之后,初始化下列代码:

this._router.use(query(this.get(‘query parser fn‘)));  //this.get(‘query parser fn‘)为一个函数:
this._router.use(middleware.init(this));//必须要值得注意的事情是:router.use(path,fn);  实际上是为path建立layer,有多少个fn,就建立多少个Layer,然后把这个layer压入router.stack数组中。

看看router的构造函数,它的定义和app的定义很类似。

 if (!fn || !fn.handle || !fn.set) {
      //handlerWrap函数为handler、 set成员!
      return router.use(path, fn);
 } // 进入router/index.js中,执行proto.use()方法。 // 实际上是给router.stack添加一个layer成员,

  var layer = new Layer(path, {
    sensitive: this.caseSensitive,
    strict: false,
    end: false
  }, fn);

  layer.route = undefined;  //给layer.route赋值undefined

this.stack.push(layer);

1.3 解析router.route

ayer.js作为中间件封装的数据结构,接着看Layer包:

function Layer(path, options, fn) {
  if (!(this instanceof Layer)) {
    // this是Layer的实例
    return new Layer(path, options, fn);
  }

  debug(‘new %s‘, path);
  var opts = options || {};

  this.handle = fn;
  this.name = fn.name || ‘<anonymous>‘;
  this.params = undefined;
  this.path = undefined;
  this.regexp = pathRegexp(path, this.keys = [], opts);

  if (path === ‘/‘ && opts.end === false) {  
    this.regexp.fast_slash = true;
  }
}

接下来看看pathRegexp,http://blog.csdn.net/chszs/article/details/51055229

rounter.use直接把该函数构造成一个Layer成员,实际上是给router.stack添加一个layer成员。

同理:this._router.use(middleware.init(this))的作用是把该函数:

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();
  };

构造成一个Layer成员,实际上是给router.stack添加一个layer成员。

这样在express初始的时候,rounter的stack数组中有两个回调函数了。如果说调用app.user(fn),都是给rounter的stack数组中添加回调函数。

执行过程:

执行的入口函数是:

var app = function(req, res, next) {
    app.handle(req, res, next);
  };

它调用application.js中handler函数,一般来说next函数为没有的,在handler函数中封装了一个callback:

// final handler, 默认就是next
  var done = callback || finalhandler(req, res, {
    env: this.get(‘env‘),
    onerror: logerror.bind(this)
  });

finalhandler是一个npm包,可以在github上看它的作用,它的作用就是一个http请求的最后一步的处理方式。

接着请求会转向rounter.handler函数,在handler函数中,最终是执行next(),在看next()之前,先来弄清除几个回调函数,第一个:done:

function restore(fn, obj) {
  var props = new Array(arguments.length - 2);
  var vals = new Array(arguments.length - 2);
  for (var i = 0; i < props.length; i++) {
    props[i] = arguments[i + 2];  // 储存除fn,obj以外的参数
    vals[i] = obj[props[i]];      // obj中的对象。‘baseUrl‘, ‘next‘, ‘params‘
  }
  return function(err){
    // restore vals
    for (var i = 0; i < props.length; i++) {
      obj[props[i]] = vals[i];
    }
    return fn.apply(this, arguments);
  };
}
/*out是finalhandler(req, res, {
    env: this.get(‘env‘),
    onerror: logerror.bind(this)
  });
*/
var done = restore(out, req, ‘baseUrl‘, ‘next‘, ‘params‘);/*  done = function(err){    给req赋值,重新储存‘baseUrl‘, ‘next‘, ‘params‘,      返回fn.apply(this,arguments);  }*/

next()函数中如果没有获取到req中的pathname,那么它会执行:

if (path == null) {
      //layerError为undefined
      return done(layerError);   
}
/*
   结果是:out.apply(undefined,undefined);最终转向finalhandler执行完毕
*/

最后,重点弄清楚的代码是:

while (match !== true && idx < stack.length) {
      layer = stack[idx++]; //第一个Layer
      match = matchLayer(layer, path); //
      route = layer.route;

      if (typeof match !== ‘boolean‘) {
        // hold on to layerError
        layerError = layerError || match;
      }

      if (match !== true) {
        continue;
      }

      if (!route) {
        // process non-route handlers normally
        continue;
      }

      if (layerError) {
        // routes do not match with a pending error
        match = false;
        continue;
      }

      var method = req.method;
      var has_method = route._handles_method(method);

      // build up automatic options response
      if (!has_method && method === ‘OPTIONS‘) {
        appendMethods(options, route._options());
      }

      // don‘t even bother matching route
      if (!has_method && method !== ‘HEAD‘) {
        match = false;
        continue;
      }
    }

    // no match
    if (match !== true) {
      return done(layerError);
    }

    // store route for dispatch on change
    if (route) {
      req.route = route;
    }

    // Capture one-time layer values
    req.params = self.mergeParams
      ? mergeParams(layer.params, parentParams)
      : layer.params;
    var layerPath = layer.path;

    // this should be done for the layer
    self.process_params(layer, paramcalled, req, res, function (err) {
      if (err) {
        return next(layerError || err);
      }

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

      trim_prefix(layer, layerError, layerPath, path);
    });
  }

看看Layer.matchLayer(layer, path),他返回是true/false;不过会给该Layer添加:params = {};path 和keys。

重点代码:

fns.forEach(function (fn) {
    // non-express app,fn && fn.handler && fn.set为true
    if (!fn || !fn.handle || !fn.set) {
      return router.use(path, fn);
    }

    debug(‘.use app under %s‘, path);
    //给fn添加两个变量
    fn.mountpath = path;
    fn.parent = this;

    // restore .app property on req and res
    router.use(path, function mounted_app(req, res, next) {
      var orig = req.app;
      fn.handle(req, res, function (err) {
        req.__proto__ = orig.request;
        res.__proto__ = orig.response;
        next(err);
      });
    });

    // mounted an app
    fn.emit(‘mount‘, this);
  }, this);

1.2 send函数解析:

 1 function send(req, res, status, body) {
 2   function write() {
 3     res.statusCode = status
 4
 5     // security header for content sniffing
 6     res.setHeader(‘X-Content-Type-Options‘, ‘nosniff‘)
 7
 8     // standard headers
 9     res.setHeader(‘Content-Type‘, ‘text/html; charset=utf-8‘)
10     res.setHeader(‘Content-Length‘, Buffer.byteLength(body, ‘utf8‘))
11     //请求方法为head,不需要发送body,header的作用多用于自动搜索机器人获取网页的标志信息,或者rss种子信息,或者传递安全认证信息等。
12     if (req.method === ‘HEAD‘) {
13       res.end()
14       return
15     }
16
17     res.end(body, ‘utf8‘)
18   }
19
20   if (isFinished(req)) {      //req流已经结束
21     write()                       //给res写入信息,并且返回给客户端
22     return
23   }
24
25   // unpipe everything from the request
26   unpipe(req)                   //取消在pipe中设置的通道
27
28   // flush the request
29   onFinished(req, write)    //req流完成之后,触发write事件。
30   req.resume()                 //开启req流
31 }

注意,express在finalhandler包中重写了流的unpipe的方法,在onfinished包中。

时间: 2024-12-29 15:39:04

express源码剖析(3)的相关文章

express源码剖析1

在通读源码之前,先把一些比较难理解的代码吃透: 1.EventEmitter.prototype mixin(app, EventEmitter.prototype, false); app为一个函数,也是对象. mixin是一个类库(merge-descriptors)它就是一种mixin设计模式,作用是让app这个对象具有EventEmitter.prototype的方法. 第三个参数表示"是否重新定义app中与EventEmitter.prototype中存在重名的方法. 2.EventE

express源码剖析--Router模块

1.加载模块执行代码: methods.forEach(function(method){ //method是http协议的各种请求方法,如:get,put,headee,post Route.prototype[method] = function(){ ....为method的各种方法构造一个Layer,并且放入stack数组中 }; });//其实router.all()的功能和methods的功能差不多. 2.构造函数 function Route(path) { //三个成员, 路由的

《STL源码剖析》---stl_pair.h阅读笔记

pair是STL中的模板类型,它可以存储两个元素,它也被称作"对组".在map中已经用到了它,pair其实就是一个struct结构,存有两个public的元素,重载了几个运算符,没有什么成员函数,源代码很简单. G++ 2.91.57,cygnus\cygwin-b20\include\g++\stl_pair.h 完整列表 /* * * Copyright (c) 1994 * Hewlett-Packard Company * * Permission to use, copy,

《STL源码剖析》---stl_tree.h阅读笔记

STL中,关联式容器的内部结构是一颗平衡二叉树,以便获得良好的搜索效率.红黑树是平衡二叉树的一种,它不像AVL树那样要求绝对平衡,降低了对旋转的要求,但是其性能并没有下降很多,它的搜索.插入.删除都能以O(nlogn)时间完成.平衡可以在一次或者两次旋转解决,是"性价比"很高的平衡二叉树. RB-tree(red black tree)红黑树是平衡二叉树.它满足一下规则 (1)每个节点不是红色就是黑色. (2)根节点是黑色. (3)如果节点为红色,则其子节点比为黑色. (4)任何一个节

《STL源码剖析》---stl_iterator.h阅读笔记

STL设计的中心思想是将容器(container)和算法(algorithm)分开,迭代器是容器(container)和算法(algorithm)之间的桥梁. 迭代器可以如下定义:提供一种方法,能够依序寻访某个容器内的所有元素,而又无需暴露该容器的内部表达方式. 在阅读代码之前,要先了解一个新概念:Traits编程技法 template <class T> struct MyIter { typedef T value_type //内嵌型别声明 T *ptr; MyIter(T *p = 0

STL 之 queue、priority_queue 源码剖析

/* * Copyright (c) 1994 * Hewlett-Packard Company * * Permission to use, copy, modify, distribute and sell this software * and its documentation for any purpose is hereby granted without fee, * provided that the above copyright notice appear in all c

《STL源码剖析》---stl_hashtable.h阅读笔记

在前面介绍的RB-tree红黑树中,可以看出红黑树的插入.查找.删除的平均时间复杂度为O(nlogn).但这是基于一个假设:输入数据具有随机性.而哈希表/散列表hash table在插入.删除.查找上具有"平均常数时间复杂度"O(1):且不依赖输入数据的随机性. hash table的实现有线性探测.二次探测.二次散列等实现,SGI的STL是采用开链法(separate chaining)来实现的.大概原理就是在hash table的每一项都是个指针(指向一个链表),叫做bucket.

《STL源码剖析》---stl_alloc.h阅读笔记

这一节是讲空间的配置与释放,但不涉及对象的构造和析构,只是讲解对象构造前空前的申请以及对象析构后空间怎么释放. SGI版本的STL对空间的的申请和释放做了如下考虑: 1.向堆申请空间 2.考虑了多线程.但是这节目的只是讲解空间配置与释放,因此忽略了多线程,集中学习空间的申请和释放. 3.内存不足时的应变措施 4.考虑到了内存碎片的问题.多次申请释放小块内存可能会造成内存碎片. 在C++中,内存的申请和释放是通过operator new函数和operator delete函数,这两个函数相当于C语

《STL源码剖析》---stl_multiset.h阅读笔记

STL中的set不允许键值重复,因此就有了multiset.multiset和set操作一样,功能一样,但是multiset允许键值重复,在使用红黑树的插入操作是,用的是insert_equal,而set用的是insert_unique,其他代码一样. G++ 2.91.57,cygnus\cygwin-b20\include\g++\stl_multiset.h 完整列表 /* * * Copyright (c) 1994 * Hewlett-Packard Company * * Permi