核心内容
var http = require('http');
http.createServer(function(request,response){
console.log(request);
response.end('Hello world!');
}).listen(8888);
这就是最简单的实现
如果要是根据不同url去处理呢?
var http = require('http');
http.createServer(function(req, res){
console.log(req);
if(req.url =='/'){
res.end('Hello world!');
}else if(req.url =='/2'){
res.end('Hello world!2');
}else{
res.end('Hello world! other');
}
}).listen(8888);
如果成百上千、甚至更多http服务呢?这样写起来是不是太low了,肯定要封装一下,让它更简单。
Express框架的底层Connect就是这样的一个框架,Koa实际上也提供类似机制,下面我们看一下
我们用http和koa 1.x写一个同样功能的demo
var http = require('http');
var koa = require('koa');
var app = koa();
app.use(function *(next){
if(this.url =='/'){
this.body = 'Hello world!'
}else if(this.url =='/2'){
this.body = 'Hello world!2'
}else{
this.body = 'Hello world! other'
}
})
var server = http.createServer(app.callback());
server.listen(8888);
对比一下,koa的demo里
http.createServer(app.callback())
和http示例里的
http.createServer(function(req, res){
console.log(req);
if(req.url =='/'){
res.end('Hello world!');
}else if(req.url =='/2'){
res.end('Hello world!2');
}else{
res.end('Hello world! other');
}
})
很明显,koa把http.createServer里的内容给封装到app.callback()
里了。
那么app.callback()
里到底有啥呢?
function(req, res){...}
给出服务器启动流程图
看到此处,你是否能够理解什么是中间件了呢?
中间件是Application提供请求处理的扩展机制,主要抽象HTTP协议里的request、response
如果把一个http处理过程比作是污水处理,中间件就像是一层层的过滤网。每个中间件在http处理过程中通过改写request或(和)response的数据、状态,实现了特定的功能。
http协议是无状态协议,所以http请求的过程可以这样理解,请求(request)过来,经过无数中间件拦截,直至响应(response)为止。
Koa目前主要分1.x版本和2.x版本,它们最主要的差异就在于中间件的写法,本小节会一一举例,并最后对比它们的异同,以便后面章节的源码分析。
请求到达服务器后,依次经过各个中间件,直至被响应,所以整个流程
var koa = require('koa');
var app = koa();
// logger
app.use(function *(next){
var start = new Date;
yield next;
var ms = new Date - start;
console.log('%s %s - %s', this.method, this.url, ms);
});
// response
app.use(function *(){
this.body = 'Hello World';
});
app.listen(3000);
Koa 2.x是一个现代的中间件框架,它支持3种不同类型函数的中间件:
yield *
那个先看一下common function
const Koa = require('koa');
const app = new Koa();
// logger
app.use((ctx, next) => {
const start = new Date();
return next().then(() => {
const ms = new Date() - start;
console.log(`${ctx.method} ${ctx.url} - ${ms}ms`);
});
});
// response
app.use(ctx => {
ctx.body = 'Hello Koa';
});
app.listen(3000);
中间件定义
(ctx, next) => {
}
等同于
function (ctx, next){
}
和1.x相比较
它不限于Node.js版本,只要4.x以上都可以(Koa 2.x的中间件最终还是会转成generator),而且是支持Promise,所以整体来说,这种方式是最容易上手的,难度系数是3种中间件里最低的。如果有express经验,可以考虑从这种方式入手,学起来更简单。
Koa设计初衷就是基于generator来构建更好的流程控制的web框架,所以在Koa 2.x里也支持generator,但和1.x稍有不同,先看代码,稍后会解释。
const Koa = require('koa');
const app = new Koa();
// logger
app.use(co.wrap(function *(ctx, next) {
const start = new Date();
yield next();
const ms = new Date() - start;
console.log(`${ctx.method} ${ctx.url} - ${ms}ms`);
}));
// response
app.use(ctx => {
ctx.body = 'Hello Koa';
});
app.listen(3000);
对比1.x
yield next();
,而不是1.x用的yield next;
它和上面common function一样,不限于Node.js版本,只要4.x以上都可以(Koa 2.x的中间件最终还是会转成generator),2.x中间件generatorFunction这种方式是唯一和Koa 1.x最像的方式,可以说是从1.x迁移到2.x的最佳手段。
同样是generator的好处是,可以在generator里进行yieldable操作,对于熟悉1.x的读者来说,是非常好的。
还得爽爽的yieldable么?
Koa 之所以从1.x升级到2.x,可以说async函数居功不小。async函数是es7里stage-3里的特性,可以非常好的解决异步问题,相当于更高级的generator(注:babel或regenerator就是用generator实现的async)。
async函数的好处
下面给出async函数中间件用法
const Koa = require('koa');
const app = new Koa();
// logger
app.use(async (ctx, next) => {
const start = new Date();
await next();
const ms = new Date() - start;
console.log(`${ctx.method} ${ctx.url} - ${ms}ms`);
});
// response
app.use(ctx => {
ctx.body = 'Hello Koa';
});
app.listen(3000);
对比一下
await next();
,和commonfunction里的return next()
类似总结一下,从形式讲,async函数无疑是所有中间件中最耀眼的那个,语言清楚,简洁,结合await关键字,可以非常好的正好Promise资源。它虽好,可是执行的时候却很麻烦,目前Node.js里还没有原生支持async函数,所以只能借助于babel这样编译器工具来完成代码转换。
Koa 2.x迟迟没有发布2.0正式版原因就是Node.js没有原生支持async函数,但我们都非常期待使用这样好的特性。目前Chrome已经实现了async函数,所以Node.js实现async函数也会非常快的,预计是10月份之前,只要性能不是太差,大家都切过去吧。
这样变化的最主要的原因是,在你写koa apps 时使用async箭头函数的时候:
app.use(async (ctx, next) => {
await next()
})
这种情况下,使用this是万万不可能的。
因为 Arrow Function是 Lexical scoping(定义时绑定), this指向定义Arrow Function时外围, 而不是运行时的对象。
1.x
var koa = require('koa');
var app = koa();
2.x
const Koa = require('koa');
const app = new Koa();
源码
1.x
/**
* Application prototype.
*/
var app = Application.prototype;
/**
* Expose `Application`.
*/
module.exports = Application;
/**
* Initialize a new `Application`.
*
* @api public
*/
function Application() {
if (!(this instanceof Application)) return new Application;
this.env = process.env.NODE_ENV || 'development';
this.subdomainOffset = 2;
this.middleware = [];
this.proxy = false;
this.context = Object.create(context);
this.request = Object.create(request);
this.response = Object.create(response);
}
2.x
/**
* Expose `Application` class.
* Inherits from `Emitter.prototype`.
*/
module.exports = class Application extends Emitter {
/**
* Initialize a new `Application`.
*
* @api public
*/
constructor() {
super();
this.proxy = false;
this.middleware = [];
this.subdomainOffset = 2;
this.env = process.env.NODE_ENV || 'development';
this.context = Object.create(context);
this.request = Object.create(request);
this.response = Object.create(response);
}
}
很明显,1.x是函数,而2.x是类,需要new来实例化。
整个Koa2.x里只有application做了类化,其他的还是保持之前的风格,大概还没有到必须修改的时候吧。
看一下解析流程
// m1
app.use((ctx, next) => {
console.log('第1个中间件before 1')
return next().then(() => {
// after
console.log('第1个中间件after 2')
})
});
const Koa = require('koa');
const app = new Koa();
var co = require('co')
// m1
app.use((ctx, next) => {
console.log('第1个中间件before 1')
return next().then(() => {
// after
console.log('第1个中间件after 2')
})
});
// response
app.use(ctx => {
console.log('业务逻辑处理')
return ctx.body = {
data: {},
status: {
code : 0,
msg :'sucess'
}
}
});
app.listen(3000);
$ nodemon koa-core/middleware/app.js
[nodemon] 1.9.1
[nodemon] to restart at any time, enter `rs`
[nodemon] watching: *.*
[nodemon] starting `node koa-core/middleware/app.js`
第1个中间件before 1
业务逻辑处理
第1个中间件after 2
我们可以看看
// m1
app.use((ctx, next) => {
console.log('1')
return next().then(() => {
// after
console.log('2')
})
});
// m2
app.use((ctx, next) => {
console.log('3')
return next().then(() => {
// after
console.log('4')
})
});
这里定义m1和m2中间件,
const Koa = require('koa');
const app = new Koa();
// m1
app.use((ctx, next) => {
console.log('第1个中间件before 1')
return next().then(() => {
// after
console.log('第1个中间件after 2')
})
});
// m2
app.use((ctx, next) => {
console.log('第2个中间件before 3')
return next().then(() => {
// after
console.log('第2个中间件after 4')
})
});
// response
app.use(ctx => {
return ctx.body = {
data: {},
status: {
code : 0,
msg :'sucess'
}
}
});
app.listen(3000);
$ nodemon koa-core/middleware/app.js
第1个中间件before 1
第2个中间件before 3
业务逻辑处理
第2个中间件after 4
第1个中间件after 2
说明
如果中间件里调用中间件呢?