其中包括解析http 报文参数,计算上传文件md5,并解决了当请求body 大于client_body_buffer_size导致ngx.req.get_post_args()无法获取到参数的问题。
https://github.com/openresty/lua-nginx-module#ngxreqget_post_args
问题:request body 大于client_body_buffer_size,导致ngx.req.get_post_args()无法获取到参数。
原因分析:当post请求body size大于client_body_buffer_size 默认值8k或16k时,请求报文将会被nginx缓存到硬盘,此时ngx.req.get_post_args()无法获取到参数,此时post参数需要从ngx.req.get_body_data() 或者ngx.req.get_body_file()中获取,获取后的参数是进过unicode编码过的,我们如果要取得原始的值,还需要进行unicode解码。
解决方法:
1.修改nginx client_body_buffer_size 128k,或者更大。
2.当ngx.req.get_post_args()无法获取到参数时,从ngx.req.get_body_data() 或者ngx.req.get_body_file()中手动解析参数。
以下是核心代码
local function init_form_args() -- 返回args 和 文件md5 local args = {} local file_args = {} local is_have_file_param = false local receive_headers = ngx.req.get_headers() local request_method = ngx.var.request_method local error_code = 0 local error_msg = "未初始化" local file_md5 = "" if "GET" == request_method then -- 普通get请求 args = ngx.req.get_uri_args() elseif "POST" == request_method then ngx.req.read_body() if string.sub(receive_headers["content-type"],1,20) == "multipart/form-data;" then--判断是否是multipart/form-data类型的表单 is_have_file_param = true local body_data = ngx.req.get_body_data()--body_data可是符合http协议的请求体,不是普通的字符串 --请求体的size大于nginx配置里的client_body_buffer_size,则会导致请求体被缓冲到磁盘临时文件里,client_body_buffer_size默认是8k或者16k if not body_data then local datafile = ngx.req.get_body_file() if not datafile then error_code = 1 error_msg = "no request body found" else local fh, err = io.open(datafile, "r") if not fh then error_code = 2 error_msg = "failed to open " .. tostring(datafile) .. "for reading: " .. tostring(err) else fh:seek("set") body_data = fh:read("*a") fh:close() if body_data == "" then error_code = 3 error_msg = "request body is empty" end end end end local new_body_data = {} --确保取到请求体的数据 if error_code == 0 then local boundary = get_boundary(body_data) -- 兼容处理:当content-type中取不到boundary时,直接从body首行提取。 local body_data_table = explode(tostring(body_data), boundary) local first_string = table.remove(body_data_table,1) local last_string = table.remove(body_data_table) -- ngx.log(ngx.ERR, ">>>>>>>>>>>>>>>>>>>>>start\n", table.concat(body_data_table,"<<<<<<>>>>>>"), ">>>>>>>>>>>>>>>>>>>>>end\n") for i,v in ipairs(body_data_table) do local start_pos,end_pos,capture,capture2 = string.find(v,'Content%-Disposition: form%-data; name="(.+)"; filename="(.*)"') if not start_pos then --普通参数 local t = explode(v,"\r\n\r\n") --[[ 按照双换行切分后得到的table 第一个元素,t[1]=' Content-Disposition: form-data; name="_data_" Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit ' 第二个元素,t[2]='{"fileName":"redis-use.png","bizCode":"1099","description":"redis使用范例.png","type":"application/octet-stream"}' 从第一个元素中提取到参数名称,第二个元素就是参数的值。 ]] local param_name_start_pos, param_name_end_pos, temp_param_name = string.find(t[1],'Content%-Disposition: form%-data; name="(.+)"') local temp_param_value = string.sub(t[2],1,-3) args[temp_param_name] = temp_param_value end end -- 找到第一行\r\n\r\n进行切割,将Contentype-*的内容去掉 local file_pos_start, file_pos_end = string.find(last_string, "\r\n\r\n") local temps_table = explode(boundary, "\r\n") -- local boundary_end = temps_table[1] .. "--" local boundary_end = temps_table[1] -- ngx.log(ngx.ERR, "boundary_end:", boundary_end) -- ngx.log(ngx.ERR, "文件报文原文:", last_string) local last_boundary_pos_start, last_boundary_pos_end = string.find(last_string, boundary_end) -- 减去3是为了:\r\n两个字符,加上sub对end标记的位置是闭合的截取策略,所以还要减去1。 local file_string = string.sub(last_string, file_pos_end+1, last_boundary_pos_start - 3) -- 去掉最后的换行符 -- file_string file_md5 = ngx.md5(file_string) -- ngx.log(ngx.ERR, "文件报文原文:", file_string) -- ngx.log(ngx.ERR, "\nfile_real_md5:", real_md5) -- local lua_md5 = md5.sumhexa(file_string) -- ngx.log(ngx.ERR, "字符串长度:", #file_string) -- 调试代码,当计算MD5不一致时,直接读取文件,计算长度. --[[ local file = io.open("/var/tmp/readme.txt","r") local string_source = file:read("*a") file:close() local red_md5 = md5.sumhexa(string_source) ngx.log(ngx.ERR, "直接读取文件原文:",string_source) ngx.log(ngx.ERR, "字符串长度:", #string_source) ngx.log(ngx.ERR, "直接读取文件计算md5:", red_md5) ]] end else -- 普通post请求 args = ngx.req.get_post_args() --[[ 请求体的size大于nginx配置里的client_body_buffer_size,则会导致请求体被缓冲到磁盘临时文件里 此时,get_post_args 无法获取参数时,需要从缓冲区文件中读取http报文。http报文遵循param1=value1¶m2=value2的格式。 ]] if not args then args = {} -- body_data可是符合http协议的请求体,不是普通的字符串 local body_data = ngx.req.get_body_data() -- client_body_buffer_size默认是8k或者16k if not body_data then local datafile = ngx.req.get_body_file() if not datafile then error_code = 1 error_msg = "no request body found" else local fh, err = io.open(datafile, "r") if not fh then error_code = 2 error_msg = "failed to open " .. tostring(datafile) .. "for reading: " .. tostring(err) else fh:seek("set") body_data = fh:read("*a") fh:close() if body_data == "" then error_code = 3 error_msg = "request body is empty" end end end end -- 解析body_data local post_param_table = explode(tostring(body_data), "&") for i,v in ipairs(post_param_table) do local paramEntity = explode(v,"=") local tempValue = paramEntity[2] -- 对请求参数的value进行unicode解码 tempValue = unescape(tempValue) args[paramEntity[1]] = tempValue end end end end return args, file_md5; end
评论前必须登录!
注册