使用 ngx_lua (openresty)正确读取 HTTP 请求 body

之前用 ngx_lua(openresty) 写了一个处理 HTTP 请求的程序,今天发现当发送的 HTTP 请求 body 很大的时候,发现老是报错,最后定位到 ngx.req.get_body_data() 这个函数返回 nil ,而不是真正的 body。

于是我去 ngx_lua 的文档看这个函数的文档,发现文档中说有三种情况下,这个函数的返回值会是 nil:

This function returns nil if

  1. the request body has not been read,
  2. the request body has been read into disk temporary files,
  3. or the request body has zero size.

第一条,我是读了文档的,知道要 Nginx 默认是不会读 body 的,要么打开读 body 的开关,要么显示调用一下 read。所以代码中已经调用了 ngx.req.read_body() 了,不会是这个原因。

第三条,也不可能,我通过 curl 发送的,body 肯定是发出去了(可以抓包验证)。

那么基本上就确定是这个 request body 被读到硬盘的临时文件里了。看到这里我猜应该是 Nginx 是将大的 HTTP 请求放到磁盘中而不是放到内存中。搜了一下文档,发现有这个参数:

当请求体的大小大禹 client_body_buffer_size 的时候,Nginx 将会把它存到一个临时文件中,而不是放到内存中。这个值的大小默认是内存页的两倍:32 位系统上是 8k,64位系统上是16k。

OK,问题找到了,现在解决方案有两个:1)调大这个值,我觉得是不合理的,这样会浪费内存。2)可以想办法读到临时文件中的大 body。ngx_lua 提供了配套的方法 ngx_req.get_body_file,注意这只是获得文件,还要在 lua 代码中打开读取文件。

所以最后,处理一个请求且还能正确处理很大 body 的请求的代码是这样的,其中高亮的部分,是核心的逻辑,先尝试从内存中读 body,如果读不到,就去临时文件中读。

我读这个实际的应用场景是,读出 body,按照 json 解析出来当做路由规则(需求是需要动态设置 HTTP 的路由规则,所以我用 ngx_lua 在 nginx 上新开了个端口监控这样的规则)。现在用很大的规则一测试,发现性能下降很厉害。存到文件、再读出文件是一方面。另一方面是 ngx_lua 是为每一个 HTTP 请求开一个 lua 协程处理,不能共享变量,只能通过 lua_shared_dict 来保存持久的变量。但是 lua_shared_dict 的问题是,这个 dict 只支持 “Lua booleans, numbers, strings, or nil”,解析出来的 json 是一个 lua 的 table,不能保存到 lua_shared_dict 里面,我只好保存一个字符串,然后对每一个 HTTP 请求 json decode 这个字符串了。不知道有没有更好的方法。

使用 ngx_lua (openresty)正确读取 HTTP 请求 body”已经有2条评论

    • 动态设置倒不是高频操作,最关键的问题是,不能存下来这个 table,只能保存字符串,对于每一个正常的 HTTP 请求都将 json 字符串解析成 table,这里的开销很大。

      原先的需求就是动态设置 Nginx 的 proxy_pass 规则,做灰度引流和蓝绿发布之类的。

Leave a comment

电子邮件地址不会被公开。 必填项已用*标注