内容简介:上篇提到,请求到来时,接下来我们看一下上下文 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 提供的原生 request
和 response
传给回调 handleRequest
,它执行两项工作:
-
创建一个上下文
ctx
,封装了本次的请求和响应 -
将上下文
ctx
和函数fn
交由this.handleRequest()
处理
接下来我们看一下上下文 ctx 是怎么创建和使用的。
创建上下文 ctx
直接将 Node 提供的原生 request
和 response
传给了 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
处理的事情委托给了 request
和 response
,这两个对象来自于 request.js
和 response.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.type
和 ctx.length
就会由 response
对象执行, ctx.path
和 ctx.method
就会由 request
对象执行。不要忘了, response
和 request
是 Koa 自己的请求和响应。怎么把它们与 Node 请求&响应联系起来呢?
请求与响应
再啰嗦一遍,真正将请求与响应的操作落实到位的不是上下文 ctx
,而是来自 request.js
的 request
对象和来自 response.js
的 response
对象。我们看一下这两个对象的实现。
/* 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
委托给了 request
, request
对 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 } }) 复制代码
以上就是本文的全部内容,希望本文的内容对大家的学习或者工作能带来一定的帮助,也希望大家多多支持 码农网
猜你喜欢:- flask 源码解析4:上下文
- Spring源码系列 —— 构造和初始化上下文
- DDD:识别限界上下文以及理解上下文映射
- DDD:识别限界上下文以及理解上下文映射
- 如何划分限界上下文
- 详解Flask上下文
本站部分资源来源于网络,本站转载出于传递更多信息之目的,版权归原作者或者来源机构所有,如转载稿涉及版权问题,请联系我们。
URL 编码/解码
URL 编码/解码
RGB HSV 转换
RGB HSV 互转工具