Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

feat: body-transformer plugin enhancement(#10472) #10496

Merged
merged 4 commits into from
Nov 27, 2023
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
34 changes: 26 additions & 8 deletions apisix/plugins/body-transformer.lua
Original file line number Diff line number Diff line change
Expand Up @@ -21,7 +21,10 @@ local template = require("resty.template")
local ngx = ngx
local decode_base64 = ngx.decode_base64
local req_set_body_data = ngx.req.set_body_data
local req_get_uri_args = ngx.req.get_uri_args
local str_format = string.format
local decode_args = ngx.decode_args
local str_find = core.string.find
local type = type
local pcall = pcall
local pairs = pairs
Expand All @@ -31,8 +34,9 @@ local next = next
local transform_schema = {
type = "object",
properties = {
input_format = { type = "string", enum = {"xml", "json"} },
input_format = { type = "string", enum = {"xml", "json", "encoded", "args"} },
template = { type = "string" },
template_is_base64 = { type = "boolean" },
},
required = {"template"},
}
Expand Down Expand Up @@ -108,6 +112,12 @@ local decoders = {
json = function(data)
return core.json.decode(data)
end,
encoded = function(data)
return decode_args(data)
end,
args = function()
return req_get_uri_args()
end,
}


Expand All @@ -116,11 +126,11 @@ function _M.check_schema(conf)
end


local function transform(conf, body, typ, ctx)
local function transform(conf, body, typ, ctx, request_method)
local out = {}
if body then
local format = conf[typ].input_format
if body or request_method == "GET" then
local err
local format = conf[typ].input_format
if format then
out, err = decoders[format](body)
if not out then
Expand All @@ -134,7 +144,9 @@ local function transform(conf, body, typ, ctx)
end

local text = conf[typ].template
text = decode_base64(text) or text
if (conf[typ].template_is_base64 or (format and format ~= "encoded" and format ~= "args")) then
text = decode_base64(text) or text
end
local ok, render = pcall(template.compile, text)
if not ok then
local err = render
Expand All @@ -159,24 +171,30 @@ local function transform(conf, body, typ, ctx)
end


local function set_input_format(conf, typ, ct)
local function set_input_format(conf, typ, ct, method)
if method == "GET" then
conf[typ].input_format = "args"
end
if conf[typ].input_format == nil and ct then
if ct:find("text/xml") then
conf[typ].input_format = "xml"
elseif ct:find("application/json") then
conf[typ].input_format = "json"
elseif str_find(ct:lower(), "application/x-www-form-urlencoded", nil, true) then
yongxiaodong marked this conversation as resolved.
Show resolved Hide resolved
conf[typ].input_format = "encoded"
end
end
end


function _M.rewrite(conf, ctx)
if conf.request then
local request_method = ngx.var.request_method
conf = core.table.deepcopy(conf)
ctx.body_transformer_conf = conf
local body = core.request.get_body()
set_input_format(conf, "request", ctx.var.http_content_type)
local out, status, err = transform(conf, body, "request", ctx)
set_input_format(conf, "request", ctx.var.http_content_type, request_method)
local out, status, err = transform(conf, body, "request", ctx, request_method)
if not out then
return status, { message = err }
end
Expand Down
175 changes: 175 additions & 0 deletions t/plugin/body-transformer.t
Original file line number Diff line number Diff line change
Expand Up @@ -894,3 +894,178 @@ location /demo {
assert(core.json.stably_encode(data1) == core.json.stably_encode(data2))
}
}



=== TEST 13: test x-www-form-urlencoded to JSON
--- config
location /demo {
content_by_lua_block {
local core = require("apisix.core")
local body = core.request.get_body()
local data = core.json.decode(body)
assert(data.foo == "hello world" and data.bar == 30)
}
}
location /t {
content_by_lua_block {
local t = require("lib.test_admin")
local core = require("apisix.core")
local req_template = [[{"foo":"{{name .. " world"}}","bar":{{age+10}}}]]
local code, body = t.test('/apisix/admin/routes/1',
ngx.HTTP_PUT,
string.format([[{
"uri": "/foobar",
"plugins": {
"proxy-rewrite": {
"uri": "/demo"
},
"body-transformer": {
"request": {
"template": "%s"
}
}
},
"upstream": {
"type": "roundrobin",
"nodes": {
"127.0.0.1:%d": 1
}
}
}]], req_template:gsub('"', '\\"'), ngx.var.server_port)
)

if code >= 300 then
ngx.status = code
return
end
ngx.sleep(0.5)

local core = require("apisix.core")
local http = require("resty.http")
local uri = "http://127.0.0.1:" .. ngx.var.server_port .. "/foobar"
local data = {name = "hello", age = 20}
local body = ngx.encode_args(data)
local opt = {method = "POST", body = body, headers = {["Content-Type"] = "application/x-www-form-urlencoded"}}
shreemaan-abhishek marked this conversation as resolved.
Show resolved Hide resolved
local httpc = http.new()
local res = httpc:request_uri(uri, opt)
assert(res.status == 200)
}
}



=== TEST 14: test get request to JSON
--- config
location /demo {
content_by_lua_block {
local core = require("apisix.core")
local body = core.request.get_body()
local data = core.json.decode(body)
assert(data.foo == "hello world" and data.bar == 30)
}
}
location /t {
content_by_lua_block {
local t = require("lib.test_admin")
local core = require("apisix.core")
local req_template = [[{"foo":"{{name .. " world"}}","bar":{{age+10}}}]]

local code, body = t.test('/apisix/admin/routes/1',
ngx.HTTP_PUT,
string.format([[{
"uri": "/foobar",
"plugins": {
"proxy-rewrite": {
"uri": "/demo"
},
"body-transformer": {
"request": {
"template": "%s"
}
}
},
"upstream": {
"type": "roundrobin",
"nodes": {
"127.0.0.1:%d": 1
}
}
}]], req_template:gsub('"', '\\"'), ngx.var.server_port)
)

if code >= 300 then
ngx.status = code
return
end
ngx.sleep(0.5)

local core = require("apisix.core")
local http = require("resty.http")
local uri = "http://127.0.0.1:" .. ngx.var.server_port .. "/foobar" .. "?name=hello&age=20"
local opt = {method = "GET"}
local httpc = http.new()
local res = httpc:request_uri(uri, opt)
assert(res.status == 200)
}
}



=== TEST 15: test input is in base64-encoded urlencoded format
--- config
location /demo {
content_by_lua_block {
local core = require("apisix.core")
local body = core.request.get_body()
local data = ngx.decode_args(body)
assert(data.foo == "hello world" and data.bar == "30")
}
}
location /t {
content_by_lua_block {
local t = require("lib.test_admin")
local core = require("apisix.core")
local req_template = ngx.encode_base64[[foo={{name .. " world"}}&bar={{age+10}}]]

local code, body = t.test('/apisix/admin/routes/1',
ngx.HTTP_PUT,
string.format([[{
"uri": "/foobar",
"plugins": {
"proxy-rewrite": {
"uri": "/demo"
},
"body-transformer": {
"request": {
"template_is_base64": true,
"template": "%s"
}
}
},
"upstream": {
"type": "roundrobin",
"nodes": {
"127.0.0.1:%d": 1
}
}
}]], req_template:gsub('"', '\\"'), ngx.var.server_port)
)

if code >= 300 then
ngx.status = code
return
end
ngx.sleep(0.5)

local core = require("apisix.core")
local http = require("resty.http")
local uri = "http://127.0.0.1:" .. ngx.var.server_port .. "/foobar"
local data = {name = "hello", age = 20}
local body = ngx.encode_args(data)
local opt = {method = "POST", body = body, headers = {["Content-Type"] = "application/x-www-form-urlencoded"}}
local httpc = http.new()
local res = httpc:request_uri(uri, opt)
assert(res.status == 200)
}
}
Loading