龙空技术网

Nginx借助lua-resty-upload库获取Post(form-data)的请求参数

SapphireCoder 208

前言:

眼前姐妹们对“nginxpost文件上传”大体比较注重,各位老铁们都想要知道一些“nginxpost文件上传”的相关内容。那么小编也在网上搜集了一些关于“nginxpost文件上传””的相关知识,希望咱们能喜欢,各位老铁们快快来学习一下吧!

导读:本文将讨论如何实现 Nginx 接收 Post 请求(数据格式为 form-data),并将 RequestBody 按照规定的 format 格式写入到 Nginx 的日志中。下面将分为以下几点展开讨论:

Post 请求中 form-data 和 x-www-form 格式的区别Lua 在 Nginx 中的应用及 lua-resty-upload 库Lua 脚本具体实现配置 Nginx 日志格式配置 Nginx Server用 POSTMAN 模拟请求并观察日志输出Post 请求中 form-data 和 x-www-form 格式的区别

下面这篇文章很详细地描述了两者数据格式间的区别.

这里我们重点关注 form-data 数据格式。form-data 是一种重视数据的方式,通常我们在 value 值中会发送大量的文本信息或者直接传送一个文件,数据直接编码为二进制发送,不会产生多余的字节,比较适合大文本的传输。下面是一个典型的 form-data 数据格式:

POST /users/ HTTP/1.1Host: localhost:8000Content-Type: multipart/form-data; boundary=----WebKitFormBoundary7MA4YWxkTrZu0gW------WebKitFormBoundary7MA4YWxkTrZu0gW--,Content-Disposition: form-data; name="country"中国------WebKitFormBoundary7MA4YWxkTrZu0gW--Content-Disposition: form-data; name="city"北京------WebKitFormBoundary7MA4YWxkTrZu0gW--

解析:在header头信息中,Content-Type: multipart/form-data; boundary=----WebKitFormBoundary7MA4YWxkTrZu0gW 分别指定了格式和 boundary(分割字符串),在body中使用了这个 boundary 指定的字符串作为分割,从而可以轻易地还原为key:value的形式。

Lua 在 Nginx 中的应用及 lua-resty-upload 库

为了灵活实现上述场景,这里我使用 OpenResty 提供的 lua-nginx-module 方案实现 Nginx Lua 扩展。关于 Lua 在 Nginx 中的应用在下面的文章中已详细描述,这里笔者就不再赘述。

获取 Post 请求采用默认的 “x-www-form-urlencoded” 数据格式的请求参数比较简单,我们只需要通过以下代码即可实现:

local args, err = ngx.req.get_post_args()

而要实现对于 "multipart/form-data" 格式的 POST 参数获取,我们需借助 lua-resty-upload 库。以下是该 Lua 库的 github 地址以及实现的理念。

Lua 脚本具体实现

该 Lua 脚本主要由三个方法组成:

split() 方法用于切割字符串post_form_data() 核心方法,将数据处理成键值对存放到 Lua Table 中 table2json() 将 Lua Table 转换为 json 字符串形式

package.path  = '/usr/local/nginx/conf/?.lua;;' .. package.pathlocal args = {}local upload = require "resty.upload";local cjson = require "cjson"local chunk_size = 4096local form, err = upload:new(chunk_size)function split(s, delim)    if type(delim) ~= "string" or string.len(delim) <= 0 then        return nil    end     local start = 1    local t = {}    while true do        local pos = string.find (s, delim, start, true)                if not pos then            break        end         table.insert (t, string.sub (s, start, pos - 1))        start = pos + string.len (delim)    end    table.insert (t, string.sub (s, start))     return tendfunction post_form_data(form,err)  if not form then    ngx.say(ngx.ERR, "failed to new upload: ", err)    ngx.exit(500)  end  form:set_timeout(1000)  local paramTable = {["s"]=1}  local tempkey = ""  while true do    local typ, res, err = form:read()    if not typ then        ngx.say("failed to read: ", err)        return {}    end    local key = ""    local value = ""    if typ == "header" then    	local key_res = split(res[2],";")   	key_res = key_res[2]    	key_res = split(key_res,"=")    	key = (string.gsub(key_res[2],"\"",""))    	paramTable[key] = ""    	tempkey = key    end	    if typ == "body" then    	value = res    	if paramTable.s ~= nil then paramTable.s = nil end    	paramTable[tempkey] = value    end    if typ == "eof" then        break    end  end  return paramTable endargs = post_form_data(form,err)		function table2json(t)        local function serialize(tbl)                local tmp = {}                for k, v in pairs(tbl) do                        local k_type = type(k)                        local v_type = type(v)                        local key = (k_type == "string" and "\"" .. k .. "\":")                            or (k_type == "number" and "")                        local value = (v_type == "table" and serialize(v))                            or (v_type == "boolean" and tostring(v))                            or (v_type == "string" and "\"" .. v .. "\"")                            or (v_type == "number" and v)                        tmp[#tmp + 1] = key and value and tostring(key) .. tostring(value) or nil                end                if table.maxn(tbl) == 0 then                        return "{" .. table.concat(tmp, ",") .. "}"                else                        return "[" .. table.concat(tmp, ",") .. "]"                end        end        assert(type(t) == "table")        return serialize(t)endngx.var.request_body_data = table2json(args);ngx.say('{"code":0,"message":""}');
配置 Nginx 日志格式

这里按实际需求定义了一个 Nginx 日志 Format(这里有一个细节,由于 $request_body 是默认变量,所以笔者将自己处理完的请求体内容存于 $request_body_data 变量中)。

http {   ... 省略其他内容  log_format  yw_log        escape=json '{'                                                                '"timestamp":"$time_iso8601",'                                                                '"host":"$host",'                                                                '"remote_addr":"$remote_addr",'                                                                '"request_method":"$request_method",'                                                                '"request_uri":"$request_uri",'                                                                '"request_status":"$status",'                                                                '"request_length":$request_length,'                                                                '"request_time":$request_time,'                                                                '"request_body":"$request_body_data"'                                                                '}';}
配置 Nginx Server

配置一个 uri,并指定我们编写好的 Lua 脚本运行的时机,最后指定日志输出的位置。

     location ~ ^/api/yw/(\w+) {            # lua_need_request_body on;            set $request_body_data '';            content_by_lua_file conf/lua-script/mulformData.lua;            set  $log_name "$1";            access_log  /data/logs/nginx/${log_name}.log  yw_log;        }

这里笔者踩了一个坑,就是被注释掉的这句 “lua_need_request_body on” 。 假设开启的话,那么当我们编写的 mulformData.lua 脚本执行到 upload:new(chunk_size) 这句代码时就会出现如下错误:

Failed to new upload: request body already exists

因为开启 lua_need_request_body 会导致你的 Lua 代码被执行前,请求体就被 ngx_lua 自动读取完毕了,所以报了 request body already exists。解决方案则是将其注释即可,默认 off。

用 POSTMAN 模拟 Post form-data 请求

我们查看 Nginx 日志结果输出,自此我们便成功的得到了请求参数,并按我们想要的格式写入到 Nginx 的日志中。

最后

以上就是关于笔者实现 Nginx 借助 lua-resty-upload 库获取 Post(form-data) 的请求参数并按指定格式写入到 Nginx 日志中的实践,分享出来希望对各位有所帮助。

感谢您的阅读,如果喜欢本文欢迎关注和转发,转载需注明出处,本头条号将持续分享IT技术知识。对于文章内容有其他想法或意见建议等,欢迎提出共同讨论共同进步。

参考文章

标签: #nginxpost文件上传