Koa router的反思 - 利用函数式编程封装中间件
2017-08-19 本文已影响133人
编程go
关键词: Koa router、函数式编程、 中间件
问题描述:
在开发微信公众号时,微信服务器与我自己的服务频繁的交互。微信的服务器对我的服务器目前也仅仅有两种方式: 一种是Get 请求另外一种是Post 请求。于是我在设计Koa 的router 时,想把所有关于微信服务器与我的服务器交互的逻辑统一写在一个中间件中。如下:
export const router = app => {
const router = new Router()
router.all('/wechat-hear', async (ctx, next) => {
// call wetchat middleware
wechatMiddle(config.wechat, reply)(ctx, next)
})
app
.use(router.routes())
.use(router.allowedMethods())
}
明显可以看出wechatMiddle 利用了函数式编程,这样的设计自认为代码的逻辑更强了。但是在测试时总是出现下面的exception:
(node:41169) UnhandledPromiseRejectionWarning: Unhandled promise rejection (rejection id: 1): AssertionError [ERR_ASSERTION]: headers have already been sent
下面时中间件的代码:
export default function (opts, reply) {
return async function(ctx, next) {
// Get wechat access token,.
if (ctx.method === 'GET') {
// do validation
} else if (ctx.method === 'POST') {
// do validation
}
// parse request from wchat to object
const data = await getRawBody(ctx.req, {
length: ctx.length,
limit: '1mb',
encoding: ctx.charset
})
const content = await util.parseXML (data)
var xml =
`<xml>
<ToUserName><![CDATA[${content.xml.FromUserName[0]}]]></ToUserName>
<FromUserName><![CDATA[${content.xml.ToUserName[0]}]]></FromUserName>
</xml>`
ctx.body = xml
ctx.status = 200
ctx.type = 'application/xml'
}
}
踩过的坑:
- 首先根据exception的信息和bing 搜索后的结果,初步认为时由于Koa 的ctx(上下文对象)已经返回但是代码再一次返回。
- 在代码的最后三句,我看到连续对上下文(ctx)赋值了三次。尽管我估计不会时这里出现的问题,但是我还是把其中的两个ctx 赋值的语句注释掉,程序出现exception。
- 由于Koa 引入了async/await 优雅的实现了nodejs 的异步问题,当前这个exception 也很有可能时异步的问题,中间件返回函数我是用async 关键字标记了,中间件中的关键方法我也用await 关键字标记了。因此我自认为不是异步的问题,此时心中开始抓狂了,把许多代码大片注释并调试。此刻就像无头的苍蝇再乱撞,希望可以碰到root cause。这个过程浪费了大量的时间和精力, 依旧一无所获。
- 最后我决定取消对router koa中间件的封装,此时发现了问题,在router koa 类中,我没有在调用中间件时对他使用await关键字标记。
正确如下:
router.all('/wechat-hear', async (ctx, next) => {
// call wetchat middleware
await wechatMiddle(config.wechat, reply)(ctx, next)
})
坑后总结:
前两步思路还是正确的,第三步上半部分也没有问题,但是在后来暴力调试劳力伤神这一点要总结,如果在第三步的下半部分可以稍微王router koa中调用中间件的方法思考下,这个问题则可以很快完美的解决了。