Skip to content

Commit

Permalink
feat(utils) new get_body_info() public API (Kong#2822)
Browse files Browse the repository at this point in the history
New public API `get_body_info()` to retrieve:
- parsed body args (similar to `get_body_args()` (table)
- error code due to potential unknown request MIME type
- raw body (string)
- the request MIME type (as an enum value for programmable usage)

New public API enum values:
- `req_mime_types`: table containing known request MIME types
- `req_body_errors`: table containing enum of req body parsing errors

The previous `get_body_args()` API is left untouched from a user's POV
to preserve backwards compatibility: it still return only one argument
which is the parsed body, or an empty table.

The implementation tries to be efficient and lazily reads the request
body only when necessary.
  • Loading branch information
thibaultcha authored Sep 1, 2017
1 parent caa91e1 commit 0a2d7c5
Showing 1 changed file with 102 additions and 19 deletions.
121 changes: 102 additions & 19 deletions kong/tools/public.lua
Original file line number Diff line number Diff line change
Expand Up @@ -12,62 +12,145 @@ do


local str_find = string.find
local str_format = string.format
local ngx_req_get_post_args = ngx.req.get_post_args
local ngx_req_get_body_data = ngx.req.get_body_data


local content_type_map = {
[1] = function(content_type) -- multipart
return multipart(ngx_req_get_body_data(), content_type):get_all()
local MIME_TYPES = {
form_url_encoded = 1,
json = 2,
xml = 3,
multipart = 4,
text = 5,
html = 6,
}


local ERRORS = {
no_ct = 1,
[1] = "don't know how to parse request body (no Content-Type)",
unknown_ct = 2,
[2] = "don't know how to parse request body (" ..
"unknown Content-Type '%s')",
unsupported_ct = 3,
[3] = "don't know how to parse request body (" ..
"can't decode Content-Type '%s')",
}


_M.req_mime_types = MIME_TYPES
_M.req_body_errors = ERRORS


local MIME_DECODERS = {
[MIME_TYPES.multipart] = function(content_type)
local raw_body = ngx_req_get_body_data()
local args = multipart(raw_body, content_type):get_all()

return args, raw_body
end,

[2] = function() -- json
local body, err = cjson.decode(ngx_req_get_body_data())
[MIME_TYPES.json] = function()
local raw_body = ngx_req_get_body_data()
local args, err = cjson.decode(raw_body)
if err then
ngx_log(ERR, "could not decode JSON body args: ", err)
return {}
return {}, raw_body
end

return body
return args, raw_body
end,

[3] = function() -- encoded form
[MIME_TYPES.form_url_encoded] = function()
local ok, res, err = pcall(ngx_req_get_post_args)
if not ok or err then
local msg = res and res or err
ngx_log(ERR, "could not get body args: ", msg)
return {}
end

-- don't read raw_body if not necessary
-- if we called get_body_args(), we only want the parsed body
return res
end,
}

function _M.get_body_args()

local function get_body_info()
local content_type = ngx.var.http_content_type

if not content_type or content_type == "" then
return {}
ngx_log(ERR, ERRORS[ERRORS.no_ct])

return {}, ERRORS.no_ct
end

local map_type
local req_mime

if str_find(content_type, "multipart/form-data", nil, true) then
map_type = 1
req_mime = MIME_TYPES.multipart

elseif str_find(content_type, "application/json", nil, true) then
map_type = 2
req_mime = MIME_TYPES.json

elseif str_find(content_type, "application/www-form-urlencoded", nil, true) or
str_find(content_type, "application/x-www-form-urlencoded", nil, true) then
map_type = 3
str_find(content_type, "application/x-www-form-urlencoded", nil, true)
then
req_mime = MIME_TYPES.form_url_encoded

elseif str_find(content_type, "text/plain", nil, true) then
req_mime = MIME_TYPES.text

elseif str_find(content_type, "text/html", nil, true) then
req_mime = MIME_TYPES.html

elseif str_find(content_type, "application/xml", nil, true) or
str_find(content_type, "text/xml", nil, true) or
str_find(content_type, "application/soap+xml", nil, true)
then
-- considering SOAP 1.1 (text/xml) and SOAP 1.2 (application/soap+xml)
-- as XML only for now.
req_mime = MIME_TYPES.xml
end

if not req_mime then
-- unknown Content-Type
ngx_log(ERR, str_format(ERRORS[ERRORS.unsupported_ct], content_type))

return {}, ERRORS.unknown_ct
end

if not MIME_DECODERS[req_mime] then
-- known Content-Type, but cannot decode
ngx_log(ERR, str_format(ERRORS[ERRORS.unsupported_ct], content_type))

return {}, ERRORS.unsupported_ct, nil, req_mime
end

-- decoded Content-Type
local args, raw_body = MIME_DECODERS[req_mime](content_type)

return args, nil, raw_body, req_mime
end


function _M.get_body_args()
-- only return args
return (get_body_info())
end


else
ngx_log(ERR, "don't know how to parse request body of Content-Type: '", content_type, "'")
return {}
function _M.get_body_info()
local args, err_code, raw_body, req_mime = get_body_info()
if not raw_body then
-- if our body was form-urlencoded and read via ngx.req.get_post_args()
-- we need to retrieve the raw body because it was not retrieved by the
-- decoder
raw_body = ngx_req_get_body_data()
end

return content_type_map[map_type](content_type)
return args, err_code, raw_body, req_mime
end
end

Expand Down

0 comments on commit 0a2d7c5

Please sign in to comment.