咱们从以下几个方面来学习Koa

  • 创立应用程序函数
  • 扩大res和req
  • 中间件实现原理

创立应用程序函数

Koa 是依赖 node 原生的 http 模块来实现 http server 的能力,原生 http 模块能够通过几行代码就启动一个监听在 8080 端口的http服务,createServer 的第一个参数是一个回调函数,这个回调函数有两个参数,一个是申请对象,一个是响应对象,能够依据申请对象的内容来决定响应数据的内容;

const http = require("http");const server = http.createServer((req, res) => {  // 每一次申请解决的办法  console.log(req.url);  res.writeHead(200, { "Content-Type": "text/plain" });  res.end("Hello NodeJS");});server.listen(8080);

在 Koa 中,createServer 回调函数中的 req 和 res 会被保留到 ctx 对象上,随同整个解决申请的生命周期,Koa 源码中的 request.js 和 response.js 就是对这两个对象增加了大量便捷获取数据和设置数据的办法,如获取申请的办法、申请的门路、设置返回数据体、设置返回状态码等操作。

而Koa在封装创立应用程序的办法中次要执行了以下流程:

  • 组织中间件(监听申请之前)
  • 生成context上下文对象
  • 执行中间件
  • 执行默认响应办法或者异样解决办法
// application.js// 这个办法是封装了http模块提供的http.createServer和listen办法listen(...args) {  debug('listen');  const server = http.createServer(this.callback());  return server.listen(...args);}callback() {  //组织中间件,在监听申请之前实现的  const fn = compose(this.middleware);  if (!this.listenerCount('error')) this.on('error', this.onerror);  const handleRequest = (req, res) => {    //创立context上下文对象    const ctx = this.createContext(req, res);    return this.handleRequest(ctx, fn);  };  return handleRequest;}handleRequest(ctx, fnMiddleware) {  const res = ctx.res;  // 默认状态码为404  res.statusCode = 404;  const onerror = err => ctx.onerror(err);  const handleResponse = () => respond(ctx);  onFinished(res, onerror);  // 执行中间件  return fnMiddleware(ctx).then(handleResponse).catch(onerror);}// 创立context上下文对象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.state = {};  return context;}

扩大res和req

NodeJS中原生的res和req是http.IncomingMessage和http.ServerResponse的实例,Koa中则是自定义request和response对象,放弃对原生的res和req援用,而后通过getter和setter办法实现扩大。

// application.jscreateContext(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; // 保留原生 req 对象    context.res = request.res = response.res = res; // 保留原生 res 对象    request.ctx = response.ctx = context;    request.response = response; // response 拓展    response.request = request; // request 拓展    context.originalUrl = request.originalUrl = req.url;    context.state = {};    // 最终返回残缺的context上下文对象    return context;}

在Koa中要区别这两组对象:

  • request、response: Koa扩大的对象
  • res、req: NodeJS原生对象
// request.jsget header() {  return this.req.headers;},set header(val) {  this.req.headers = val;}

此时曾经能够采纳这样的形式拜访header属性:

ctx.request.header

delegates 属性代理

koa对response和request应用了属性代理,使咱们能够间接在context中应用request和response的办法,其中method办法是委托办法,getter办法用来委托getter,access办法委托getter+setter

// context.js/** * Response delegation. */delegate(proto, 'response')  .method('attachment')  .method('redirect')  .method('remove')  .method('vary')  .method('has')  .method('set')  .method('append')  .method('flushHeaders')  .access('status')  .access('message')  .access('body')  .access('length')  .access('type')  .access('lastModified')  .access('etag')  .getter('headerSent')  .getter('writable');/** * Request delegation. */delegate(proto, 'request')  .method('acceptsLanguages')  .method('acceptsEncodings')  .method('acceptsCharsets')  .method('accepts')  .method('get')  .method('is')  .access('querystring')  .access('idempotent')  .access('socket')  .access('search')  .access('method')  .access('query')  .access('path')  .access('url')  .access('accept')  .getter('origin')  .getter('href')  .getter('subdomains')  .getter('protocol')  .getter('host')  .getter('hostname')  .getter('URL')  .getter('header')  .getter('headers')  .getter('secure')  .getter('stale')  .getter('fresh')  .getter('ips')  .getter('ip');

delegates 实现

对于 setter 和 getter办法,是通过调用对象上的 __defineSetter__ 和 __defineGetter__ 来实现的

// delegates/index.js// getterDelegator.prototype.getter = function(name){  var proto = this.proto;  var target = this.target;  this.getters.push(name);  proto.__defineGetter__(name, function(){    return this[target][name];  });  return this;};// setterDelegator.prototype.setter = function(name){  var proto = this.proto;  var target = this.target;  this.setters.push(name);  proto.__defineSetter__(name, function(val){    return this[target][name] = val;  });  return this;};// accessDelegator.prototype.access = function(name){  return this.getter(name).setter(name);};// methodDelegator.prototype.method = function(name){  var proto = this.proto;  var target = this.target;  this.methods.push(name);  proto[name] = function(){    return this[target][name].apply(this[target], arguments);  };  return this;};

中间件实现原理

在Koa中,通过app.use() 来注册中间件,Koa反对三种不同类型的中间件:一般函数,async 函数,Generator函数,如果是Generator函数,那就用 convert 把函数包起来,而后在push到 this.middleware

  use(fn) {    if (typeof fn !== 'function') throw new TypeError('middleware must be a function!');    if (isGeneratorFunction(fn)) {        fn = convert(fn);    }    debug('use %s', fn._name || fn.name || '-');    this.middleware.push(fn);    return this;  }

convert作用

convert是用于将koa中以前基于generator写法的中间件转为基于promise写法。convert()的源码实现逻辑如下:

  1. convert 办法首先判断传入的中间件是否是一个函数,如果不是就抛出异样;
  2. 接着判断是否是一个 generator 函数,如果不是就间接返回,不做解决;
  3. 利用co将 generator 函数模式的中间件转成 promise 模式的中间件。

    function convert (mw) {  if (typeof mw !== 'function') { throw new TypeError('middleware must be a function')  }  // assume it's Promise-based middleware  if ( mw.constructor.name !== 'GeneratorFunction' && mw.constructor.name !== 'AsyncGeneratorFunction'  ) { return mw  }  const converted = function (ctx, next) { return co.call(   ctx,   mw.call(     ctx,     (function * (next) { return yield next() })(next)   ))  }  converted._name = mw._name || mw.name  return converted}

    中间件的执行

    Koa中间件的执行流程次要通过koa-compose中的compose函数实现,基于洋葱圈模型:

    koa-compose 的代码很短,一共才不到50行,次要执行程序如下:

    // koa-compose function compose (middleware) { //传入middleware数组  // 不是数组抛出异样  if (!Array.isArray(middleware)) throw new TypeError('Middleware stack must be an array!')  // 判断每个middleware中的每一项是否为函数  // 不是函数抛出异样  for (const fn of middleware) { if (typeof fn !== 'function') throw new TypeError('Middleware must be composed of functions!')  }  // 返回一个函数  return function (context, next) { //index计数 let index = -1 return dispatch(0) //调用dispatch,从第一个中间件开始 function dispatch (i) {   // i小于index,证实在中间件内调用了不止一次的next(),抛出谬误   if (i <= index) return Promise.reject(new Error('next() called multiple times'))   index = i // 更新index的值   let fn = middleware[i] //middleware中的函数,从第i个开始   if (i === middleware.length) fn = next //如果i走到最初一个的前面,就让fn为next,此时fn为undefined   if (!fn) return Promise.resolve()// 那么这时候就间接resolve   try {    // 把下一个中间件作为以后中间件的next传入     return Promise.resolve(fn(context, dispatch.bind(null, i + 1)));   } catch (err) {     return Promise.reject(err)   } }  }}