注册 登录  
 加关注
   显示下一条  |  关闭
温馨提示!由于新浪微博认证机制调整,您的新浪微博帐号绑定已过期,请重新绑定!立即重新绑定新浪微博》  |  关闭

snoopyxdy的博客

https://github.com/DoubleSpout

 
 
 

日志

 
 

expressjs源码解读(四) —— 3.0版本expressjs  

2011-12-21 16:44:00|  分类: node |  标签: |举报 |字号 订阅

  下载LOFTER 我的照片书  |
前面介绍了connect.js的源代码和工作原理,除了route.js实现比较复杂外,其他的例如middleware实现都非常巧妙,值得学习。
我们来看下3.0版本的expressjs有什么新东西,初看3.0 版本的expressjs比起2.4版本代码变化不少,值得我们学习的route模块也全部重新改写了,我们来简单看下:

1、express.js
文件输出 createApplication 方法:
function createApplication() {
  var app = connect();
  utils.merge(app, proto);
  app.request = { __proto__: req };
  app.response = { __proto__: res };
  app.init();
  return app;
}
app继承自 connect,并且app合并proto对象,然后将app.requset对象的原型链指向req,同理respose。最后初始化,返回app对象。下面是一些输出对象,值得关注的是这一段:
for (var key in connect.middleware) {
  Object.defineProperty(exports, key , Object.getOwnPropertyDescriptor(connect.middleware, key));
}
我们先看  Object.defineProperty(obj, prop, descriptor)  这个方法,这是javaScript 1.8.5的功能之一,是用来给obj参数定义属性或是方法的,参数解释如下:(以下例子拷贝自司徒正美,原文地址
1、obj:目标对象
2、prop:需要定义的属性或方法的名字。
3、descriptor:目标属性所拥有的特性,可以为如下几种:
value:属性的值
writable:如果为false,属性的值就不能被重写。
get: 一旦目标属性被访问就会调回此方法,并将此方法的运算结果返回用户。
set:一旦目标属性被赋值,就会调回此方法。
configurable:如果为false,则任何尝试删除目标属性或修改属性以下特性(writable, configurable, enumerable)的行为将被无效化。
enumerable:是否能在for...in循环中遍历出来或在Object.keys中列举出来
举个例子:
1、Object.defineProperty(a,"bloger",{get:function(){return "司徒正美"}});   //a.bloger是司徒正美
2、Object.defineProperty(b, "p", {value:"这是不可改变的默认值" ,writable: false }); //这样定义的p是不可以被修改的
其他的例子不列举了,可以自己尝试下。
而 Object.getOwnPropertyDescriptor(object, propertyname) 方法是配合上述方法使用的,获取obj参数的对应key的值,并且以上述 descriptor的方式返回一个data property 或 Accessor Properties 对象, 此方法接收2个参数:
1、object:Required. The object that contains the property. This can be a native JavaScript object or a Document Object Model (DOM) object.
2、propertyname :Required. A string that contains the name of the property.
返回的结果可能是以下2种:
1、Data descriptor attribute:{value :value, writable:writable, enumerable:enumerable, configurable:configurable}
2、Accessor descriptor attribute:{get:function(){}, set:function(value){}, enumerable:enumerable, configurable:configurable}

言归正传,那段for循环就是给exports对象定义和connect.middleware一样的属性和方法的,并且复制同样的数据属性和访问属性,上面那些新特性可以让我们不必再定义闭包,轻松实现对象的私有变量和可读可写可枚举。

2、application.js
貌似expressjs的核心就在这个文件上了,我们简单看一下:
methods = Router.methods.concat('del', 'all')
为methods数组添加两个新内容,methods数组存放就是get,post,option,head等http请求方法。
 methods.forEach(function(method){
    self.lookup[method] = function(path){
      return self._router.lookup(method, path);
    };

    self.match[method] = function(path){
      return self._router.match(method, path);
    };

    self.remove[method] = function(path){
      return self._router.lookup(method, path).remove();
    };
  });
注册路由方法函数,具体 self._router 是什么,我们下一节讨论路由时再说。

  this._router = new Router(this);
  this.routes = this._router.routes;
实例化Router类,

app.use = function(route, fn){ ... }
借助connect.js的use方法,做中间件。

methods.forEach(function(method){ ... }
对之前methods数组进行重组,让其支持app[method](path,callback) 等于调用 app.router(method,path,callback)

app.param = function(name, fn){
  var self = this
    , fns = [].slice.call(arguments, 1);

  // array
  if (Array.isArray(name)) {
    name.forEach(function(name){
      fns.forEach(function(fn){
        self.param(name, fn);
      });
    });
  // param logic
  } else if ('function' == typeof name) {
    this._router.param(name);
  // single
  } else {
    if (':' == name[0]) name = name.substr(1);
    fns.forEach(function(fn){
      self._router.param(name, fn);
    });
  }
  return this;
};
这个方法是用来匹配或者获取参数的,借助的还是 _router.param 方法

其他代码不多说了,主要是实现api的一些方法。

3、request.js

var req = exports = module.exports = {
  __proto__: http.IncomingMessage.prototype
};
这里定义的一些req的方法是直接在http.IncomingMessage原型链上定义的。

这个文件大部分是方便获取客户端request过来的请求头信息的。

4、response.js

res.send = function(body){...}
对请求的响应,我们来看一下这个方法
if (2 == arguments.length) {
    this.statusCode = body;
    body = arguments[1];
  }
如果参数2个,则认为第一个参数是状态码,第二个是响应的主体内容

switch (typeof body) {
    // response status
    case 'number':
      this.header('Content-Type') || this.contentType('.txt');
      this.statusCode = body;
      body = http.STATUS_CODES[body];
      break;
    // string defaulting to html
    case 'string':
      if (!this.header('Content-Type')) {
        this.charset = this.charset || 'utf-8';
        this.contentType('.html');
      }
      break;
    case 'boolean':
    case 'object':
      if (null == body) {
        body = '';
      } else if (Buffer.isBuffer(body)) {
        this.header('Content-Type') || this.contentType('.bin');
      } else {
        return this.json(body);
      }
      break;
  }
如果是数字,认为是输出状态码,则body为: http.STATUS_CODES[num],这里不得不再次批评写node.js api的人,STATUS_CODES也是 http模块对外exports的一个对象,里面记录了状态码和英文内容对应值。
如果是字符串,则认为是html,修改请求头信息mimetype值;
如果是buffer,则输出buffer,否则认为是json数据输出。

  if (undefined !== body && !this.header('Content-Length')) {
    this.header('Content-Length', Buffer.isBuffer(body)
      ? body.length
      : Buffer.byteLength(body));
  }
定义Content-Length

  // respond
  this.end(head ? null : body);
  return this;
返回给客户端,并且关闭连接。这如果想多次调用res.send()恐怕不行了,这个方法很强大,可以做一些自动的处理,建议安装好expressjs第一件事情就是把这里的this.end改为this.write,然后我们可以手动调用res.end关闭连接,可能有些地方需要使用类似bigpipe的多次响应不关闭。

我们来看下跳转的方法:
res.redirect = function(url){...}

 if (!~url.indexOf('://')) {
    var path = app.path();
// relative to path
    if (0 == url.indexOf('./') || 0 == url.indexOf('..')) {
      url = req.path + '/' + url;
    // relative to mount-point
    } else if ('/' != url[0]) {
      url = path + '/' + url;
    }

    // Absolute
    var host = req.header('Host')
      , proto = req.header('X-Forwarded-Proto')
      , tls = 'https' == proto || req.secure;

    url = 'http' + (tls ? 's' : '') + '://' + host + url;
}
这段代码是用来拼装 将要跳转的url的
这又是作者装B的写法,如果没有找到://则将path设置为网站的域名目录
如果是相对目录是 ./ 或者 ../ 开头的,则直接将当前路径与它拼上。
如果不是/开头的,则拼上path+‘/’+url
如果是绝对路径,就是以/开头的,则加上请求host头等信息拼上url

 if (req.accepts('html')) {
    body = '<p>' + statusCodes[status] + '. Redirecting to <a href="' + url + '">' + url + '</a></p>';
    this.header('Content-Type', 'text/html');
  } else {
    body = statusCodes[status] + '. Redirecting to ' + url;
    this.header('Content-Type', 'text/plain');
  }

  // Respond
  this.statusCode = status; 
  this.header('Location', url);
  this.end(head ? null : body);
这里如果是访问html的,则返回一段html代码表示跳转,否则返回一段文字,最后设置Location头的url,表示跳转到此url地址,然后客户端重新请求此url地址,获取正确的内容。
这里直接修改ServerResponse.prototype.statusCode属性,node.js的api里并不支持这么做,API推荐使用:response.setHeader(name, value) 来设置修改http请求状态码,看来作者在写expressjs时很详细的了解了node.js http模块的源码,改天我也来写份心得。

  评论这张
 
阅读(2583)| 评论(2)
推荐 转载

历史上的今天

在LOFTER的更多文章

评论

<#--最新日志,群博日志--> <#--推荐日志--> <#--引用记录--> <#--博主推荐--> <#--随机阅读--> <#--首页推荐--> <#--历史上的今天--> <#--被推荐日志--> <#--上一篇,下一篇--> <#-- 热度 --> <#-- 网易新闻广告 --> <#--右边模块结构--> <#--评论模块结构--> <#--引用模块结构--> <#--博主发起的投票-->
 
 
 
 
 
 
 
 
 
 
 
 
 
 

页脚

网易公司版权所有 ©1997-2016