• 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,代码一点都不复杂,但组合起来,就是一个非常巧妙的设计!

     

  • 相关阅读:
    nginx查看配置文件nginx.conf路径
    http://127.0.0.1/thinkphp5/public/index/teacher/delete/id/1.html 这样的URL下,页面收不到get参数
    thinkphp5访问报错 ...with message 'mkdir(): Permission denied' in... 或...failed to open stream: Permission denied' in...
    thinkphp报错No input file specified. (对于隐藏url中的index.php 是如何做到的?)
    收藏网址
    jquery 和 原生js 获取<select><option></option></select>标签的值和文本
    Macbook下MySQL卸载方法
    提示错误 Call to undefined function imagepng() in …
    Mac 安装phpmyadmin提示无法登陆 mysql服务器解决办法
    php把一个字符串分割成字符数组(可以用参数指定数组每个元素字符的长度)
  • 原文地址:https://www.cnblogs.com/TheViper/p/4216003.html
Copyright © 2020-2023  润新知