ngx.lua 中 ngx.req.socket 和 ngx.req.get_body_data 的总结

最近将上传接口从php+apache重构成nginx+lua,因为是多人协作的项目,在完成后进行测试时,总会出现上传时无法读取图片数据的问题。于是昨天晚上,我花了一些时间研究了一下,发现是ngx.req.socketngx.req.get_body_data混用导致。下面简单记录发现和解决这个问题的过程。

背景简介

图片上传时,除了图片数据,还需要一些参数,比如验证参数、图片处理参数等。而这些参数可以以两种方式传递到我们服务端:

  • application/x-www-form-urlencoded: 一般只POST比较短的参数
  • multipart/form-data: 一般用于POST比较大的二进制数据

因此,我们需要解析这两种上传的方式,而坑就埋在了此处。

问题初探

这个问题的发现和定位还是很简单的。部署完测试环境进行测试时,发现测试没成功,返回的错误信息显示上传数据失败。因此,问题已经在解析参数和上传数据的那个库args.lua中。

args.lua中,有三个函数:

  • 读取并解析完整请求参数;
  • 获取上传所需要的参数,用于验证和处理图片;
  • 获取上传的图片数据。

显然,问题在获取上传的图片数据这个函数中。这个函数比较简单:

  • 读取Content-Length,判断是否有效(有最大上传限制);
  • 使用ngx.req.socket读取数据,并验证其长度是否有效。

上面说到要支持form-urlencodedmultipart/form-data这两种上传方式,而判断上传类型并读取解析上传参数是在解析参数这个函数中操作的:

  • ngx.req.get_headers中读取Content-Type,得到上传的方式;
  • 对于form-urlencoded,先使用ngx.req.read_body函数,然后使用ngx.req.get_post_args函数获取参数;
  • 对于multipart/form-data,我们使用了春哥写的upload.luagithub地址),而这里面用的是ngx.req.socket

我在OpenResty的邮件列表中,看到ngx.req.get_bodyngx.req.socket不要混用,是因为前者会把数据都出来,再使用后者自然就无法读取数据了;而如果先用socket,读了数据就会对ngx.req.get_body有影响。

有上面的情况可以看到,是由二者的混用导致的问题。而这时,我尝试修改读取数据的函数,看能不能把这两种方式孤立开,避免混用。这个说简单也简单,毕竟upload.lua中已经把数据读取分离出来了,而ngx.req.read_body中只需要使用ngx.req.get_body_data就可以了;但说难也难,因为这改动很有可能把这个库文件的接口改掉,又需要改上层的业务逻辑。

问题解决

梳理了一下思路,发现获取数据的这个函数只要在Content-Typeform-urlencoded时调用,为multipart/form-data时不调用就可以避免冲突了,这么一看,还是改上层业务逻辑比较方便些。

看了下上传逻辑中,发现是由一个__用户上传的参数__确定是否调用读取数据的函数,这就出现个比较egg pain的问题:如果业务方调用我们接口时,是按照文档约定,也就是使用不同的上传方式就传入对应的参数的话,就没有问题;但如果没传参数,就有可能会出现混用的情况了!

最后想了想,在args.lua里面使用个局部变量进行上传方式的标识,在获取用于验证和处理图片的参数时,用于修改用户上传的这个参数。这样,上传类型和对应参数就可以准确对应上,混合调用的情况就可以避免了~

后记

对于提供服务接口的参数设计,还是需要谨慎一些,而这种历史遗留问题,又很难做大的改动。如果有更好的解决思路或想法,欢迎交流~