介绍
koa是一个相对于express来说,更小,更健壮,更富表现力的Web框架。koa通过组合不同的generator来避免繁琐的回调函数调用。koa的核心库没有绑定任何的中间件,仅仅提供了一个轻量优雅的函数库,使得编写Web应用变得得心应手。
使用
在项目目录路径下运行命令 npm install --save-dev koa 就可以在本地安装koa模块。
安装完成后,写一个hello world来验证是否生效。
hello world
hello world代码十分简单。
const koa = require('koa');
const app = koa();
app.use(function *() {
this.body = 'hello world';
});
app.listen(3000);
上面8行代码就实现了koa的hello world。第4行调用的 app.use() 传入了一个generator方法,就是koa中间件的基本实现。koa应用的实现就是由一个一个的中间件来实现。每一个中间件都是一个generator方法,通过yield语句,将一个一个中间件逻辑级联起来。举个例子:
const koa = require('koa');
const app = koa();
// x-response-time
app.use(function *(next) {
console.log('line 1');
const start = new Date;
yield next;
console.log('line 5');
const ms = new Date - start;
this.set('X-Response-Time', ${ms}ms
);
});
// log time
app.use(function *(next) {
console.log('line 2');
const start = new Date;
yield next;
console.log('line4');
const ms = new Date - start;
console.info('%s %s : %s ms', this.method, this.url, ms);
});
// response
app.use(function *(next) {
console.log('line3');
this.body = 'hello world';
});
app.listen(3000);
运行上面的代码后,当有一个请求被触发时,控制台的log就会打印如下日志(请忽略上面代码中的info日志):
line 1
line 2
line 3
line 4
line 5
可以看出,koa中间件的运行,被yield分割成了两段来运行,运行的时间顺序如下图:
koa框架本身的功能十分简单,koa应用的功能都是通过中间件来实现的,下面我们来介绍常用的几个koa中间件。
koa-static
koa-static是管理静态文件请求的中间件。比如要请求html,JS,图片的静态文件时,就可以使用koa-static来实现。
举个例子,比如项目根目录下得static目录用于存放静态文件,那么如下代码就可以实现该目录的静态文件请求
const path = require('path');
const staticServer = require('koa-static');
app.use(staticServer(path.join(__dirname, 'static')));
koa-router
koa-router是一个路由中间件,用法如下:
const router = require('koa-router')();
// 监听url请求
router.get('/list', function *() {
// ...
});
router.post('/user/register', function *() {
// ...
});
koa-safe-jsonp
JSONP格式返回的中间件,用法:
const jsonp = require('koa-safe-jsonp');
const router = require('koa-router');
const koa = require('koa');
const app = koa();
jsonp(app);
router.get('./list', function *() {
const list = [];
this.json = list;
});
koa-session
session管理的中间件,用法:
const session = require('koa-session');
app.use(session(app));
router.post('./user/login', function *() {
this.session.user = user;
});
koa-onerror
koa-onerror用于格式化异常情况的页面输出。用法:
const onerror = require('koa-onerror');
const koa = require('koa');
const app = koa();
onerror(app);
中间件实现
上面列出的常用中间件都是十分常用的中间件,但是在业务开发过程中,我们会根据业务场景实现一些专门的中间件,那么如何开发一个中间件呢?
现在我们来讲下如何实现koa的中间件。比如我们要实现一个统计请求响应实现的功能,就可以开发一个统计请求响应时间的中间件来使用。
基本从上面的例子中,可以看出,中间件都是通过 app.use() 来注册的,而 app.use() 方法的参数是一个generator方法。所以, koa中间件需要返回一个generator方法,所以实现代码如下:
// middleware/timer.js
module.exports = function() {
return function *(next) {
const path = this.path;
const start = new Date;
yield next;
const end = new Date;
console.log(`${path} response time: ${ end - start }ms`);
}
};
// server.js
const timer = require('./middleware/timer.js');
app.use(timer());
上面这段代码,就实现了统计请求响应时间的功能。功能十分简单, middleware/timer.js 中直接返回了一个generator方法,在里面分别统计请求开始时间和结束时间,并打印时间差。
我们可以将中间件设计的稍微复杂一点,比如我们可以控制过滤掉一部分请求不打印,打印格式也可以进行控制。这个时候我们需要传入一些参数来控制中间件的功能,代码如下:
// middleware/timer.js
module.exports = function timer(options) {
return function *timer() {
let start = new Date;
const path = this.path;
const method = this.request.method;
if ( method !== options.filter.method ) start = 0;
yield next;
const end = new Date;
if(start !== 0 && end - start > options.filter.min) {
console.log(options.format.replace(/:url/g, path).replace(/:time/g, `${ end - start }ms`));
}
}
};
// server.js
const timer = require('./middleware/timer.js');
// GET 请求超过100ms时,打印日志,日志格式: url time
app.use(timer({
format: ':url :time'
filter: {
min: 100
method: 'GET'
}
}));
koa源码理解
最前面介绍的时候有说到,koa的核心库十分简单,没有绑定任何中间件。所以,在最后说一下koa的源码。
koa.js的源码可以去 https://github.com/koajs/koa 获得。
koa.js的源码有4个文件,分别是 lib/application.js , lib/context.js , lib/request.js , lib/response.js 。从名字中可以看出来, context.js , request.js , response.js 分别是上下文环境对象,request对象,response对象,而 application.js 就是koa.js的核心代码。
在上面的例子中,都使用了两个接口, use 和 listen 。下面我们先介绍下这两个方法, 下面是 application.js 中这两个方法的源码:
// ...
app.listen = function(){
debug('listen');
var server = http.createServer(this.callback());
return server.listen.apply(server, arguments);
};
// ...
app.use = function(fn){
if (!this.experimental) {
// es7 async functions are not allowed,
// so we have to make sure that `fn` is a generator function
assert(fn && 'GeneratorFunction' == fn.constructor.name, 'app.use() requires a generator function');
}
debug('use %s', fn._name || fn.name || '-');
this.middleware.push(fn);
return this;
};
// ...
可以看出来, use 的作用就是将传入的中间件generator方法放到 this.middleware 中。 listen 接口的作用其实就是启动了一个server,并将请求处理设置为 this.callback() 的返回方法。
然后我们来看下 this.callback 怎么写的:
// ...
app.callback = function(){
if (this.experimental) {
console.error('Experimental ES7 Async Function support is deprecated. Please look into Koa v2 as the middleware signature has changed.')
}
var fn = this.experimental
? compose_es7(this.middleware)
: co.wrap(compose(this.middleware));
var self = this;
if (!this.listeners('error').length) this.on('error', this.onerror);
return function(req, res){
res.statusCode = 404;
var ctx = self.createContext(req, res);
onFinished(res, ctx.onerror);
fn.call(ctx).then(function () {
respond.call(ctx);
}).catch(ctx.onerror);
}
};
// ...
其中的核心代码是这两句:
// ...
co.wrap(compose(this.middleware));
// ...
return function(req, res){
// ...
fn.call(ctx).then(function () {
respond.call(ctx);
}).catch(ctx.onerror);
}
// ...
其中 compose(this.middleware) 的作用是将传入的中间件数组合并成层层调用的generator函数。 co.wrap() 方法的作用是将generator数转化成一个自执行的函数。最后的 fn.call(ctx) 就开始逐步执行中间件了。