connect.js源码解析
前言
众所周知,connect是TJ大神所造的一个大轮子,是大家用尾式调用
来控制异步流程时最常使用的库,也是后来著名的express框架的本源。但令人惊讶的是,它的源码其实只有200多行,今天也来解析一下它的真容。
解析
以下是connect源码的主要文件结构:
- lib目录
- connect.js
- proto.js
- index.js
是的,就这三个js文件。。
以下是一个connect的经典用法:
js
var app = require('connect'); var http = require('http'); app.use('/',function(req,res){res.send("haha"); }); app.use('/',function(req,res){res.end("hoho"); }) http.createServer(app).listen(3000)
可以看到,所有的奥秘,都在于app
这个变量。让我们先来看看require('connect')
到底返回的是何物。
index.js
js
module.exports = require('./lib/connect'); //好吧。。这只是一个入口,让我们跟随它的脚步进入./lib/connect.js
lib/connect.js
js
var EventEmitter = require('events').EventEmitter; var merge = require('utils-merge'); var proto = require('./proto');module.exports = createServer;//对外暴露createServer函数/** * 这个函数return出来的app对象便是我们在之前例子中的见到的那个app对象, * 可以看到他自身便是一个带req,res参数的函数,所以这也是它可以直接 * 作为参数被传递给http.createServer的原因。而且,由于在javascript * 中,函数也是对象,所以app函数也有自己的属性,他继承了./lib/proto.js * 中暴露出来的方法,也继承了EventEmitter的原型。可以看到,route属性是 * 用来表示请求路径。stack属性,则是一个存放所有中间件的容器数组。 */ function createServer() {function app(req, res, next){ app.handle(req, res, next); }merge(app, proto);merge(app, EventEmitter.prototype);app.route = '/';app.stack = [];return app; }
从上面的代码中我们可以发现,自把app对象
作为参数传递给了http.createServer
方法形成httpServer实例,并监听了某个端口之后,我们的Server其实是在所有请求的callback里,都执行了app.handle(req,res,next)
。这个handle
函数到底是在哪定义的呢?从merge(app, proto)
这里不难看出,它是从./lib/proto.js
这里暴露出来的方法。让我们来看看最后还剩的这个./lib/proto.js
。
lib/proto.js
在proto.js
中,主要暴露出了3个方法,分别为use
,handle
和call
,我们来逐一分解:
js
/*** 这就是我们最后使用app.use()函数,用作添加中间件,route默认为“/”,其最终 * 任务为将请求路由与其处理函数绑定为一个形为* {route: route , handle : fn}的匿名函数,推入自身的stack数组中。*/app.use = function(route, fn){//如果第一个参数不是字符串,则路由默认为"/"if ('string' != typeof route) {fn = route;route = '/';}//如果fn为一个app的实例,则将其自身handle方法的包裹给fnif ('function' == typeof fn.handle) {var server = fn;server.route = route;fn = function(req, res, next){server.handle(req, res, next);};}//如果fn为一个http.Server实例,则fn为其request事件的第一个监听器if (fn instanceof http.Server) {fn = fn.listeners('request')[0];}//如果route参数的以"/"结尾,则删除"/"if ('/' == route[route.length - 1]) {route = route.slice(0, -1);}//输出测试信息debug('use %s %s', route || '/', fn.name || 'anonymous');//将一个包裹route和fn的匿名对象推入stack数组this.stack.push({ route: route, handle: fn });//返回自身,以便继续链式调用return this; };
可以看到这个use
方法的任务便是中间件的登记
,这样一来,自身的stack
数组中变充满了一个个登记了的{route: route , handle : fn}
匿名函数。为请求到达时,匹配URL,并执行对应的函数,做好了在一个地点,统一格式化,统一存放
。
接下来我们就看看真正挂在Server里的handle
处理函数:
js
/*** 这个函数的是为当前请求路径寻找出在stack里所有与之相匹配的中间件,* 并依次调用call* 方法执行(主要做的是大量的缜密的字符串匹配工作,详看内部注释)*/app.handle = function(req, res, out) {var stack = this.stack//req中“?”字符的位置索引,用来判断是否有query string, searchIndex = req.url.indexOf('?')//获取url的长度(除去query string), pathlength = searchIndex !== -1 ? searchIndex : req.url.length//若url以“/”开头,则为false,否则为"://"字符串的位置索引, fqdn = req.url[0] !== '/' && 1 + req.url.substr(0, pathlength).indexOf('://')//若url不以“/”开头,则protohost为 协议:/(如https:/), protohost = fqdn ? req.url.substr(0, req.url.indexOf('/', 2 + fqdn)) : '', removed = ''// 标记:url是否以"/"结尾, slashAdded = false, index = 0;//若含有next(第三个)参数,则继续调用,若无,则使用finalhandler库,作为请求最后的处理函数,若有err则抛出,否则则报404var done = out || finalhandler(req, res, {env: env,onerror: logerror});req.originalUrl = req.originalUrl || req.url;function next(err) {//若salshAdded标记为真,则去除最前面的“/”if (slashAdded) {req.url = req.url.substr(1);slashAdded = false;}if (removed.length !== 0) {req.url = protohost + removed + req.url.substr(protohost.length);removed = '';}//取本index的中间件,之后把index+1var layer = stack[index++];//如果已没有更多中间件,则结束if (!layer) {defer(done, err);return;}//路由路径var path = parseUrl(req).pathname || '/';//此中间件的route,用作与path匹配比较var route = layer.route;//查看当前请求路由是否匹配route,只匹配route长度的字符串,如"/foo/bar"与"/foo"是匹配的if (path.toLowerCase().substr(0, route.length) !== route.toLowerCase()) {return next(err);}//如果匹配到的路径不以'/'与‘.’结尾,或已结束,则报错(即上一个if保证了头匹配,这里保证了尾部匹配)var c = path[route.length];if (c !== undefined && '/' !== c && '.' !== c) {return next(err);}//去除与route不匹配的其他部分if (route.length !== 0 && route !== '/') {removed = route;req.url = protohost + req.url.substr(protohost.length + removed.length);//保证路径以"/"开头if (!fqdn && req.url[0] !== '/') {req.url = '/' + req.url;slashAdded = true;}}//调用call函数执行layercall(layer.handle, route, err, req, res, next);}next(); };
所以这个handle
方法的角色只是一个对请求路径
和中间件注册路径
的一个匹配者,找出所有相匹配的中间件,并负责把它们一个个有序(因为中间件也是有序的push进的stack,handle又是靠索引来取的stack里的匿名对象)
传入call
方法执行。
好,我们来看最后的call
方法:
js
/*** 主要任务便是执行handler中匹配到的中间件*/function call(handle, route, err, req, res, next) {//handle函数的参数个数(3个参数为一般中间件,4个参数为错误处理中间件)var arity = handle.length;//是否有错var hasError = Boolean(err);//输出测试信息 debug('%s %s : %s', handle.name || '<anonymous>', route, req.originalUrl);try {//执行错误处理中间件if (hasError && arity === 4) {handle(err, req, res, next);return;} else if (!hasError && arity < 4) {//执行一般中间件handle(req, res, next);return;}} catch (e) {// reset the errorerr = e;}next(err); }
所以,可喜可贺,看到这里,我们大概已经摸清了connect
的庐山真面目了,其整体的结构大致可概括为:
- 暴露出的app函数(函数体为自己的
handle
方法)- 从
proto
处继承的属性(方法) - 继承的
EventEmitter
的原型 route
属性,表示中间件的默认请求路径stack
数组,所有的中间件的存放处,中间件会被格式化成形为{route: route , handle : fn}
的匿名对象存放
- 从
而整体的运行过程大致可概括为:
use
注册中间件- Server接受请求
- 调用
handle
检查stack
数组中注册的中间件与此请求的url是否匹配 - 若匹配到了一个中间件,则调用
call
执行 - 继续寻找是否还有匹配的中间件并执行...
- 登记的中间件全部查询完毕,匹配的中间件全部执行完毕,结束。
connect.js源码解析相关推荐
- js怎么调用wasm_Long.js源码解析
基于现在市面上到处都是 Vue/React 之类的源码分析文章实在是太多了.(虽然我也写过 Vite的源码解析 所以这次来写点不一样的.由于微信这边用的是 protobuf 来进行 rpc 调用.所以 ...
- 【Vue.js源码解析 一】-- 响应式原理
前言 笔记来源:拉勾教育 大前端高薪训练营 阅读建议:建议通过左侧导航栏进行阅读 课程目标 Vue.js 的静态成员和实例成员初始化过程 首次渲染的过程 数据响应式原理 – 最核心的特性之一 准备工作 ...
- js define函数_不夸张,这真的是前端圈宝藏书!360前端工程师Vue.js源码解析
优秀源代码背后的思想是永恒的.普适的. 这些年来,前端行业一直在飞速发展.行业的进步,导致对从业人员的要求不断攀升.放眼未来,虽然仅仅会用某些框架还可以找到工作,但仅仅满足于会用,一定无法走得更远.随 ...
- JavaScript数字运算必备库——big.js源码解析
概述 在我们常见的JavaScript数字运算中,小数和大数都是会让我们比较头疼的两个数据类型. 在大数运算中,由于number类型的数字长度限制,我们经常会遇到超出范围的情况.比如在我们传递Long ...
- video.js 源码解析
为什么80%的码农都做不了架构师?>>> 写在前面 现在视频业务越来越流行了,播放器也比较多,作为前端工程师如何打造一个属于自己的播放器呢?最快最有效的方式是基于开源播放器深度 ...
- 如何将文件地址转为url_Node.js 源码解析 util.promisify 如何将 Callback 转为 Promise
Nodejs util 模块提供了很多工具函数.为了解决回调地狱问题,Nodejs v8.0.0 提供了 promisify 方法可以将 Callback 转为 Promise 对象. 工作中对于一些 ...
- 【Vue.js源码解析 三】-- 模板编译和组件化
前言 笔记来源:拉勾教育 大前端高薪训练营 阅读建议:建议通过左侧导航栏进行阅读 模板编译 模板编译的主要目的是将模板 (template) 转换为渲染函数 (render) <div> ...
- addEvent.js源码解析
露露 前言: 看两三遍即可. 在看 jQuery 源码时,发现了这段注释: //源码5235行 /* * Helper functions for managing events -- not par ...
- 史上最全的vue.js源码解析(四)
虽然vue3已经出来很久了,但我觉得vue.js的源码还是非常值得去学习一下.vue.js里面封装的很多工具类在我们平时工作项目中也会经常用到.所以我近期会对vue.js的源码进行解读,分享值得去学习 ...
最新文章
- 一致性hash算法虚拟节点_Hash算法和一致性Hash算法
- python对csv文件中的数据进行分类_利用Python对csv文件中的数据进行排序
- WebSen!NT的行业分类说明
- PHP盈亏问题,小升初数学必考经典应用题—盈亏问题!(附经典例题分析)
- 借助opencv将unsigned char数组显示为图像
- Leecode刷题热题HOT100(22)——括号生成
- 用 Go 重构 C 语言系统,这个抗住春晚红包的百度转发引擎承接了万亿流量
- ssh远程登录报错:WARNING: REMOTE HOST IDENTIFICATION HAS CHANGED
- 浏览器文件分段断点上传简单示例(python 篇)
- 学MySQL,这篇万字总结,真的够用了
- KEIL中无IAP或者STC芯片型号怎么办
- 迅雷极速版|xunlei下载
- 数字藏品文博周将至,拙政园主题数字藏品全网首发
- 解决 :No active profile set, falling back to default profiles: default 问题
- 光影魔术手出现load XAR失败,解决办法。
- 浅谈PPO算法-玩转月球登陆
- python怎么新建工程_Python vue坏境搭建及项目创建
- python中self的个人理解
- 3.docker创建容器 (docker容器命令)
- 多媒体艺术家jaime levy在网站甚至还没有存在之前就处于网页设计的最前沿