1 koa2简介
koa2是基于 Node.js 平台的下一代 web 开发框架。
koa2框架是基于Node.js平台的web开发框架,因提供了一套优化的方法,能够让开发者快速而愉快地编写服务端应用程序API而流行。
koa分1.0版本和2.0版本。koa和koa2中间件的思路是一样的,但是实现方式有所区别,koa是基于generator/yield的实现。和koa 1相比,koa2.0版本基于async/await实现,完全使用Promise并配合async来实现异步。
不同node版本对ES6支持的力度不同,所以需要node版本为v7.6.0及以上 来 支持 async/await方法。
2 koa基本使用
2.1 创建服务器
- 安装node,可以使用 nvm工具 来 安装 node
- 新建一个项目文件夹demo-koa
- 在根目录下创建package.json文件并配置,可以在终端通过npm init命令,该命令会提示配置包的相关信息,名称版本等等,都是包的基本配置信息。
- 终端进入项目根目录,执行npm install koa 或者 在package.json文件中添加koa依赖后执行 npm install
"dependencies": { "koa": "^2.11.0" }
- 根目录下新建demo1.js文件,文件内容为
// demo1.js
const Koa = require('koa')
const app = new Koa()
const main = (ctx) => {
ctx.body = 'Hello Koa' // ctx.response.body = 'Hello Koa'
}
app.use(main)
app.listen(3000)
运行脚本
node demo1.js
打开浏览器,访问 http://127.0.0.1:3000
这里使用原生http模块实现相同功能的逻辑var http = require("http")
var server = http.createServer(function(req, res){
res.writeHead(
200,
{"Content-type":"text/html;charset=UTF-8"})
res.end("Hello Koa")
})
//运行服务器,监听3000端口
server.listen(3000, "127.0.0.1")
对比可以看到,koa的方法确实要简洁一些。不过这还只是koa的特点之一。
2.2 上下文(Context)对象
Koa 的 Context对象对原生node的request对象和response对象做了封装。
原生request对象表示http请求,包含了请求查询字符串、参数、http头部等属性。
原生response对象表示http请求,凡是需要向用户响应的操作,都需要通过该对象进行。
app.listen启动服务后,每一个请求都将创建一个 Context对象,用ctx标识符作为接收器引用。
app.use(async ctx => {
ctx; // 这是 Context
ctx.request; // 这是 koa Request,对node 的request做了进一步封装
ctx.response; // 这是 koa Response, 对node 的response做了进一步封装
})
通过赋值这个ctx对象的属性type和body属性,可以控制返回给用户的内容。
const main = ctx => {
// console.log(ctx.header)
if (ctx.accepts('html')) {
ctx.type = 'html';
ctx.body = '<p>Hello World</p>';
} else if (ctx.accepts('xml')) {
ctx.type = 'xml';
ctx.body = '<data>Hello World</data>';
} else if (ctx.accepts('json')) {
ctx.type = 'json';
ctx.body = { data: 'Hello World' };
} else {
ctx.type = 'text';
ctx.body = 'Hello World';
}
};
Context对象(ctx)的许多访问器和方法其实是直接委托给它的 ctx.request或 ctx.response的 ,例如 ctx.type 和 ctx.body 委托给 response 对象,ctx.path 和 ctx.method 委托给 request。也就是说ctx.body 和 ctx.response.body是相同的。
3 中间件
3.1 中间件概念
想要在收到HTTP请求后到返回响应结果之前的中间过程中(HTTP Request 和 HTTP Response 之间)做一些事情,如打印日志等,我们可以定义函数来实现功能。该函数就叫做中间件(middleware)。
简言之:koa支持自定义中间件函数在请求和响应做一些事情。
下面看下一个请求经过中间件最后生成响应的过程: 中间件:依次打印日志,记录处理时间,输出HTML
const Koa = require('koa')
const app = new Koa()
// 打印日志中间件
const logger = (ctx, next) => {
console.log(`${ctx.request.method} ${ctx.request.url}`) // 打印URL
next()
}
app.use(logger)
// 记录时间中间件
app.use((ctx, next) => {
const start = new Date().getTime() // 当前时间
next() // 调用下一个middleware
const ms = new Date().getTime() - start // 耗费时间
console.log(`Time: ${ms}ms`) // 打印耗费时间
});
// 返回结果中间件
const main = (ctx) => {
ctx.body = 'Hello Koa' // ctx.response.body = 'Hello Koa'
}
app.use(main)
app.listen(3000)
app.use(middleWare)用来加载中间件。
访问 http://127.0.0.1:3000/index1.html, 命令行窗口会显示
GET /index1.html
Time: 1ms
Koa本身没有捆绑任何中间件(使得其本身轻巧),但是可以通过其提供的use方法来加载各种中间件(上面示例中的main方法也是中间件),这样一来可以在HTTP Request 和 HTTP Response 之间做很多的事情。这是koa简洁却功能强大的原因。
3.2 中间件栈
每个中间件默认接受两个参数
- 第一个参数是 Context对象
- 第二个参数是next函数,对于一个中间件函数来讲,next函数很关键
- 无next调用
- 不执行后续的一系列中间件
- 有next调用,表示接着往下执行
- 调用next函数之前,执行当前中间件逻辑
- 调用next函数之后,该函数暂停,把执行权转交给下一个中间件,下一个中间件开始执行
- 无next调用
多个中间件通过app.use(middleWare)加载后,形成一个“中间件栈”,执行顺序以中间件里的next函数调用为界限:
const koa = require('koa');
const app = new koa();
const one = (ctx, next) => {
console.log('one next之前')
next()
console.log('one next之后')
}
const two = (ctx, next) => {
console.log('two next之前')
next()
console.log('two next之后')
}
const three = (ctx, next) => {
console.log('three next之前')
next()
}
const four = (ctx, next) => {
next()
console.log('four next之后')
}
const five = (ctx, next) => {
console.log('five next之前')
next()
console.log('five next之后')
}
app.use(one)
app.use(two)
app.use(three)
app.use(four)
app.use(five)
app.use(ctx => {
console.log('返回结果');
ctx.body = 'hello'
})
app.listen(3000)
打印顺序:
one next之前
two next之前
three next之前
five next之前
返回结果
five next之后
four next之后
two next之后
one next之后
- 最外层的中间件首先执行。
- 调用next函数,把执行权交给下一个中间件。
- ...
- 最内层的中间件最后执行。
- 执行结束后,把执行权交回上一层的中间件。
- ...
- 最外层的中间件收回执行权之后,执行next函数后面的代码。
中间件流程控制简单描述就是:中间件的执行是以next为界限,先执行本层中next以前的部分,遇到next后执行下一层,一层层下去。当最后一层中间件执行完毕后,再返回上一层执行next以后的部分,一层层回来。所以如果某一层没有next,说明到该层方法就执行完毕了,就开始返回上一层执行上一层的next之后的部分了。
如果中间件内部没有调用next函数,那么执行权就不会传递下去,而是向上返回了。
试试如果把five函数离得next去掉,则浏览器不会正常显示返回内容(Not Found)了。
这也就是koa所谓的洋葱模型,从外面一层层的深入,再一层层的穿出来。
3.3 异步中间件
异步中间件就是函数里包含了异步操作,中间件必须被写成 async 函数。 示例:fs.readFile异步读取文件
<html>
<body>
<h1>index1.html的内容</h1>
<p>Hello Wrold</p>
</body>
</html>
// demo5.js
const fs = require('fs.promised');
const Koa = require('koa');
const app = new Koa();
const main = async (ctx) => {
const data = await fs.readFile('index1.html', 'utf8')
ctx.response.type = 'html'
ctx.response.body = data
}
app.use(main)
app.listen(3000)
// 原fs模块读取文件后都要用回调函数来处理结果
// 而fs.promised可以用promised来代替回调函数
// 通过 npm install fs.promised 安装 fs.promised
// fs.readFile('index1.html', 'utf8', function (err, data) {
// if (!err) {
// console.log( data )
// } else {
// console.log("读取文件出错")
// }
// })
执行node demo5.js后,浏览器访问http://127.0.0.1:3000/后展示index1.html网页
4 错误处理
4.1 ctx.throw
Koa 提供了ctx.throw()方法,用来抛出错误,这个时候网页展示 Internal Server Error
4.2 ctx.status
如果ctx.body为空,设置ctx.status
- ctx.status=404, 网页展示 Not Found
- ctx.status=500, 网页展示 Internal Server Error
4.3 try...catch捕获
可以为每个中间件写一个try...catch,也可以让最外层的中间件负责所有中间件的错误处理。
const Koa = require('koa')
const app = new Koa()
const handler = async (ctx, next) => {
try {
await next();
} catch (err) {
ctx.response.status = err.statusCode || err.status || 500;
ctx.response.body = {
message: err.message
};
}
}
const one = (ctx, next) => {
console.log('one next之前')
ctx.throw("one 出错了")
next()
}
const two = (ctx, next) => {
console.log('two next之前')
next()
console.log('two next之后')
}
const main = (ctx) => {
console.log("main")
ctx.body = 'Hello Koa' // ctx.response.body = 'Hello Koa'
}
app.use(handler);
app.use(one);
app.use(two);
app.use(main)
app.listen(3000)
4.4 监听error事件
Koa继承了原生event模块的EventEmitter 类,所有运行过程中一旦出错,Koa 会触发一个error事件。监听这个事件,也可以处理错误。
const Koa = require('koa')
const app = new Koa()
app.on('error', (err, next) => {
console.error('server 出错了', err)
})
const main = ctx => {
ctx.throw(500)
}
app.use(main)
app.listen(3000)
需要注意的是,如果错误被try...catch捕获,就不会触发error事件。这时,必须调用ctx.app.emit(),手动释放error事件,才能让监听函数生效。
app.on('error', (err, next) => {
console.error('server 出错了', err)
})
const main = ctx => {
// ctx.throw(500)
try {
ctx.throw(500)
} catch {
ctx.status = 500
ctx.app.emit('error', err, ctx)
}
}
5 Cookie
ctx.cookies 用来读写 Cookie。 ctx.cookies.set(key,value) 设置cookie ctx.cookies.get(key) 获取cookie
const Koa = require('koa')
const compose = require('koa-compose')
const app = new Koa()
const main = ctx => {
const n = Number(ctx.cookies.get('view') || 0) + 1;
ctx.cookies.set('view',n)
ctx.body = n + ' views';
}
app.use(main)
app.listen(3000)
6 其他相关功能模块
- koa-route 模块:路由判断
- koa-static模块:方便静态资源
- koa-body模块: 支持从 POST 请求的数据体里面提取键值对、文件上传(没理解用法。。)
- koa-compose模块:将多个中间件合成为一个大的中间件,koa2的源码实现了就用的这个该模块的compose方法来将所有中间件合成为一个大的中间件后再处理的。
总结
koa2作为一个node.js的web开发框架,只提供了应用(Application)、上下文(Context),通过中间件机制来支持在请求和响应之间优雅的实现很多功能。