koa是什么?
koa是基于nodejs的一个http中间件框架。koa源码只有一千多行,所有的功能都可以通过插件实现,简单易懂,自由度高。
准备
- node需要v7.6.0或者更高的版本(支持es6和async的语法)。
- npm install koa
开始
先看看来自官方github的最小demo
const Koa = require('koa');
const app = new Koa();
// response
app.use((ctx,next) => {
ctx.body = 'Hello Koa';
});
app.listen(3000);
复制代码
不难发现,相对于原生nodejs来说,只多了use、listen两个方法和ctx、next两个参数。
中间件
koa的一个最重要的设计就是中间件。
1. 中间件是什么?
中间件(middleware)本质上就是一个函数,处于http request和http response中间,实现某种中间的功能。使用app.use()来加载中间件。koa中每个中间件默认两个参数,第一个是ctx对象,第二个是next函数。我们可以使用next函数来把执行权转交给下一个中间件。
2. 中间件栈
多个中间件会形成一个栈结构,以"先进后出"的原则执行。
const one = (ctx, next) => {
console.log('1');
next();
console.log('2');
}
const two = (ctx, next) => {
console.log('3');
next();
console.log('4');
}
const three = (ctx, next) => {
console.log('5');
next();
console.log('6');
}
app.use(one);
app.use(two);
app.use(three);
// 1 3 5 6 4 2
复制代码
3. 移除next?
中间件通过next来将函数执行权移交到下一个中间件,若无next,执行权就不会移交下一个中间件,后续中间件无效。
app.use((crx, next) => {
console.log(1)
next()
console.log(2)
})
app.use((crx, next) => {
console.log(3)
// next()
console.log(4)
})
app.use((crx, next) => {
console.log(5)
next()
console.log(6)
})
app.listen(3000);
// 1 3 4 2
复制代码
use
- 每一次use传入一个回调函数f,listen时执行这个函数f,demo的回调函数f是
(ctx) => {
ctx.body = 'Hello Koa';
}
复制代码
- 回调函数f一共有两个参数,分别是context(以下简写ctx)和next。第一个参数ctx是原生req、res经过一系列处理后产生的对象。 函数f的第二个参数next()跳到下一个回调函数,多个use的回调函数按照顺序执行。(例子见中间件-中间件栈)
listen
listen的实现原理其实就是对http.createServer进行了一个封装,重点是这个函数中传入的callback。callback就是所有中间件的组合。
原生node监听3000端口
http.createServer(function(req, res){
res.writeHead(200, {'Content-Type': 'text/html'});
res.write('Hello Http Server');
res.end();
}).listen(3000);
复制代码
koa的封装实现
listen(...args) {
const server = http.createServer(this.callback());
return server.listen(...args);
}
复制代码
ctx
koa中, ctx表示一次对话的上下文(包括 HTTP 请求和 HTTP 回复)。为了开发方便而设计的一个js对象,绑定了请求和响应相关的数据和方法(如ctx.path、ctx.body等)。本质上使用request、response两个文件拓展属性,ctx通过delegate实现代理拿到request和response的方法和属性。
delegate(proto, 'response')
.method('attachment')
.method('redirect')
.method('remove')
.method('vary')
.method('set')
.method('append')
.method('flushHeaders')
.access('status')
.access('message')
.access('body')
.access('length')
.access('type')
.access('lastModified')
.access('etag')
.getter('headerSent')
.getter('writable');
delegate(proto, 'request')
.method('acceptsLanguages')
.method('acceptsEncodings')
.method('acceptsCharsets')
.method('accepts')
.method('get')
.method('is')
.access('querystring')
.access('idempotent')
.access('socket')
.access('search')
.access('method')
.access('query')
.access('path')
.access('url')
.access('accept')
.getter('origin')
.getter('href')
.getter('subdomains')
.getter('protocol')
.getter('host')
.getter('hostname')
.getter('URL')
.getter('header')
.getter('headers')
.getter('secure')
.getter('stale')
.getter('fresh')
.getter('ips')
.getter('ip');
复制代码
比如我们要访问ctx.repsponse.status,在开发中可以直接访问ctx.status。
next
在中间件中,使用next函数来把执行权转交给下一个中间件。
路由
网站一般有多个页面/接口,通过ctx.request.path获取用户请求的路径来实现简单的路由。
const main = ctx => {
if (ctx.request.path !== '/') {
ctx.response.type = 'html';
ctx.response.body = '<a href="/">Index Page</a>';
} else {
ctx.response.body = 'Hello World';
}
}
复制代码
随着项目不断迭代,页面/接口数量会上升。上述代码不易维护,此时可以使用koa-router
错误捕获
在发生错误的时候,能够捕获到错误和抛出的异常,并反馈出来。koa中有以下两种比较通用的捕获错误的方法。
- 运行出错时,koa会触发一个error事件,我们可以监听这个事件来处理错误。
app.on('error', (err,ctx) => {
console.error('server error', err)
});
复制代码
- 自己写一个最外层的中间件,负责所有中间件的错误处理。
const errorHandler = async (ctx,next) => {
try {
await next();
} catch (err) {
ctx.status = err.statusCode || err.status || 500;
ctx.body = {
message: err.message
}
}
}
复制代码
当错误被try...catch捕获时,就不会触发koa的error事件。当我们catch捕获到错误时,可以手动触发error事件。
const errorHandler = async (ctx,next) => {
try {
await next();
} catch (err) {
ctx.status = err.statusCode || err.status || 500;
ctx.body = {
message: err.message
}
ctx.app.emit('error', err, ctx) // 手动触发error事件
}
}
复制代码