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 2 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 = 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)
yongxiaodong marked this conversation as resolved.
Show resolved Hide resolved
end,
args = function()
return req_get_uri_args()
yongxiaodong marked this conversation as resolved.
Show resolved Hide resolved
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: The template uses x-www-form-urlencoded format
Copy link
Contributor

@monkeyDluffy6017 monkeyDluffy6017 Nov 21, 2023

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Wrong title? test template_is_base64 here? what's the purpose of this test case?

--- 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)
yongxiaodong marked this conversation as resolved.
Show resolved Hide resolved
}
}
Loading