koa中间件分析

转载请注明: TheViper http://www.cnblogs.com/TheViper 

另外可以参考http://purplebamboo.github.io/2014/05/24/koa-source-analytics-3/,作者用简单的方式造了一个山寨koa.

koa是什么?

koa是从2013年11月开始发布,更新的。和express相比,koa太年轻了.但它(用文档上的话说)通过组合不同的 generator,可以免除重复繁琐的回调函数嵌套,并极大地提升常用错误处理效率。Koa 不在内核方法中绑定任何中间件,仅仅提供了一个轻量优雅的函数。

严格的说koa并不是完整的web框架,实际使用的时候,开发者根据自己的需要,需要用到其他的中间件。

koa只是提供了一种不同于connect的中间件解决方案,另外再加上一些对request,response的简单封装。这个看它的api就看的出来。比如,对请求/login?name=a&pwd=b,直接用koa的api,只能取url,query(name=a&pwd=b),它不像其他的框架,会进行进一步的封装,将请求的参数封装成键值形式。这时,需要用其他中间件如koa-bodyparser来完成。

还可能用到其他的中间件,具体参见https://github.com/koajs/koa/wiki,比如,

var path = require(‘path‘)
var route= require(‘koa-route‘);//路由
var koa = require(‘koa‘);
var gzip = require(‘koa-gzip‘);//gzip压缩
var staticCache = require(‘koa-static-cache‘);//在响应中添加对静态文件缓存的header
var json = require(‘koa-json‘);//返回json格式的响应
var bodyParser = require(‘koa-bodyparser‘);//解析请求参数
var app = koa();

var user_controller=require(‘./app/controllers/userController‘);

app.use(staticCache(path.join(__dirname, ‘public‘), {
  maxAge:24 * 60 * 60
}))
app.use(bodyParser());
app.use(gzip());
app.use(route.post(‘/login‘, new user_controller().login));
app.use(json({pretty: false}));

app.listen(8000);

下面分析koa中间件的实现。

使用方式

var koa = require(‘koa‘);
var app = koa();
//添加中间件1
app.use(function *(next){
  var start = new Date;
  console.log("start=======1111");
  yield next;
  console.log("end=======1111");
  var ms = new Date - start;
  console.log(‘%s %s - %s‘, this.method, this.url, ms);
});
//添加中间件2
app.use(function *(){
  console.log("start=======2222");
  this.body = ‘Hello World‘;
  console.log("end=======2222");
});

app.listen(3000);
/*
start=======1111
start=======2222
end=======2222
end=======1111
GET / - 10
start=======1111
start=======2222
end=======2222
end=======1111
GET /favicon.ico - 5
*/

app.use()来添加中间件。use函数接受一个generator function。这个generator function就是一个中间件。generator function有一个参数next。这个next是下一个中间件generator function的对应generator对象。yield next;调用下一个中间件的代码。具体的,

application.js

app.callback = function(){
  var mw = [respond].concat(this.middleware);
  var gen = compose(mw);
  var fn = co.wrap(gen);
  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).catch(ctx.onerror);
  }
};
app.listen = function(){
  debug(‘listen‘);
  var server = http.createServer(this.callback());
  return server.listen.apply(server, arguments);
};

listen()时,执行callback(),里面返回function(req,res)回调,然后是对node原生的监听listen().也就是说,上面代码相当于

var http = require("http");

http.createServer(function(request, response) {
    res.statusCode = 404;
    var ctx = self.createContext(req, res);
    onFinished(res, ctx.onerror);
    fn.call(ctx).catch(ctx.onerror);
}).listen(8888);

下面开始分析回调,

app.use = function(fn){
  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;
};

app.use只是把回调的generator function添加到middleware数组。注意,中间件必须是generator function。比如koa-json

module.exports = function(opts){
  。。。。。。。。。
  return function *filter(next){
    yield *next;
    。。。。。。。
  }
};

这也是编写中间件的格式。在启动文件中koa-json()返回generator function。

继续说callback()回调

app.callback = function(){
  var mw = [respond].concat(this.middleware);
  var gen = compose(mw);
  var fn = co.wrap(gen);
  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).catch(ctx.onerror);
  }
};

首先将response添加到middleware中间件数组中,response也被写出了generator function形式。注意数组中中间件的顺序,后面可以看到这样做的目的。然后compose(mw)用到了koa-compose模块。

function compose(middleware){
  return function *(next){
    var i = middleware.length;
    var prev = next || noop();
    var curr;

    while (i--) {
      curr = middleware[i];
      prev = curr.call(this, prev);
    }

    yield *prev;
  }
}

可以看到compose的作用就是向每个中间件的next参数,按照它们在数组中的顺序,传入下一个中间件的generator function,有点像链表。compose(mw)返回generator.

然后co.wrap(gen);,新点的koa用的是co 4.x,co 4.x基于promise,添加了wrap()方法,该方法是将generator function变成promise供co使用。

老点的koa如0.8.1,在这里是var fn = co(gen);

回调里面self.createContext(req, res);,将node原生的request,response传入koa,封装。

onFinished(res, ctx.onerror);,onFinished是当请求关闭,完成或出现错误时,专门封装的的模块,这里不作讨论。

fn.call(ctx).catch(ctx.onerror);,开始执行co(gen)()。

co分析中说到co内部有第一次的gen.next().后面异步操作成功后才会执行next(),但上面源码却找不到像next()的语句。为什么呢?

原因在于yield语句针对的对象。如果是yield generator function,那代码会单步进入,如果单步进入后仍然有yield generator function,继续单步进入。,无需用gen.next(),代码会自动单步进入。如果有yield非函数,比如yield ‘end‘,那相当于在那设置了一个断点,代码会暂时停在那。这时需要gen.next()才会继续执行下面的代码。

co分析中的例子因为里面有yield ‘start’,yield ‘end‘等语句,所以就需要多个gen.next()才能让程序执行完。

下面是一个全是yield generator function的例子,是把前面的改了一下

function* run1() {
console.log("step in child generator1")
console.log("step out child generator1")
}

function* run() {
console.log("step in child generator")
var b=yield *run1();
console.log("step out child generator")
}
function* start() {
  yield *run();
  return ‘over‘;
}
var it = start();
console.log(it.next());

结果

可以看到只用了一个next()就将所有的generator function都执行完了。

明白了这个再重新看compose,它是从数组最后的中间件开始,只要遇到yield,就单步进入,因为所有中间件都是以generator function的形式存在的,所以只要有第一次next(),就会执行到底,也就是执行到没有yield next语句的中间件,然后又原路返回。具体的就像

来源:http://purplebamboo.github.io/2014/05/24/koa-source-analytics-3/

注意compose()里面yield *prev,此时i=0,prev就是*response()。

function *respond(next) {
  yield *next;
  .............
}

可以看到yield *next在最前面,就会单步进入下一个中间件。..。后面的过程,上面的图说的很清楚。

不得不说,从compose到co,代码一点都不复杂,但组合起来,就是一个非常巧妙的设计!

时间: 2024-11-10 00:40:46

koa中间件分析的相关文章

KOA中间件实现原理

1 //基本原理 2 var empty=(function *(){})(); 3 //中间件3 4 var mid2=function *(){ 5 console.log("2:before yield"); 6 yield empty; 7 console.log("2:after yield"); 8 } 9 //中间件2 10 var mid1=function *(){ 11 console.log("1:before yield"

Koa - 中间件

前言 Koa 应用程序是一个包含一组中间件函数的对象,它是按照类似堆栈的方式组织和执行的. 当一个中间件调用 next() 则该函数暂停并将控制传递给定义的下一个中间件.当在下游没有更多的中间件执行后,堆栈将展开并且每个中间件恢复执行其上游行为. 以上两句话,是我在官方文档中找到其对 Koa 中间件的描述. 在Koa中,中间件是一个很有意思的设计,它处于request和response中间,被用来实现某种功能.像上篇文章所使用的 koa-router .koa-bodyparser 等都是中间件

Koa中间件(middleware)级联原理

前言 上次看到了koa-compose的代码,今天来说一下koa中间件的级联以及工作原理. 中间件工作原理 初始化koa实例后,我们会用use方法来加载中间件(middleware),会有一个数组来存储中间件,use调用顺序会决定中间件的执行顺序. 每个中间件都是一个函数(不是函数将报错),接收两个参数,第一个是ctx上下文对象,另一个是next函数(由koa-compose定义) 在建立好http服务器后,会调用koa-compose模块对middleware中间件数组进行处理.具体代码这里就

koa中间件实现分析

最近团队内部做了一个web app,用koa做服务端,一直对他中间件实现很感兴趣,对他的源码研究之后,写了一份简化版本的中间件实现.代码除了用到ES6的Generator和Promise,没有用到其他三方库,总共不到一百行,希望能帮助大家理解! 'use strict'; var middleware = []; //向数据库请求数据 var getDataPromise = new Promise(function(resolve,reject){ setTimeout(function(){

傻瓜式解读koa中间件处理模块koa-compose

最近需要单独使用到koa-compose这个模块,虽然使用koa的时候大致知道中间件的执行流程,但是没仔细研究过源码用起来还是不放心(主要是这个模块代码少,多的话也没兴趣去研究了). koa-compose看起来代码少,但是确实绕.闭包,递归,Promise...看了一遍脑子里绕不清楚.看了网上几篇解读文章,都是针对单行代码做解释,还是绕不清楚.最后只好采取一种傻瓜的方式: koa-compose去掉一些注释,类型校验后,源码如下: function compose (middleware) {

nextjs作为koa中间件的使用

react客户端渲染的缺点:首屏速度慢,对SEO不友好 浏览器请求步骤                                                        客户端跳转 1. 浏览器发起请求 /index                                           1.  点击按钮 2. koa接受请求,并且调用nextjs                                 2. 异步加载组件的js 3. nextjs开始渲染   

koa2入门--03.koa中间件以及中间件执行流程

//中间件:先访问app的中间件的执行顺序类似嵌套函数,由外到内,再由内到外 //应用级中间件 const koa = require('koa'); var router = require('koa-router')(); var app = new koa(); //匹配任意路由之前打印日期 app.use(async (ctx,next)=>{ console.log(new Date()); await next(); }); router.get('/',async (ctx)=>

koa 中间件 koa-art-template 的使用

例子 const Koa = require('koa'); const render =require('koa-art-template'); const path= require('path'); //创建服务器 const app=new Koa(); //渲染 render(app, { root: path.join(__dirname, 'view'), extname: '.html', debug: process.env.NODE_ENV !== 'production'

koa中间件

function pv(ctx){ global.console.log(ctx.path); } module.exports=function(){ return async function(ctx,next){ pv(ctx); await next(); //next是继续执行下面的中间件,如果不写会直接跳出,不会继续执行 } } //app.js const pv = require('./middleware/koa-pv'); app.use(pv()) 原文地址:https:/