聊一聊koa

栏目: Node.js · 发布时间: 5年前

内容简介:本文主要通过一个简单的例子来解释koa的内部原理。在文章开头的例子中,执行const app = new koa()时,实际上构造了一个Application实例,在application的构造方法中,创建了context.js、request.js、response.js代码的运行实例。

目标

本文主要通过一个简单的例子来解释koa的内部原理。

koa的一个简单例子

const Koa = require('koa');
const app = new Koa();

app.use(async ctx => {
  ctx.body = 'Hello World';
});

app.listen(3000);

koa内部文件组成

聊一聊koa

  • application.js中包含了Application类和一些辅助方法
  • context.js主要作用是承载上下信息,并封装了处理上下文信息的操作
  • request.js中封装了处理请求信息的基本操作
  • response.js中封装了处理响应信息的基本操作

koa内部

在文章开头的例子中,执行const app = new koa()时,实际上构造了一个Application实例,在application的构造方法中,创建了context.js、request.js、response.js代码的运行实例。 下文约定request值request.js代码运行实例,response指response.js运行实例,context指context.js运行实例。

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);
    if (util.inspect.custom) {
      this[util.inspect.custom] = this.inspect;
    }
  }

调用app.listen(3000)方法监听3000端口的http请求,listen方法内部创建了一个http.Server对象,并调用http.Server的listen方法。具体代码如下:

listen(...args) {
    debug('listen');
    const server = http.createServer(this.callback());
    return server.listen(...args);
  }

其中this.callback方法的源码如下:

callback() {
    const fn = compose(this.middleware);

    if (!this.listenerCount('error')) this.on('error', this.onerror);

    const handleRequest = (req, res) => {
      const ctx = this.createContext(req, res);
      return this.handleRequest(ctx, fn);
    };

    return handleRequest;
  }

该方法返回一个handleRequest函数,作为createServer的参数,当http.Server实例接收到一个http请求时,会将请求信息和请求响应对象传给handleRequest函数,具体指将http.IncomingMessage的实例req,和http.ServerResponse的实例res传给handleRequest方法。其中this.createContext函数的源码如下:

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;
  }

上面代码的主要作用是将请求信息和响应信息封装在request.js和response.js的运行实例中,并创建上下文对象context,context包含了application实例、request实例、response实例的引用。在this.callback方法中还有另一行很重要的代码:

const fn = compose(this.middleware);

可以从Application的构造方法知道this.middleware是一个数组,该数组用来存储app.use方法传入的中间件方法。Application的use方法具体代码实现细节如下,其中最关键的一行代码是this.middleware.push(fn)。

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;
  }

compose方法的实现包含在koa-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, dispatch.bind(null, i + 1)));
      } catch (err) {
        return Promise.reject(err)
      }
    }
  }
}

compose方法接收this.middleware数组,返回一个匿名函数,该函数接收两个参数,上下文实例context和一个next函数,执行该匿名函数会执行this.middleware数组中的所有中间件函数,然后在执行传入的next函数。在Application的callback方法的最后将执行上下文对象和compose方法返回的匿名函数作为参数调用this.handleRequest方法。接下来看一下this.handleRequest方法的具体细节:

handleRequest(ctx, fnMiddleware) {
    const res = ctx.res;
    res.statusCode = 404;
    const onerror = err => ctx.onerror(err);
    const handleResponse = () => respond(ctx);
    onFinished(res, onerror);
    return fnMiddleware(ctx).then(handleResponse).catch(onerror);
  }

在this.handleRequest方法中创建了错误处理方法 onError 和返回响应的方法 handleResponse

fnMiddleware就是compose方法返回的匿名函数。在this.handleRequest方法的最后执行匿名函数,并传入hanldeResponse和onError函数分别处理正常请求响应流程和异常情况。

return fnMiddleware(ctx).then(handleResponse).catch(onerror);

执行fnMiddleware函数,实际上是执行之前传入所有中间件函数。在中间函数中可以拿到上下文对象的引用,通过上下文对象我们可以获取到经过封装的请求和响应实例,具体形式如下:

app.use(async ctx => {
  ctx; // 这是 Context
  ctx.request; // 这是 koa Request
  ctx.response; // 这是 koa Response
});

在中间件方法中可以设置响应头信息、响应内容。以及读取数据库,获取html模版等。将需要返回给用户端的数据赋值给上下文对象context的body属性。respond方法的具体实现如下:

function respond(ctx) {
  // allow bypassing koa
  if (false === ctx.respond) return;

  if (!ctx.writable) return;

  const res = ctx.res;
  let body = ctx.body;
  const code = ctx.status;

  // ignore body
  if (statuses.empty[code]) {
    // strip headers
    ctx.body = null;
    return res.end();
  }

  if ('HEAD' == ctx.method) {
    if (!res.headersSent && isJSON(body)) {
      ctx.length = Buffer.byteLength(JSON.stringify(body));
    }
    return res.end();
  }

  // status body
  if (null == body) {
    if (ctx.req.httpVersionMajor >= 2) {
      body = String(code);
    } else {
      body = ctx.message || String(code);
    }
    if (!res.headersSent) {
      ctx.type = 'text';
      ctx.length = Buffer.byteLength(body);
    }
    return res.end(body);
  }

  // responses
  if (Buffer.isBuffer(body)) return res.end(body);
  if ('string' == typeof body) return res.end(body);
  if (body instanceof Stream) return body.pipe(res);

  // body: json
  body = JSON.stringify(body);
  if (!res.headersSent) {
    ctx.length = Buffer.byteLength(body);
  }
  res.end(body);
}

respond方法的主要作用是对返回的内容进行一些处理,然后调用node.js的http.ServerResponse实例的end方法,将具体内容返回给用户端。

request.js和response.js

在Application的createContext方法中,将node.js的请求(http.IncomingMessage)和响应对象(http.ServerResponse)分别赋值给了request.js和response.js文件代码实例对象。

request.js中主要包含了处理请求的方法(实际上都是get方法,或者获取器),获取请求数据,例如:

get host() {
    const proxy = this.app.proxy;
    let host = proxy && this.get('X-Forwarded-Host');
    if (!host) {
      if (this.req.httpVersionMajor >= 2) host = this.get(':authority');
      if (!host) host = this.get('Host');
    }
    if (!host) return '';
    return host.split(/\s*,\s*/, 1)[0];
  },

上面的代码中this.req是http.IncomingMessage实例,包含了http请求信息。

response.js中包含了处理请求响应的操作。例如设置响应状态:

set status(code) {
    if (this.headerSent) return;
    assert(Number.isInteger(code), 'status code must be a number');
    assert(code >= 100 && code <= 999, `invalid status code: ${code}`);
    this._explicitStatus = true;
    this.res.statusCode = code;
    if (this.req.httpVersionMajor < 2) this.res.statusMessage = statuses[code];
    if (this.body && statuses.empty[code]) this.body = null;
  }

this.res指http.ServerResponse对象实例

结尾

~~~~~


以上就是本文的全部内容,希望对大家的学习有所帮助,也希望大家多多支持 码农网

查看所有标签

猜你喜欢:

本站部分资源来源于网络,本站转载出于传递更多信息之目的,版权归原作者或者来源机构所有,如转载稿涉及版权问题,请联系我们

Ajax for Web Application Developers

Ajax for Web Application Developers

Kris Hadlock / Sams / 2006-10-30 / GBP 32.99

Book Description Reusable components and patterns for Ajax-driven applications Ajax is one of the latest and greatest ways to improve users’ online experience and create new and innovative web f......一起来看看 《Ajax for Web Application Developers》 这本书的介绍吧!

JS 压缩/解压工具
JS 压缩/解压工具

在线压缩/解压 JS 代码

CSS 压缩/解压工具
CSS 压缩/解压工具

在线压缩/解压 CSS 代码

XML 在线格式化
XML 在线格式化

在线 XML 格式化压缩工具