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

snoopyxdy的博客

https://github.com/DoubleSpout

 
 
 

日志

 
 

expressjs源码解读(一) —— http(s)模块  

2011-12-15 15:05:46|  分类: node |  标签: |举报 |字号 订阅

  下载LOFTER 我的照片书  |
expressjs是目前最流行的node.js web服务器框架,很多前辈都说看expressjs的源码,特别是connect的源码有很大帮助,最近闲下来了,我也来研究一下expressjs到底有何秘密,提升一下自己的node.js水平,顺便看看expressjs有没有什么地方不合理,或是可以提升一下性能。

我使用expressjs做过一些简单的2次开发,但是都没有去修改它的源码,只是在expressjs的基础上封装了一些方法,丰富了一些api。所以对于expressjs的API比较熟悉,但是对于源码还是很陌生的,第一次看,利用expressjs搭建了一个个人站:spout.cnodejs.net

1、神秘的app
大家使用expressjs第一影响便是使用app来创建一个web服务器,接下来我就以app为线索解读源码。
var app = require('express').createServer();
app.get('/', function(req, res){
  res.send('hello world');
});
app.listen(3000);
这个app是什么呢?打开lib文件夹express.js文件,我们看到:
var exports = module.exports = connect.middleware;
原来require('express')就是加载了connect.middleware,至于这个connect.middleware是什么,我们下一节讨论connect时再说。
回到app,我们看createServer这个方法:
exports.createServer = function(options){
  if ('object' == typeof options)  return new HTTPSServer(options, Array.prototype.slice.call(arguments, 1));
 else return new HTTPServer(Array.prototype.slice.call(arguments));
};
这里为了缩小篇幅,我把一些大括号去掉了,下同。require('express').createServer()方法是expressjs为connect.middleware新增加的一个方法,返回一个httpserver实例或者httpsserver实例,我们以常用的httpserver为主来解读app。

2、http.js模块
从上我们知道,http.js模块就是exports一个httpserver的类,我们看一下这个类:
var app = HTTPServer.prototype;
function HTTPServer(middleware){
  connect.HTTPServer.call(this, []);
  this.init(middleware);
};
这里的app并不是我们上面说的app,而是供http.js内部使用的一个变量,虽然它也有和上面那个app一样的属性和方法。
函数里的内容是典型的js类继承方式,不理解的同学可以看我的另外一篇blog,<关于js的继承>
通过connect.HTTPServer.call(this, []);这行代码可以让httpserver类继承connect.HTTPServer类的所有属性和方法。当然这里还少一行代码:
HTTPServer.prototype.constructor = HTTPServer;  这样实例化httpserver以后查找它的construcor属性不会出错,而是指向httpserver。
这个middleware参数是什么呢?示例代码在实例化httpserver时并没有传递任何参数,我们先不管它。

我们去connect库里找一下HTTPServer,发现:
var Server = exports.Server = function HTTPServer(middleware) {
  this.stack = [];
  middleware.forEach(function(fn){
    this.use(fn);
  }, this);
  http.Server.call(this, this.handle);
};
这个方法先是将middleware文件夹下的一些中间件作为回调函数,存入this.stack数组中,即为了当有请求过来,先执行中间件的回调,然后再抛给应用者,说白了就是对请求过来的信息的封装,方便我们使用。具体middleware我们之后再讨论。
然后直接调用http.Server.call(this, this.handle);这个来注册onrequest事件,http.Server我们需要去扒开node.js源码来看了:
function Server(requestListener) {
  if (!(this instanceof Server)) return new Server(requestListener); //这里写的很巧妙,值得学习
  net.Server.call(this, { allowHalfOpen: true });

  if (requestListener) {
    this.addListener('request', requestListener);//成功注册handle方法,当有请求过来变会调用handle方法
  }

  // Similar option to this. Too lazy to write my own docs.
  // http://www.squid-cache.org/Doc/config/half_closed_clients/
  // http://wiki.squid-cache.org/SquidFaq/InnerWorkings#What_is_a_half-closed_filedescriptor.3F
  this.httpAllowHalfOpen = false;

  this.addListener('connection', connectionListener);
}
这样我们就顺利的将HTTPServer.prototype.handle方法注册到了request事件中。
接下来我们就来看handle方法,我们究竟注册了怎样一个方法给request事件呢:
 * Handle server requests, punting them down
 * the middleware stack.
这2行是对handle方法的注释,字面意思是 处理服务器的请求,将他们传递给中间件的堆栈。根据字面理解这个方法就是执行之前放在stack数组中的一些回调函数的,做到重新封装或自动处理的目的。
不过在源码中只找到了:self.emit('request', req, res); 目前还不知道Server.prototype.handle = function(req, res, out) {...} 中的out参数从何而来,可能在middleware中有应用吧。
handle方法中有一个闭包,function next()
layer = stack[index++];一个个取出来,等待屠宰,哈哈。
1、先判断是否有layer,如果没有则根据是否传递错误参数来输出500(内部错误)和404页面。当然其中有一个诡异的 out 参数,目前不清楚它的由来。
2、在根据一定的条件执行midddleware中的回调,或者next(),调用下一个stack中的回调。

3、app到底做了什么?
绕了这么一大大大圈的,我们的app到底做了什么呢?
我们的app其实只做了两件事情:
1、将api源码的http.createServer(fn);这样的形式改写,注册了一个request事件的回调函数handle,并且可以在任何时候修改stack堆栈的内容,达到修改中间件的目的。最后执行listen()方法,监听指定端口。也就是说一旦有客户端的request访问指定端口,则将触发handle方法,挨个将stack堆栈中的回调执行,对req进行封装,比如提供session服务,提供cookie服务或是直接响应静态文件给客户端,做到了web服务器的事情。
2、app是HTTPServer的实例,HTTPServer继承自http.Server,所以app具有http.Server的属性和方法,看node.js代码知道,http.Server原来继承自net模块,listen方法也是net模块中的,所以app是一个强化版本的http.Server实例。

4、总结
没想到一个app可以写这么多内容,我们顺藤摸瓜,探究了app对象的真实情况,感叹 connect 改写api的巧妙。最后我来分析一下上面橙色字部分的代码实现,简单改写一下:
var x = function(a){
  if (!(this instanceof arguments.callee)){
 return new arguments.callee(a);
    };
  this.u = a
}
var y = x('888');
alert(y.u);
var z = new x('888');
alert(z.u);
你会发现,alert()两次的值是相同的,这样就省略了new关键字,同时又不排斥new 关键字。扩展一下jquery的写法:
function aaa(a){
    return new aaa.prototype.init(a);
}
aaa.prototype.init = function(a){
    console.log(111);
    this.name=a;
}
aaa.prototype.init.prototype = aaa.prototype;
var a = aaa('aaa')
同样是为了不排斥new关键字,并且达到了扩充aaa类和init类都可以达到同时扩充。
可能还有一句比较诡异的:
Server.prototype.__proto__ = http.Server.prototype;//指向 Server.prototype 原型链的父链,
这里这么写是为了添加Server.prototype上的方法不破坏 http.Server.prototype,又可以继承 http.Server.prototype 上的方法。


  评论这张
 
阅读(3170)| 评论(9)
推荐 转载

历史上的今天

在LOFTER的更多文章

评论

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

页脚

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