相信用过node.js的人都多多少少听说过或用过express框架,而express正是在connect的基础上搭建的,可以是express是connect的升级版,如果我们读懂了connect的源码的话,那么接下来读懂express源代码也应该是不在话下了,其实connect只有200多行代码,读懂也不是很难的,那就让我们来分析一下它的源码吧.
先来看一个Hello world例子:
|
|
这就是一个很清晰简洁的一个链式调用,connect提供了一种新的组织代码的方式来与请求和响应对象进行交互,称为中间件,而use函数就是用来添加中间件的,当http请求到来时,就像是水流一样流过每一个中间件,当路径相同时,就会响应该请求,否则就继续往下流,直到结束,那么它是怎么实现的,关键就在源代码里.
Connect中主要有五个函数
- createServer
- use
- handle
- call
- listen
ps:其实还有一个next()函数,但它是包含于handle函数里的,所有这里就不算上它,后面会分析到它.
createServer()
|
|
这个方法返回了一个app,而app是里面定义的一个函数,并且app函数里调用了app.handle()函数,可是handle是哪里来的呢,我们看接下来两行,原来app通过utils.merge方法,把proto和EventEmitter的方法合并到了app函数上,这样app就继承了proto和EventEmitter的方法了,而handle()方法正是从proto继承来的,接下来两句创建了两个属性app.route以及app.stack,app.route是一个路径地址,app.stack用来存放中间件.
app.use(route, fn)
route是中间件所使用的请求路径,默认为’/’,可省略,fn是中间件处理函数,有两种形式,分别为fn(req,res,next)和fn(err,req,res,next),fn(req,res,next)是正常处理函数,fn(err,req,res,next)是异常处理函数.
next()
next是触发后续流程的回调函数,带一个err参数,通过传递给next传递一个err参数,告诉框架当前中间件处理出现异常。如果err为空,则会按顺序执行后面正常处理函数,忽略异常处理函数;相反,如果err非空,则会按顺序执行后续的异常处理函数,而忽略正常处理函数。
http请求到来时,当所有的中间件函数都被调用之后仍旧没有匹配到路由,则会出现错误,connect会认为这个请求没有中间件认领,如果next的err参数非空,则会给页面返回500错误,表示server出现了内部错误;如果err为空,则返回404错误,即访问的资源不存在。
|
|
通过上面的源代码可以看出,app.use()作用是向stack中添加逻辑处理函数 (中间件)的,stack数组中存储了一个个的{route: route , handle : fn}对象,从proto.use的源码可以看出,新定义的handle变量就是use要添加的中间件,path是请求的路径,fn表示处理逻辑函数,分3种情况,它可以是:
- 一个普通的
function(req,res[,next]){}; - 一个httpServer;
- 另一个connect的app对象(sub app特性);
3种情况本质都是处理逻辑,都可以用一个 function(req,res,next){}将它们概括起来,Connect把他们都转化为这个函数,然后把它们存起来。
如何将这三种分别转换为function(req, res, next) {}的形式呢?
- 不用转换;
- httpServer的定义是“对事件’request’后handler的对象”, 我们可以从httpServer.listeners(‘request’)中得到这个函数;
- 另一个connect对象,而connect()返回的app就是
function(req, res, out) {};
在这里,我们也可以看出来,各个中间件之间其实并没有直接的依赖。request和response就成为它们在connect中传递信息的唯一桥梁。前面的中间件处理完毕后,把结果附到request或response之上,后面的中间件便可以从中获取到这些数据。所以,中间件的加载顺序在connect中就显得格外重要,必须将被依赖的中间件放在依赖它的模块之前。比如说,解析cookie的中间件应该放在处理session的中间件之前,因为一般session id是通过cookie来传递的。
app.handle(req, res, out)
app.handle()也来自于proto.handle,最主要的作用就是定义了next函数,app.handle()是直接处理http请求的,请求到达时,触发第一个next并触发链式调用,接下来通过next()函数顺序调用存储在stack中的中间件,layer = stack[index++]保存了当前的请求路径和中间件函数,如果不匹配则会返回next(err)重新调用next函数,此时layer的值是下一个中间件的相关信息,这样就会一直循环下去,直到堆栈已经没有值或者route的值匹配成功为止。如果匹配成功,则会调用call(layer.handle, route, err, req, res, next),这个layer.handle就是中间件函数,call的作用就是调用layer.handle(err,req, res, next)或layer.handle(req, res, next).
|
|
call(handle, route, err, req, res, next)
因为Function.length返回函数定义的参数个数,而Connect中规定function(err, req, res, next) {}形式为错误处理函数,function(req, res, next) {}为正常的业务逻辑处理函数。那么,可以根据Function.length以判断它是否为错误处理函数,当发生错误且传递了4个参数时就会调用handle(err,req, res, next)这个函数,当没有发生错误且传递的参数小于4个时就会调用handle(req, res, next)函数.
|
|
listen()
listen()比较简单,就是创建一个httpServer,将Connect自己的业务逻辑作为requestHandler,监听端口.
|
|
参考: https://github.com/alsotang/node-lessons/tree/master/lesson18#next