红联Linux门户
Linux帮助

node.js模块connect源码分析

发布时间:2014-11-25 22:00:58来源:linux网站作者:dead-horse

connect是一个web server中间件。
使用方法:
var connect = require('connect');
connect(
connect.static(__dirname + '/public', { maxAge: 0 })
, function(req, res) {
res.setHeader('Content-Type', 'text/html');
res.end('<img src="/tobi.jpeg" />')
}
).listen(3000);


思路:

通过connect创建一个http|https server,提供http server的所有功能。

connect是原型继承于http server的,它会用use到的中间件替换掉server的requestListener。

通过connect.use(route, handle)来对每一个路由添加中间件,这些中间件handle会与route绑定保存在一个stack里面,每次有request请求的时候,遍历这个堆,找到对应route的handle,执行handle,如果handle最后调用了next(),就会继续寻找并执行下一个匹配的handle。

通过封装handle,可以很容易的在connect基础上添加更多的middleware。


connect.js

有一个createServer方法,可以通过connect()访问到。根据第一个参数,如果是object,就当作是https的选项,创建HTTPSServer,如果第一个参数不是object,则创建HTTPServer,所有的参数(除了https的选项)都是一个中间件handle,会在HTTPServer绑定到‘/’路径上。

HTTPSServer是在HTTPServer的基础上添加了一层,可以启用HTTPS服务。

同时,connect.js会读取middleware文件夹,把里面的中间件读取到,为他们创建getter,可以通过connect.static()访问到。

而每一个中间件文件暴露在外的函数都是返回一个handle。


http.js

HTTPServer:初始化的时候会把所有的参数当作handle存放进stack,然后以handle方法为requestListener调用http.Server方法。

HTTPServer随后会继承http.Server的原型。

use(route, handle)

把handle去除外壳之后绑定到route上面,存入stack中。

handle(req, res, next)

遍历整个stack,寻找到req.url与route匹配的元素,执行它的handle。当所有的元素都遍历完还有错误,则输出。


util.js

这是一个工具包,里面包含了用到的各种工具函数。

pause(obj)

把传递进来的obj对象的'data'和'end‘事件都保存下来,返回两个函数:end():不再保存事件。resume():停止保存并把之前保存的事件释放出去给obj再次捕获,达到暂停这个obj对象的效果。(感觉可能会有bug,如果在这里释放的时候又有'data'或者'end'事件触发会不会导致顺序变乱?)

parseCookie(str)

把str以;或者,为分隔符分开。每一个都是一个cookie键值对,然后再以=分开。去除value的引号。每个键只能被取得一次。

中间件:

router
connect的route使用方法和express类似。
1 connect(connect.route(function(app){
2     app.get('/:id', middle1, middle2, cb);
3     app.post('/admin', cbpost);
4 }));

route.js内有一个_methods数组,存放所有的route请求方法名称。(get/post/put/...)。
methods对象和routes对象根据_methods内的名称,包含着响应的元素,如:methods['get'], routes['get']。

methods对象,根据_methods数组内的方法名称,为每一个方法调用来一个生产函数,这个函数首先把routes对象内的成员赋值[],然后返回一个函数,这个函数用来产生routes的内容。
methods还有一个元素param,调用它可以为path中出现了某个param的时候设置对应的处理方法。
例如:
app.param('id', function(req, res, next, val){}),
当path中有param id出现的时候,会先调用这个注册的函数再进行后面的操作。

在进行完上述对象的初始化之后,route模块会进行fn.call(this, methods)的调用,即用methods作为参数调用传递进来的匿名函数。所以在app.get('/:id', cb, cb1);的时候,实际调用的是methods.get('/:id', cb, cb1),而methods.get即是之前生产函数的返回函数。
这个函数的处理:cb为这条路由的handle,middle1..middle2..等中间件函数将会存放在cb.middleware数组中(这里会产生一个bug)。然后把'/'转化成为正则对象,然后在转化正则的时候,可能会遇到路径里面有:id等key,会把这些key存放到keys里面。
最终的routes内将会多处一条routes['GET']的记录:

GET:
[ { fn: [Object],
path: /^\/(?:([^\/]+?))\/?$/i,
keys: [Object],
orig: '/:id',
method: 'GET' } ]

刚才说会产生一个bug,是当有两条以上的route以cb作为handle的时候:
app.get('/:id', middle1, middle2, cb);
app.get('/:id/test', middle3, cb);
因为最终的handle都是cb,此时cb的middleware数组会在第二次处理get的时候把第一次的覆盖掉,造成第一次的middleware被替换。

至此,所有的准备工作完成了,然后会返回一个router函数作为handle。


实际request请求触发的时候:

作为handle的router函数被调用,先通过match(req, routes, i)函数,查找req.method对应方法的route的path,与req.pathName匹配。找到路径匹配的把这个route内的这个对象内的fn,同时把keys params method存放到fn里面整合称为一个route返回。返回的route内容形式为
{ [Function]
middleware: [ [Function], [Function] ],
keys: [ 'id' ],
method: 'GET',
params: [ id: 'ca' ] }
然后函数去寻找是否通过methods.param定义了这条route中的param的处理函数,如果有,在这里就执行完对应param的处理函数。之后执行middleware数组内的函数,最后执行这个route。即上一段中说到的fn。这之中能够链式执行下去的条件是中间函数都执行了next(),继续调用下去,当然也可以其中某个函数就结束整个处理。


bodyParser

bodyParser用来解析post方法传递过来的参数。
只接受mime类型为
application/x-www-form-urlencoded
application/json
multipart/form-data
三种的非GET和HEAD请求。

application/x-www-form-urlencoded通过模块qs.parse来解析。
application/json通过JSON.parse解析。
multipart/form-data是文件上传,通过formidable解析。


static
static是一个静态文件服务器。
connect.static(root, options)会产生一个handle,handle设置默认的options然后调用send函数。

options内容:
root:静态服务器的根路径,必须由connect.static传入。
path:访问的文件路径
getOnly:访问方法限制(默认是true:只允许get方法访问 )
maxAge:时间限制
redirect:在访问的路径是目录的时候,如果允许redirect,则会redirect到这个目录下的index.html文件,默认为true
callback:在每次静态服务之后调用的函数(包括发生错误,发生错误之后不会再调用next)。
hidden:是否允许访问隐藏文件(默认为false)

根据这些参数来决定访问限制。

支持conditional和range。


最终通过
var stream = fs.createReadStream(path, opts);
stream.pipe(res);
管道的方式来传送文件内容。