zoukankan      html  css  js  c++  java
  • koa2源码解读

    最近在复习node的基础知识,于是看了看koa2的源码,写此文分享一下包括了Koa2的使用、中间件及上下文对象的大致实现原理。

    koa的github地址:https://github.com/koajs/koa.git

    Koa2的安装和简单使用

    需要 nodev7.6.0 或者更高的版本,为了支持 ES2015 and async

    安装
    npm install koa
    
    Hello koa
    const Koa = require('koa');
    const app = new Koa();
    
    // response
    app.use(ctx => {
      ctx.body = 'Hello Koa';
    });
    
    app.listen(3000);
    
    中文的api文档:https://github.com/guo-yu/koa-guide

    简单分析koa的代码

    打开koa的源码,核心文件共四个在lib目录下,application.js,context.js,request.js,response.js

    application.js

    app的入口文件,就是一个构造函数

     简洁的代码
     module.exports = class Application extends Emitter {
      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);
      }
      //listen端口方法
      listen(...args) {
        debug('listen');
        const server = http.createServer(this.callback());
        return server.listen(...args);
      }
    
    
      toJSON() {
        return only(this, [
          'subdomainOffset',
          'proxy',
          'env'
        ]);
      }
    
      inspect() {
        return this.toJSON();
      }
    
      //中间件使用的use方法
      use(fn) {
        if (typeof fn !== 'function') throw new TypeError('middleware must be a function!');
        if (isGeneratorFunction(fn)) {
          deprecate('Support for generators will be removed in v3. ' +
                    'See the documentation for examples of how to convert old middleware ' +
                    'https://github.com/koajs/koa/blob/master/docs/migration.md');
          fn = convert(fn);
        }
        debug('use %s', fn._name || fn.name || '-');
        this.middleware.push(fn);
        return this;
      }
    
      //上下文等关键代码
      callback() {
        const fn = compose(this.middleware);
    
        if (!this.listeners('error').length) this.on('error', this.onerror);
    
        const handleRequest = (req, res) => {
          res.statusCode = 404;
          const ctx = this.createContext(req, res);
          const onerror = err => ctx.onerror(err);
          const handleResponse = () => respond(ctx);
          onFinished(res, onerror);
          return fn(ctx).then(handleResponse).catch(onerror);
        };
    
        return handleRequest;
      }
    
      //创建上下文
      createContext(req, res) {
        const context = Object.create(this.context);
        const request = context.request = Object.create(this.request);
        const response = context.response = Object.create(this.response);
        context.app = request.app = response.app = this;
        context.req = request.req = response.req = req;
        context.res = request.res = response.res = res;
        request.ctx = response.ctx = context;
        request.response = response;
        response.request = request;
        context.originalUrl = request.originalUrl = req.url;
        context.cookies = new Cookies(req, res, {
          keys: this.keys,
          secure: request.secure
        });
        request.ip = request.ips[0] || req.socket.remoteAddress || '';
        context.accept = request.accept = accepts(req);
        context.state = {};
        return context;
      }
    
      //处理报错
      onerror(err) {
        assert(err instanceof Error, `non-error thrown: ${err}`);
    
        if (404 == err.status || err.expose) return;
        if (this.silent) return;
    
        const msg = err.stack || err.toString();
        console.error();
        console.error(msg.replace(/^/gm, '  '));
        console.error();
      }
    };
    
    

    开始的流程:

    const app = new Koa();
    
    

    然后通过 listen来启动服务:

    const server = http.createServer(this.callback());
    server.listen(...args);
    

    看一下原生的启动方法:

    // http server 例子
    var server = http.createServer(function(serverReq, serverRes){
        var url = serverReq.url;
        serverRes.end( '您访问的地址是:' + url );
    });
    server.listen(3000);
    

    对比发现this.callback()就是用来创建上下文和处理req和res的,接着看this.callback那个方法:

    //处理中间件的使用,后面详细说明
        const fn = compose(this.middleware);
    
        if (!this.listeners('error').length) this.on('error', this.onerror);
       
        const handleRequest = (req, res) => {
          res.statusCode = 404;
          // 创建上下文
          const ctx = this.createContext(req, res);
          const onerror = err => ctx.onerror(err);
          
          const handleResponse = () => respond(ctx);
          onFinished(res, onerror);
          //中间件返回promise对象,成功执行handleResponese,错误用onerror处理,
          return fn(ctx).then(handleResponse).catch(onerror);
        };
        返回callback函数
        return handleRequest;
    

    启动服务:

    server.listen(...args);
    

    到此服务就起来了。在来看看中间件的使用原理:

    use(fn) {
        //判断做兼容处理
        if (typeof fn !== 'function') throw new TypeError('middleware must be a function!');
        if (isGeneratorFunction(fn)) {
          deprecate('Support for generators will be removed in v3. ' +
                    'See the documentation for examples of how to convert old middleware ' +
                    'https://github.com/koajs/koa/blob/master/docs/migration.md');
          fn = convert(fn);
        }
        debug('use %s', fn._name || fn.name || '-');
        //使用把中间件推送到middleware中保存
        this.middleware.push(fn);
        //返回this,为了连续调用
        return this;
      }
    

    保存到this.middleware,在this.callback进程了处理:

    const fn = compose(this.middleware);
    

    看一下compose是怎么处理middleware,代码在const compose = require('koa-compose');

    'use strict'
    
    
    module.exports = compose
    
    function compose (middleware) {
    //判断是参数是否为组数
      if (!Array.isArray(middleware)) throw new TypeError('Middleware stack must be an array!')
      //判断单个中间件是否为函数
      for (const fn of middleware) {
        if (typeof fn !== 'function') throw new TypeError('Middleware must be composed of functions!')
      }
    
      return function (context, next) {
        // last called middleware #
        let index = -1
        return dispatch(0)
        function dispatch (i) {
          if (i <= index) return Promise.reject(new Error('next() called multiple times'))
          index = i
          let fn = middleware[i]
          
          if (i === middleware.length) fn = next
          if (!fn) return Promise.resolve()
          try {
            return Promise.resolve(fn(context, function next () {
            //递归调用,直到全部中间件执行完
              return dispatch(i + 1)
            }))
          } catch (err) {
          //有错误
            return Promise.reject(err)
          }
        }
      }
    }
    
    

    通过上面巧妙的递归调用,执行完所有的中间件函数,返回继续启动流程,创建上下文,处理res,req等。

  • 相关阅读:
    07_Go语言 ( 切片)
    06_Go语言( 数组)
    05_Go语言( 流程控制)
    04_Go语言( 运算符)
    02_Go语言(变量和常量)
    01_Go语言(环境的搭建)
    云电脑直播简单指南
    统信UOS共享打印机配置
    #插头dp#洛谷 5074 Eat the Trees
    #状压dp#洛谷 3959 [NOIP2017 提高组] 宝藏
  • 原文地址:https://www.cnblogs.com/chenjinxinlove/p/7040704.html
Copyright © 2011-2022 走看看