一.下载koa源代码
先看看这个极简的启动代码:
const Koa = require('koa');const app = new Koa();// responseapp.use(ctx => { ctx.body = 'Hello Koa';});app.listen(3000);复制代码
我们在koa源码文件夹下创建index.js文件, 将上面的代码写入,并将require('koa')
换成 require('.')
const Koa = require('.')debuggerconst app = new Koa();app.use(ctx => { ctx.body = 'Hello Koa';});app.listen(3000);复制代码
然后进入目录运行node --inspect-brk index.js
在chrome浏览器打开调试 chrome://inspect
1.引入Koa
- require('.')经过路径分析
require 会依照这个顺序去查找需要的文件
Module._extensions={ .js:funciton(module,filename), .json:funciton(module,filename), .node:funciton(module,filename)}复制代码
通过读取package.json中的main字段得到完整路径
- 路径分析之后,是文件定位
查找到路径之后通过 fs.readFileSync加载模块
- 编译执行
读取文件后开始编译,首先将读取的代码script,进行组装,
即头部添加(function (exports, require, module, __filename, __dirname) { ',
尾部添加
'\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
响应体相关的方法属性