通过debugger学习Koa2源码第一篇

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

内容简介:个人觉得学习源码需要带着目的去看, 才能达到效果, 但是公司又没有上Node, 没有实践怎么办呢?最近发现通过调试Koa2源码也是个不错的法子。关于Node下调试推荐阅读下:我们这里只需要学习

个人觉得学习源码需要带着目的去看, 才能达到效果, 但是公司又没有上Node, 没有实践怎么办呢?最近发现通过调试Koa2源码也是个不错的法子。

准备工作

- 安装node
- 安装vscode
- 学习如何在vscode下调试
复制代码

关于Node下调试推荐阅读下: 《Node.js 调试指南》

我们这里只需要学习 如何在vscode下调试 即可。 具体就不详情说了, 见链接, 有问题我们可以讨论。

从Hello World开始学习

// 安装koa2, nodemon等后, 来个入门的hello world

const Koa = require('koa')
const app = new Koa()
const port = 3000

app.use(async (ctx, next) => {
    await next()
    ctx.response.status = 200
    ctx.response.body = 'hello world'
})

app.listen(port, () => {
    console.log(`server is running on the port: ${port}`)
})

复制代码

是的没错,通过上述这个入门代码也能学习到Koa2的源码知识。

首先观察上面用到了的一些API。
new Koa()
app.use()
app.listen()
复制代码

我们现在开始进入node_modules目录下找到Koa。

通过debugger学习Koa2源码第一篇

通过package.json得知Koa的入口文件为

"main": "lib/application.js"
复制代码
// lib目录下模块
- application.js  实例
- context.js      上下文对象
- request.js      请求对象
- response.js     响应对象
复制代码

现在我们当然从入口文件application.js开始。我们的实例代码第一行是new koa(); 我们肯定是有一个类,这里是开始的突破点。

// 构造函数继承node的EventEmitter类 
// http://nodejs.cn/api/events.html
module.exports = class Application extends Emitter {
    ...
}
复制代码

然后我们去打三个断点, 分别如下:

通过debugger学习Koa2源码第一篇
通过debugger学习Koa2源码第一篇
通过debugger学习Koa2源码第一篇

之所以在这三个地方打断点是对应前面提到的执行了Koa2的三个api.通过这三个断点我们一步步去了解Koa2内部是怎么执行的。

最开始肯定是执行constructor();部分注释见上述截图。

this.middleware = []; 这个是用来存放通过app.use()注册的中间件的。
复制代码

重点接下来看app.use()。

通过debugger学习Koa2源码第一篇

很明显fn指的就是:

async (ctx, next) => {
    await next()
    ctx.response.status = 200
    ctx.response.body = 'hello world'
}
复制代码

在use(fn)方法中主要做了以下事情:

1. 错误校验, fn必须是函数, 否则给出错误提示
2. fn不推荐使用生成器函数, v2版本Koa2会进行转化, 但是v3就会不支持生成器函数了, 这里主要是对koa1的向下兼容。
3. 存储注册的中间件
3. return this. 支持链式调用
复制代码

这个时候你可以看懂this大概有这些属性:

Application {
    _events:Object {}
    _eventsCount:0
    _maxListeners:undefined
    context:Object {}
    env:"development"
    middleware:Array(1) []
    proxy:false
    request:Object {}
    response:Object {}
    subdomainOffset:2
    Symbol(util.inspect.custom):inspect() { … }
    __proto__:EventEmitter
}
复制代码

然后进入listen(), 这里有一段this.callback(), 我们需要去这个方法下打断点看执行了什么。

// 其实就是http.createServer(app.callback()).listen(...)的语法糖
listen(...args) {
    debug('listen');
    const server = http.createServer(this.callback());
    return server.listen(...args);
  }
复制代码
通过debugger学习Koa2源码第一篇
// callback()做了以下几件事:
1. 通过compose合并中间件
2. 为应用注册error事件的监听器
3. 返回一个请求处理函数handleRequest
复制代码

接下来我们看看this.createContext()和this.handleRequest(),分别打断点看代码。

note: 提一个小问题, node应该经常会发生端口占用问题。
复制代码

每次请求都会创建一个上下文对象。

通过debugger学习Koa2源码第一篇
handleRequest(ctx, fnMiddleware) {
    const res = ctx.res;
    res.statusCode = 404;
    // 错误处理
    const onerror = err => ctx.onerror(err);
    const handleResponse = () => respond(ctx);
    // 通过第三方库on-finished监听http response,当请求结束时执行回调,这里传入的回调是context.onerror(err),即当错误发生时才执行。
    onFinished(res, onerror);
    // 即将所有中间件执行(传入请求上下文对象ctx),之后执行响应处理函数(app.respond(ctx)),当抛出异常时同样使用cintext,onerror(err)处理。
    return fnMiddleware(ctx).then(handleResponse).catch(onerror);
  }
复制代码

对respond打断点

/**
 * Response helper.
 * 在所有中间件执行完之后执行
 */

function respond(ctx) {
  // allow bypassing koa
  // 通过设置ctx.respond = false来跳过这个函数,但不推荐这样子
  if (false === ctx.respond) return;

  const res = ctx.res;
  // 上下文对象不可写时也会退出该函数
  if (!ctx.writable) return;

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

  // ignore body
  // 当返回的状态码表示没有响应主体时,将响应主体置空:
  if (statuses.empty[code]) {
    // strip headers
    ctx.body = null;
    return res.end();
  }
  // 当请求方法为HEAD时,判断响应头是否发送以及响应主体是否为JSON格式,若满足则设置响应Content-Length:
  if ('HEAD' == ctx.method) {
    if (!res.headersSent && isJSON(body)) {
      ctx.length = Buffer.byteLength(JSON.stringify(body));
    }
    return res.end();
  }

  // status body
  // 当返回的状态码表示有响应主体,但响应主体为空时,将响应主体设置为响应信息或状态码。并当响应头未发送时设置Content-Type与Content-Length:
  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);
}

复制代码

错误处理

onerror(err) {
    // 当err不为Error类型时抛出异常。
    if (!(err instanceof Error)) throw new TypeError(util.format('non-error thrown: %j', err));
    // 当 err.status 是 404 或 err.expose 是 true 时默认错误处理程序也不会输出错误
    if (404 == err.status || err.expose) return;
    // 默认情况下,将所有错误输出到 stderr,除非 app.silent 为 true
    if (this.silent) return;

    const msg = err.stack || err.toString();
    console.error();
    console.error(msg.replace(/^/gm, '  '));
    console.error();
  }
复制代码

以上就是本文的全部内容,希望本文的内容对大家的学习或者工作能带来一定的帮助,也希望大家多多支持 码农网

查看所有标签

猜你喜欢:

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

数据结构与算法分析(C++版)(第3版)

数据结构与算法分析(C++版)(第3版)

Clifford A. Shaffer / 张铭、刘晓丹、等译 / 电子工业出版社 / 2013 / 59.00元

本书采用当前流行的面向对象的C++程序设计语言来描述数据结构和算法, 因为C++语言是程序员最广泛使用的语言。因此, 程序员可以把本书中的许多算法直接应用于将来的实际项目中。尽管数据结构和算法在设计本质上还是很底层的东西, 并不像大型软件工程项目开发那样, 对面向对象方法具有直接的依赖性, 因此有人会认为并不需要采用高层次的面向对象技术来描述底层算法。 但是采用C++语言能更好地体现抽象数据类型的......一起来看看 《数据结构与算法分析(C++版)(第3版)》 这本书的介绍吧!

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

在线压缩/解压 JS 代码

Base64 编码/解码
Base64 编码/解码

Base64 编码/解码

Markdown 在线编辑器
Markdown 在线编辑器

Markdown 在线编辑器