Koa源码阅读(二)上下文ctx

·  阅读 2437
Koa源码阅读(二)上下文ctx

上篇提到,this.callback() 返回一个回调函数,其实是以闭包的形式返回了一个局部函数变量 handleRequest,供 Server 调用来处理 HTTP 请求。

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

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

  return handleRequest;
}
复制代码

请求到来时,Server 将 Node 提供的原生 requestresponse 传给回调 handleRequest,它执行两项工作:

  • 创建一个上下文 ctx,封装了本次的请求和响应
  • 将上下文 ctx 和函数 fn 交由 this.handleRequest() 处理

接下来我们看一下上下文 ctx 是怎么创建和使用的。

创建上下文 ctx

直接将 Node 提供的原生 requestresponse 传给了 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;
}
复制代码

代码似乎重复性很大,我们梳理一下:

属性 含义
context / .ctx 上下文
req / .req Node 请求
res / .res Node 响应
request / .request Koa 请求
response / .response Koa响应

主要就是上下文、Node 请求&响应、Koa 请求&响应之间的交叉引用,便于使用。

ctx 是怎么封装了请求与响应?Node 请求&响应与 Koa 请求&响应之间又是什么关系呢?这就不得不提到 Koa 用到的委托模式了。

委托模式

委托模式(Delegation Pattern)是设计模式的一种,意思是外层暴露的对象将请求委托给内部的其他对象进行处理。

context.js 可以中看到,Koa 使用 delegates 这个 NPM 包,将本应由上下文 ctx 处理的事情委托给了 requestresponse,这两个对象来自于 request.jsresponse.js

/* context.js */

const delegate = require('delegates');

const proto = module.exports = {
  /* 此处是 context 自己完成的一些方法和属性 */
}

/* 委托给 response 处理 */
delegate(proto, 'response')
  .method('attachment')
	.method('redirect')
  .access('status')
	.access('body')
	.access('length')
  /* ... */

/* 委托给 request 处理 */
delegate(proto, 'request')
  .method('acceptsLanguages')
  .method('acceptsEncodings')
  .access('method')
  .access('query')
  .access('path')
  .access('url')
  .getter('host')
  .getter('hostname')
  .getter('URL')
  /* ... */
复制代码

这样一来,我们对上下文 ctx 的操作,如 ctx.typectx.length 就会由 response 对象执行,ctx.pathctx.method 就会由 request 对象执行。不要忘了, responserequest 是 Koa 自己的请求和响应。怎么把它们与 Node 请求&响应联系起来呢?

请求与响应

再啰嗦一遍,真正将请求与响应的操作落实到位的不是上下文 ctx ,而是来自 request.jsrequest 对象和来自 response.jsresponse 对象。我们看一下这两个对象的实现。

/* request.js */

module.exports = {
  /* ... */
  
  /**
   * Get request URL.
   *
   * @return {String}
   * @api public
   */

  get url() {
    return this.req.url;
  },
  
  /* ... */
}
复制代码
/* response.js */

module.exports = {
  /* ... */
  
  /**
   * Check if a header has been written to the socket.
   *
   * @return {Boolean}
   * @api public
   */

  get headerSent() {
    return this.res.headersSent;
  },
  
  /* ... */
}
复制代码

原来是靠 Koa 请求/响应去操作 Node 请求/响应来实现的!整个流程串起来就是,上下文 ctx 委托给 Koa 请求/响应,Koa 请求/响应操作 Node 请求/响应,从而实现了完整的请求/响应处理流程。

这个关系弄懂了,Koa的上下文 ctx 是怎么回事也就明白了。

开发中常遇到的获取 POST 参数问题

前面提到,ctx.query 委托给了 requestrequest 对 Node 请求中的 query 做了封装,所以我们可以直接用 ctx.query 获取到 GET 参数。

而 POST 请求就没有这种封装,需要通过解析 Node 原生请求来获取其参数。

app.use( async ( ctx ) => {
  if ( ctx.url === '/' && ctx.method === 'POST' ) {
    // 当 POST 请求的时候,解析 POST 表单里的数据,并显示出来
    let postData = await parsePostData( ctx )
    ctx.body = postData
  }
})

// 解析上下文里 Node 原生请求的 POST 参数
function parsePostData( ctx ) {
  return new Promise((resolve, reject) => {
    try {
      let postdata = "";
      ctx.req.addListener('data', (data) => {
        postdata += data
      })
      ctx.req.addListener("end",function(){
        let parseData = parseQueryStr( postdata )
        resolve( parseData )
      })
    } catch ( err ) {
      reject(err)
    }
  })
}

// 将 POST 请求参数字符串解析成 JSON
function parseQueryStr( queryStr ) {
  let queryData = {}
  let queryStrList = queryStr.split('&')
  console.log( queryStrList )
  for (  let [ index, queryStr ] of queryStrList.entries()  ) {
    let itemList = queryStr.split('=')
    queryData[ itemList[0] ] = decodeURIComponent(itemList[1])
  }
  return queryData
}

// 代码来源于:https://chenshenhai.github.io/koa2-note/note/request/post.html
复制代码

也可以直接使用 koa-bodyparser 这个 NPM 包作为中间件完成 POST 数据处理。

const bodyparser = require('koa-bodyparser')

app.use(bodyparser())

app.use( async (ctx) => {
  if (ctx.url === '/' && ctx.method === 'POST') {
    let data = ctx.request.body
    ctx.body = data
  }
})
复制代码
分类:
前端
标签:
收藏成功!
已添加到「」, 点击更改