当前位置: 首页 > 后端技术 > Node.js

10分钟看懂Node.jskoa源码架构设计

时间:2023-04-04 00:23:28 Node.js

koa发布快6年了。作为express之后节点服务框架的最大黑马,有很多值得借鉴的设计思想。本文从简单到复杂一步步介绍koa。初学者和老手都适合阅读。简介Koa是一个全新的Web框架,由Express背后的原班人马打造,致力于成为Web应用和API开发领域更小、表现力更强、更健壮的基石。通过利用异步函数,Koa可以帮助您放弃回调并大大增强错误处理。Koa没有捆绑任何中间件,而是提供了一套优雅的方法来帮助您快速愉快地编写服务器端应用程序。既然是web框架,想必大家都不陌生。通过启动节点http服务器并监听端口,我们可以通过类似localhost:3000的方式在本地访问我们的服务。这个服务可以是一个网站,一个restful接口,也可以是静态文件服务等等。HelloWord任何语言和框架都有HelloWord例子来表达它最简单的入门Demo。代码如下。这时,访问浏览器localhost:3000,我们会看到打印出了HelloWord。这时候一个基于koa的服务就启动完成了。理解koa的上下文,第一步就是理解上下文的作用。比如:微信群里有人说外面下雪了,你却跑到窗前,看到了晴空万里。这时你才发现,这也是十月。在寒冷的北方,你在炎热的南方。一个请求会包含用户的登录状态,或者一些信息,比如Token。此信息是上下文的一部分,用于确定请求环境。Koa的Context是节点的request,response对象被封装到一个单独的对象中,并提供了很多用于开发web应用程序和API的有用方法。那些在HTTP服务器开发中使用频率很高的操作,直接在Koa中实现,而不是放在更高层的框架中,这样中间软件就不需要重复实现这些常用功能。我们来看一个中间件官方的例子:简单的解释一下,代码开始初始化一个koa实例,通过use方法加载了下面三个中间件方法。执行顺序:进入第一个中间件next()并跳转到下一个中??间件newData()记录当前时间next()跳到下一个中??间件并将ctx.body赋值回上一个中间件再次记录当前时间并计算将时间差保存在httpheader中,返回给之前的中间件把header中的X-Response-time打印出来。这里的执行顺序扩展了非常经典的洋葱模型。在一个请求的过程中,会来回通过同一个中间件两次,让我们处理不同请求阶段的逻辑源码分析。了解了koa中最重要的两个概念之后,我们来分析一下koa内部是如何工作的,所谓的洋葱模型是如何建立起来的。koa源码的lib目录很简单lib|-application.js|-context.js|-request.js|-response.jsApplication类初始化入口文件为application.js,这里先说Application是一个类,这个类继承了Node的Events,这里不再详细展开,构造函数中初始化了以下内容:proxyproxy默认不开启中间件middleware是一个空数组。这里要注意env是根据环境变量NODE_ENV来判断context、request和response的。分别通过Object.create方法将lib目录下对应的文件导入到这个当前上下文中,不要污染导入对象的use方法按照正常的编码顺序,初始化koa实例后(即constapp=newKoa()),我们需要调用app.use()来挂载我们的中间件,那么我们看看use方法做了什么判断中间件如果是函数,判断中间件是否是生成器函数类型,中间件函数被简单地推入中间件数组。此时的你心里有一个大写的WHAT吗?其实就是这么简单,没有什么复杂的逻辑,后面大家可能已经猜到了,循环调用中间件中的方法来执行。这里没有展示洋葱模型是怎么来的,所以我们不展开它,继续按代码顺序执行。listen方法遵循正常的编码顺序。使用我们的中间件后,就是app.listen(3000)。让我们看看这个listen做了什么。这里的http.createServer是node原生启动http服务的方法。这是对基础知识的一些扩展。知识点,这个方法接受两个参数options[IncomingMessage,ServerResponse]这个只有node版本v9.6.0、v8.12.0之后才支持,这里不再赘述。参数不难理解。这里的this.callback()方法必须返回一个函数,接收两个参数(req,res)。让我们看一下源代码。这个回调的信息量有点大,代码本身也不难理解。注释也从上到下解释了compose方法。这里的compose方法主要负责生成洋葱模型,通过koa-compose包实现。源代码如下。从评论中可以看出大致的逻辑。这里的巧妙之处在于fn(context,dispatch.bind(null,i+1))。这个dispatch.bind(null,i+1)就是接下来我们通常写中间件的第二个参数。当我们执行这个next()方法时,我们实际得到的是下一个中间件的执行。不难理解为什么我们在awaitnext()的时候,是在等待所有的中间件串行执行。回过头来看上面中间件部分的执行顺序,豁然开朗。createContext方法回调中的展开解释,看这里constctx=this.createContext(req,res)做了什么主要是在context上挂载req,res和this.request,this.response,并通过赋值清除层级关系循环引用,为用户提供方便。handleRequest方法仍然是回调中的扩展解释。让我们看看this.handleRequest(ctx,fn)做了什么。分别拿到ctx和compose生成的洋葱模型,开始一一消费中间件。context.js文件阐明了整体框架。让我们看一下context.js的内部细节。文件末尾有两大部分代理。这里可以看到req和res的所有方法集合。那么哪些方法是可读的,哪些是可写的,哪些是既可读又可写的,哪些方法是不允许修改的。这就是委托库所做的。委托在内部使用__defineGetter__和__defineSetter__方法来控制读写。当然我们可以借鉴他们的思路,也不能盲目的跟着这两个API去MDN上搜索。将给出相同的警告信息。此功能已弃用,有利于使用对象初始化程序语法或Object.defineProperty()API定义setter。其实推荐我们使用vue的代理方法Object.defineProperty(),但是这个库已经四年没有更新了,依然稳定运行,很受koa开发者的认可。其他的request.js和response.js文件没什么好说的,只是具体工具和方法的实现,方便开发者调用。有兴趣的可以自行阅读源码。智联前端架构的整体node服务是基于koa实现的,包括我们的vue服务端渲染和noderestfulapi等,我们选择koa的原因是它轻量级,扩展性好,支持async和await异步,并完全摆脱回调地狱。市面上也有成熟的基于koa2的企业级解决方案,比如eggjs、thinkjs。总结揭开koa的神秘面纱,让开发者专注于业务逻辑和框架本身,有利于排错和编写扩展。同时可以学习express、hapi等类似框架的思想,结合已有的企业级解决方案,选择适合自己的框架,总之框架好坏,只看场景。