nodejs之中间件框架Connect源码浅谈

前言

上篇文章介绍了express中间件的使用和如何自己实现中间件,也介绍了其实express中间件的实现依赖了Connect中间件框架,3.0版本之前的express框架的中间件完全是由另一个框架Connect实现的。虽然现在已经移除了对Connect的依赖,不过也是在express中实现了Connect。
可以说是中间件让express更加灵活,尤其是其中的next的设计属实巧妙,下面就通过源码来简单的分析下内部实现。

开始

首先安装connect
npm install connect
再看来一段官方的示例代码

var connect = require('connect');
var http = require('http');

var app = connect();

// gzip/deflate outgoing responses
var compression = require('compression');
app.use(compression());

// store session state in browser cookie
var cookieSession = require('cookie-session');
app.use(cookieSession({
    keys: ['secret1', 'secret2']
}));

// parse urlencoded request bodies into req.body
var bodyParser = require('body-parser');
app.use(bodyParser.urlencoded({extended: false}));

// respond to all requests
app.use(function(req, res){
  res.end('Hello from Connect!\n');
});

//create node.js http server and listen on port
http.createServer(app).listen(3000);

我们先来看定义了app变量,然后执行了connect方法,将方法的返回值给了app。先不急着看返回的是什么,先来分析下。

代码最后一行调用了http.createServer(app)。熟悉nodejs的人应该知道createServer接收一个函数,这个函数用来响应request事件,也就相当于是一个function(request,response)。所以我们猜connect()返回的应该是一个函数,并且至少有requestresponse这两个参数。

这时再来看源码,首先看到源码中只导出一个createServer
在这里插入图片描述
再找到这个createServer方法的定义
在这里插入图片描述
到这我们就知道了在调用var app = connect()的时候,实际上是调用源码中的createServer()方法,方法内部创建一个函数对象,在对象上挂载了一些属性,最后将这个函数对象返回。并且这里app函数中是执行了app.handle方法,也就是说在http.createServer()中响应request事件的是app.handle方法。

再总结一下上面的代码,首先把app对象作为参数传给http.createServer(app).listen(3000)之后,返回了服务实例,并且监听某一个端口,这时在所有请求的回调函数(request响应事件)中,都是执行了app.handle(req,res,next)

到这就证明我们上面的猜测是正确的。到这你可能会问那app.handle是哪来的呢?别急,继续往下看。

52行将proto对象上的属性和方法复制到app中。
53行app对象也继承了EventEmitter的原型。
54行route是表示请求路径。
55行stack是用来存放所有中间件的数组。
:其中的merge是引入的utils-merge包的方法,功能就是将源对象的属性合并到目标对象中。)

merge(app,proto);

这句就是把proto上的属性方法合并到app对象上。proto对象又是什么呢?
现在来解答上面的疑问,app.handle是哪来的,这个proto对象又是什么?
源码并不长,通过大致浏览可以得知proto对象上有3个方法,分别是usehandlecall。这三个方法也是Connect框架实现的核心。我们来逐一分析。
首先找到use方法,在使用中间件的时候都是通过app.use()函数
proto.use
在这里插入图片描述
81行——对参数进行了判断,如果实际只传入一个函数,保存这个函数并将路由设置为"/"。

87~98行——处理了fn的几种情况:

  • typeof handle.handle === 'function'就相当于 fn === connect();
    意思就是传进来的fn参数也是一个中间件时,那么将要存储的handle为这个子中间件的fn.handle()方法.
  • 当传进来的参数fn是http.Server类的实例时,就把handle的值设置为request事件的第一个监听器。

101行——如果参数route参数以"/“结尾,则去掉”/"。

107行——可以看到,将构造成特定格式的匿名对象放到stack数组中。stack就是一个装中间件的容器数组。也就是在next中取出中间件的容器。

总结:proto.use方法就是用来添加中间件,也可以理解为将中间件进行登记,将构造好的特定格式的对象放到数组中。

proto.handle
handle方法是通过当前请求路径找出stack数组中相匹配的中间件,并调用call方法。大多是进行字符串的匹配,结合注释可以看懂一些细节操作。
在这里插入图片描述
135行——创建了next函数。
147行——从stack中取出当前项,并将index++。
150行——判断是否还有需要处理的中间件,如果没有则调用defer()。
160~180行——都是字符串的操作,对路由的匹配,如果不满足就执行next()跳到下一个,注意调用next的时候要将err传递进去。
183行——条件都满足则执行call函数,将stack数组的当前项的信息传递进去。
整体思路就是对router的匹配,如果匹配到了就通过call调用handle,如果没有匹配就继续执行下一个。这里其实就是一个递归调用。

总结:handle是整个源码中最长也是最核心的一个函数了。最主要的作用就是定义了next函数,通过遍历取出当前项匿名对象的请求地址、中间件函数。然后通过当前的请求地址去匹配匿名对象的地址,如果匹配失败则返回next(err);继续处理stack中下一个中间件的信息。以此循环下去,直到stack中没有值或者地址匹配成功,则调用call(layer.handle,route,err,req,res,next);。其中的layer.handle就是在use方法中构造的匿名对象中的中间件函数。由此我们也可以猜到call内部应该就是执行了这个中间件函数。

call
上面我们猜测call中应该就是去执行匹配到的中间件函数。
在这里插入图片描述
call的实现还是比较清晰的,基本可以验证我们的猜测是正确的,call的主要任务就是执行handle方法中匹配到的中间件的函数。

当发生错误并且传递了4个参数时就会调用handle(err, req, res, next)函数,当没有发生错误且传递的参数小于4个时就会调用handle(req, res, next),这里的handle函数就是中间件函数。

在中间件逻辑代码中,最后都会调用next函数,继续匹配下一个中间件然后执行。

248行——当有错误并且传递的参数小于4个时,是不是走到两个判断中的,也就是说无法执行中间件函数,而是直接调用next(error);。匹配下一个中间件,并将错误传递进去。如一直无法满足条件,那么就会走到handle中的defer(done, err);统一处理错误。

总结

Connect是一个非常简短,但是设计非常精妙、代码简洁易读的框架。以上对Connect的源码进行了简单的分析,最主要的就是proto上的三个方法,如果通过本文能对这三个方法有一定的了解,那么你就掌握Connect框架实现的核心。

  • 1
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 0
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值