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将node的请求和响应对象封装到一个单一的对象中,并为开发web应用程序和API提供了许多有用的方法。那些在HTTP服务器开发中使用频率非常高的操作,直接在Koa中实现,而不是放在一个更高层次的框架中,这样中间件就不需要重复实现这些常用的功能。先看一个官方的中间件例子:简单解释一下,代码开始初始化一个koa实例,通过use方法加载了下面三个中间件方法,执行顺序为:进入第一个中间件next()跳转到下一个中??间件newData()记录当前时间next()跳转到下一个中??间件赋值ctx.body回到上一个中间件再次记录当前时间并计算时间差保存在http头中并返回之前的middlewareheader中的X-Response-time被打印出来了。这里的执行顺序延续了非常经典的洋葱模型:在一个请求的过程中,会两次经过同一个中间件,让我们可以处理不同请求阶段的逻辑。上面的源码分析介绍了koa中最重要的两个概念。接下来我们分析一下koa内部是如何工作的,以及所谓的洋葱模型是如何建立起来的。koa源码的lib目录很简单。lib|-application.js|-context.js|-request.js|-response.jsApplication类初始化入口文件为application.js,我们先从这里开始。Application是一个类,这个类继承了Node的Events,这里不再详细展开。构造函数中初始化如下内容:proxy代理默认不开启中间件。request和response分别通过Object.create方法将lib目录下对应的文件导入到this的当前上下文中,并且不污染导入的对象。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之后才支持,这里不再赘述。requestListener这个参数是一个函数类型,每次请求都会传入两个参数req和res。不难理解,这里的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)的作用。这里req、res和this.request、this.response都挂载在context上,通过赋值明确循环引用的层次关系,为用户提供方便。handleRequest方法还是callback中的扩展解释,看this.handleRequest(ctx,fn)是做什么的。分别拿到ctx和compose生成的洋葱模型,开始一一消费中间件。context.js文件阐明了上面的整体框架。让我们看看context.js的内部细节。文件末尾有两大部分代理。这里可以看到所有的req和res方法集合,那么哪些方法是可读的,哪些是可写的,哪些是既可读又可写的,哪些方法是不允许修改的。这就是委托库所做的。代表在内部使用。__defineGetter__和__defineSetter__方法控制读写。当然,我们可以向他们学习,不能盲从。在MDN上搜索这两个API将给出相同的警告消息此功能已弃用,有利于使用对象初始化程序语法或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等类似框架的思想,结合已有的企业级解决方案,选择适合自己的框架,总之框架好坏,只看场景。
