Koa源码分析

一.下载koa源代码

先看看这个极简的启动代码:
node

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

// response
app.use(ctx => {
  ctx.body = 'Hello Koa';
});

app.listen(3000);复制代码

咱们在koa源码文件夹下建立index.js文件, 将上面的代码写入,并将require('koa') 换成 require('.')git

const Koa = require('.')
debugger
const app = new Koa();
app.use(ctx => {
  ctx.body = 'Hello Koa';
});
app.listen(3000);
复制代码

而后进入目录运行node --inspect-brk index.jsgithub

在chrome浏览器打开调试 chrome://inspectchrome

   1.引入Koajson

  • require('.')通过路径分析


require 会依照这个顺序去查找须要的文件
数组

Module._extensions={
    .js:funciton(module,filename),
    .json:funciton(module,filename),
    .node:funciton(module,filename)
}复制代码


经过读取package.json中的main字段获得完整路径promise

  • 路径分析以后,是文件定位

查找到路径以后经过 fs.readFileSync加载模块浏览器


  • 编译执行


读取文件后开始编译,首先将读取的代码script,进行组装,bash

即头部添加(function (exports, require, module, __filename, __dirname) { ', cookie

尾部添加

'\n});'

NativeModule.wrap = function(script) {
    return NativeModule.wrapper[0] + script + NativeModule.wrapper[1];
  };

NativeModule.wrapper = [
    '(function (exports, require, module, __filename, __dirname) { ',
    '\n});'
  ];复制代码

这就是为何每一个模块没有定义exports, require, module, __filename, __dirname变量,却能使用的缘由

2. 建立Koa对象

Application

constructor()
首先 new Koa()的时候就是new的这个对象, 最关键的是建立了context,request,response对象
constructor() {
    super();

    this.proxy = false;
    // 中间件初始化为一个列表
    this.middleware = [];

    this.subdomainOffset = 2;
    // 默认为开发环境
    this.env = process.env.NODE_ENV || 'development';
    // 建立Context, Request,Response对象,为何要用Object.create函数呢?
    this.context = Object.create(context);
    this.request = Object.create(request);
    this.response = Object.create(response);
  }复制代码
use()
经过该方法引入中间件,中间件能够是一个普通的函数, 能够是一个generator, 能够是async函数,若是是generator那么koa会帮你转成基于promise的函数。根据use使用的前后顺序, middleware数组中会存储全部的中间件。
use(fn) {
     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);
    }
    this.middleware.push(fn);
    return this;
  }复制代码
listen()
listen函数表面上看是用于监听某个端口,实际上包裹了真正的nodejs提供的http server, createServer函数接受一个处理请求的函数,由callback()返回
listen(...args) {
    debug('listen');
    const server = http.createServer(this.callback());
    return server.listen(...args);
  }
复制代码
callback()
callback函数是Application中真正核心的处理逻辑, 其返回了一个请求处理器,封装了koa全部的方法逻辑
callback() {
    // compose函数将注册的中间件组合成了一个函数,相似于递归
    const fn = compose(this.middleware);
    // Koa Application 扩展了 Emitter类, listeners 是Emitter类的属性.
    // 这里表示若没有注册error事件处理函数, 则注册一个
    if (!this.listeners('error').length) this.on('error', this.onerror);
    
    // 返回的请求处理函数, req, res是createServer回调时会传入的nodejs请求和响应对象。
    const handleRequest = (req, res) => {
      // 默认的的状态码为404
      res.statusCode = 404;
      // 建立koa应用的上下文, context将不少属性和方法都代理到这个对象上方便开发.
      const ctx = this.createContext(req, res);
      // 使用 ctx.onerror处理请求错误, 详见Context
      const onerror = err => ctx.onerror(err);
      // 处理响应函数
      const handleResponse = () => respond(ctx);
      // 请求完成以后若是出错调用onerror
      onFinished(res, onerror);
      // 等中间件都处理完了以后处理响应
      return fn(ctx).then(handleResponse).catch(onerror);
    };

    return handleRequest;
  }
复制代码
createContext()
这个函数与其说是建立Context对象,不如说是将koa的各个内部对象链接在一块儿。并设置cookies和accepts.
// req, res 是node的原生请求响应对象,是全部信息的来源.
// request,response是koa提供的方便咱们开发使用的请求响应对象.
  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;
    // originalUrl即 req.url
    context.originalUrl = request.originalUrl = req.url;
    // cookies 直接使用的第三方库
    context.cookies = new Cookies(req, res, {
      keys: this.keys,
      secure: request.secure
    });
    // 更经常使用的从ip中读取请求方的IP地址, ips是?
    request.ip = request.ips[0] || req.socket.remoteAddress || '';
    // 使用accepts设置请求能接受的内容类型
    context.accept = request.accept = accepts(req);
    // ?
    context.state = {};
    return context;
  }
复制代码
respond()
当咱们完成处理须要返回时咱们设置 this.body = 'xxx' 而后函数返回,不须要咱们手动调用res.end(),由于koa已经帮咱们封装好了.
/**
 * Response helper.
 */

function respond(ctx) {
  // ...
  // 若是HTTP状态码表示内容应该为空,则设空返回
  if (statuses.empty[code]) {
    
    ctx.body = null;
    return res.end();
  }
  
  if ('HEAD' === ctx.method) {
    // 要求返回响应头,若是headersSent为false
    if (!res.headersSent && isJSON(body)) {
      ctx.length = Buffer.byteLength(JSON.stringify(body));
    }
    return res.end();
  }

  // 若是没有设置body,只设置了status,则用状态码或message设置body.
  if (null == body) {
    body = ctx.message || String(code);
    if (!res.headersSent) {
      ctx.type = 'text';
      ctx.length = Buffer.byteLength(body);
    }
    return res.end(body);
  }
    
  // koa 支持Buffer, string, Stream类型的数据
  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 处理普通json类型返回.
  body = JSON.stringify(body);
  if (!res.headersSent) {
    ctx.length = Buffer.byteLength(body);
  }
  res.end(body);
}
复制代码

Context

Context是整个请求的上下文,其最特殊的地方实际上是整合response和request,让你在应用的任何地方经过context得到应用相关的任何信息

Response

响应体相关的方法属性
相关文章
相关标签/搜索