博客
关于我
强烈建议你试试无所不能的chatGPT,快点击我
Koa 源码解析
阅读量:6464 次
发布时间:2019-06-23

本文共 4924 字,大约阅读时间需要 16 分钟。

Koa 本体项目比较简单,可以看作 node http 模块的封装。

分为 application.js、context.js、request.js、response.js 四个文件。

项目入口是 application.js

application.js

该文件定义和导出了 application 类。

首先我们来看看 listen 方法,该函数可以创建一个 Http 服务。可以理解为 http.createServer 的语法糖。 源码:

listen(...args) {  const server = http.createServer(this.callback());  return server.listen(...args);}复制代码

app.callback 是 createServer 的回调函数。

callback() {  // 中间件合成一个函数,处理中间件的数据流动  const fn = compose(this.middleware);  if (!this.listenerCount('error')) this.on('error', this.onerror);  const handleRequest = (req, res) => {    // 创建 ctx 对象    const ctx = this.createContext(req, res);    return this.handleRequest(ctx, fn);  };  return handleRequest;}复制代码

middleware 是一个中间件函数构成的数组,中间件通过 app.use 被压入这个数组中,

use(fn) {  this.middleware.push(fn);  return this;}复制代码

handleRequest:

handleRequest(ctx, fnMiddleware) {  const res = ctx.res;  res.statusCode = 404; // 默认返回 404  const onerror = err => ctx.onerror(err); // 出错时调用 ctx.onerror 函数  const handleResponse = () => respond(ctx); // 处理完中间件后,调用 respond 函数  onFinished(res, onerror);  return fnMiddleware(ctx).then(handleResponse).catch(onerror);}复制代码

respond:

function respond(ctx) {  // allow bypassing koa  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 请求  if ('HEAD' == ctx.method) {    if (!res.headersSent && isJSON(body)) {      ctx.length = Buffer.byteLength(JSON.stringify(body));    }    return res.end();  }  // status body  if (null == body) {    body = ctx.message || String(code);    if (!res.headersSent) {      ctx.type = 'text';      ctx.length = Buffer.byteLength(body);    }    return res.end(body);  }  // 处理 body 是 buffer 或者 string 或者 stream 的情况  // 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);  // 发送 json  // body: json  body = JSON.stringify(body);  if (!res.headersSent) {    ctx.length = Buffer.byteLength(body);  }  res.end(body);}复制代码

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;}复制代码

compose 是 koa 的核心,在这里 compose 则实现了 koa 的中间件模型。洋葱模型...详情见 google。

function compose (middleware) {  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)      }    }  }}复制代码

通过测试用例来理解这个函数的作用

const ctx = {}const fn0 = async (ctx, next) => {  console.log(1);  await next();  console.log(6);}const fn1 = async (ctx, next) => {  console.log(2);  await next();  console.log(5);}const fn2 = async (ctx, next) => {  console.log(3);  await next();  console.log(4);}const stack = [fn0, fn1, fn2];return compose(stack)(ctx);复制代码

函数运行时将经历的步骤是:

  1. 首先运行 fn0(ctx, dispatch.bind(null, 1))
  2. 运行到 fn0 中 await next() 的时候,即等待 dispatch(1) = fn1(ctx, dispatch.bind(null, 2)) 执行完成
  3. 运行到 fn1 中 await next() 的时候,即等待 dispatch(2) = fn2(ctx, dispatch.bind(null, 3)) 执行完成
  4. 运行到 fn2 中 await next() 的时候,即等待 dispatch(3) 执行完成。由于不存在 fn3,所以 dispatch(3) = Promise.resolve()。此时因为立即 resolve 了,所以 fn2 会继续执行。
  5. 当 fn2 执行完成,dispatch(2) 被 resolve 了,所以 fn1 会继续执行至完成,以此类推直到 fn0 执行完成。

如果 fn1 中,有两个 next

const fn1 = async (ctx, next) => {  console.log(2);  await next();  await next();  console.log(5);}复制代码

执行第二个 next() 的时候,相当于执行 dispatch(2),此时 i = 2,而 index 相当于一个全局的 i 的值,此时等于 3。判断 i < index 抛出错误。

如果传入 next 函数:

compose(stack)(ctx, async (ctx) => console.log(called))复制代码

在第 4 步,执行 dispatch(3) 的时候 if (i === middleware.length) fn = next 由于 i 为 middleware.length, next 不为 undefined。 所以将会返回 next(context, dispatch.bind(null, 4))。如果 next(context, dispatch.bind(null, 4)) 返回一个 promise,则会等待 next 返回结果,fn1 才会继续执行。

context.js

context 有一个 proto 对象,它里面里面有一些辅助的函数,并且 proto 代理了它的 response 和 request 属性中的一些属性和方法,以便快捷访问。在调用 app.createContext 函数的时候,会通过 Object.create(proto) 创建一个实例。然后给这个 request response state 之类的属性。

request.js 和 response.js

request 对象和 response 对象。改善了原生的 req 和 res 对象。 增加了易用性。

转载于:https://juejin.im/post/5bc89f576fb9a05cd24dc8aa

你可能感兴趣的文章
移动铁通宽带上网设置教程
查看>>
Python算法(含源代码下载)
查看>>
利用Windows自带的Certutil查看文件MD5
查看>>
通过原生js添加div和css
查看>>
简单的导出表格和将表格下载到桌面上。
查看>>
《ArcGIS Engine+C#实例开发教程》第一讲桌面GIS应用程序框架的建立
查看>>
查询指定名称的文件
查看>>
Python 嵌套列表解析
查看>>
[GXOI/GZOI2019]旧词——树链剖分+线段树
查看>>
anroid 广播
查看>>
AJAX POST&跨域 解决方案 - CORS
查看>>
开篇,博客的申请理由
查看>>
Servlet 技术全总结 (已完成,不定期增加内容)
查看>>
[JSOI2008]星球大战starwar BZOJ1015
查看>>
centos 7 部署LDAP服务
查看>>
揭秘马云帝国内幕:马云的野心有多大
查看>>
iOS项目分层
查看>>
一个action读取另一个action里的session
查看>>
IntelliJ IDEA 注册码
查看>>
String字符串的截取
查看>>