# # Licensed to the Apache Software Foundation (ASF) under one or more # contributor license agreements. See the NOTICE file distributed with # this work for additional information regarding copyright ownership. # The ASF licenses this file to You under the Apache License, Version 2.0 # (the "License"); you may not use this file except in compliance with # the License. You may obtain a copy of the License at # # http://www.apache.org/licenses/LICENSE-2.0 # # Unless required by applicable law or agreed to in writing, software # distributed under the License is distributed on an "AS IS" BASIS, # WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. # See the License for the specific language governing permissions and # limitations under the License. # use t::APISIX 'no_plan'; no_long_string(); no_shuffle(); no_root_location(); add_block_preprocessor(sub { my ($block) = @_; if (!$block->request) { $block->set_value("request", "GET /t"); } }); run_tests; __DATA__ === TEST 1: simulate simple SOAP proxy --- config location /demo { content_by_lua_block { local core = require("apisix.core") local body = core.request.get_body() local xml2lua = require("xml2lua") local xmlhandler = require("xmlhandler.tree") local handler = xmlhandler:new() local parser = xml2lua.parser(handler) parser:parse(body) ngx.print(string.format([[ %s 46704314 Madrid EUR ]], handler.root["soap-env:Envelope"]["soap-env:Body"]["ns0:getCountryRequest"]["ns0:name"])) } } location /t { content_by_lua_block { local t = require("lib.test_admin") local req_template = ngx.encode_base64[[ {{_escape_xml(name)}} ]] local rsp_template = ngx.encode_base64[[ {% if Envelope.Body.Fault == nil then %} { "status":"{{_ctx.var.status}}", "currency":"{{Envelope.Body.getCountryResponse.country.currency}}", "population":{{Envelope.Body.getCountryResponse.country.population}}, "capital":"{{Envelope.Body.getCountryResponse.country.capital}}", "name":"{{Envelope.Body.getCountryResponse.country.name}}" } {% else %} { "message":{*_escape_json(Envelope.Body.Fault.faultstring[1])*}, "code":"{{Envelope.Body.Fault.faultcode}}" {% if Envelope.Body.Fault.faultactor ~= nil then %} , "actor":"{{Envelope.Body.Fault.faultactor}}" {% end %} } {% end %} ]] local code, body = t.test('/apisix/admin/routes/1', ngx.HTTP_PUT, string.format([[{ "uri": "/ws", "plugins": { "proxy-rewrite": { "uri": "/demo" }, "body-transformer": { "request": { "template": "%s" }, "response": { "input_format": "xml", "template": "%s" } } }, "upstream": { "type": "roundrobin", "nodes": { "127.0.0.1:%d": 1 } } }]], req_template, rsp_template, 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 .. "/ws" local body = [[{"name": "Spain"}]] local opt = {method = "POST", body = body, headers = {["Content-Type"] = "application/json"}} local httpc = http.new() local res = httpc:request_uri(uri, opt) assert(res.status == 200) local data1 = core.json.decode(res.body) local data2 = core.json.decode[[{"status":"200","currency":"EUR","population":46704314,"capital":"Madrid","name":"Spain"}]] assert(core.json.stably_encode(data1) == core.json.stably_encode(data2)) } } === TEST 2: test JSON-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 body = [[{"name":"hello","age":20}]] local opt = {method = "POST", body = body, headers = {["Content-Type"] = "application/json"}} local httpc = http.new() local res = httpc:request_uri(uri, opt) assert(res.status == 200) } } === TEST 3: specify wrong input_format --- config 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": { "input_format": "xml", "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 body = [[{"name":"hello","age":20}]] local opt = {method = "POST", body = body, headers = {["Content-Type"] = "application/json"}} local httpc = http.new() local res = httpc:request_uri(uri, opt) assert(res.status == 400) } } --- error_log Error Parsing XML === TEST 4: invalid reference in template --- config 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 body = [[{"name":"hello","age":20}]] local opt = {method = "POST", body = body, headers = {["Content-Type"] = "application/json"}} local httpc = http.new() local res = httpc:request_uri(uri, opt) assert(res.status == 503) } } --- grep_error_log eval qr/transform\(\): request template rendering:.*/ --- grep_error_log_out eval qr/attempt to call global 'name' \(a string value\)/ === TEST 5: generate request body from scratch --- 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") } } location /t { content_by_lua_block { local t = require("lib.test_admin") local core = require("apisix.core") local req_template = [[{ "foo":"{{_ctx.var.arg_name .. " world"}}" }]] local code, body = t.test('/apisix/admin/routes/1', ngx.HTTP_PUT, string.format([[{ "uri": "/foobar", "plugins": { "proxy-rewrite": { "uri": "/demo", "method": "POST" }, "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" local opt = {method = "GET"} local httpc = http.new() local res = httpc:request_uri(uri, opt) assert(res.status == 200) } } === TEST 6: html escape in template --- config location /demo { content_by_lua_block { local core = require("apisix.core") local body = core.request.get_body() local data = core.json.decode(body) if (data == nil) or (data.agent:find("ngx_lua/", 0, true) == nil) then return ngx.exit(400) end } } location /t { content_by_lua_block { local t = require("lib.test_admin") local core = require("apisix.core") -- html escape would escape '/' to '/' in string, which may be unexpected. -- 'lua-resty-http/0.16.1 (Lua) ngx_lua/10021' -- would be escaped into -- 'lua-resty-http/0.16.1 (Lua) ngx_lua/10021' local req_template = [[{ "agent":"{{_ctx.var.http_user_agent}}" }]] local admin_body = [[{ "uri": "/foobar", "plugins": { "proxy-rewrite": { "uri": "/demo", "method": "POST" }, "body-transformer": { "request": { "template": "%s" } } }, "upstream": { "type": "roundrobin", "nodes": { "127.0.0.1:%d": 1 } } }]] local code, body = t.test('/apisix/admin/routes/1', ngx.HTTP_PUT, string.format(admin_body, 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" local opt = {method = "GET"} local httpc = http.new() local res = httpc:request_uri(uri, opt) assert(res.status == 400) -- disable html escape, now it's ok local req_template = [[{"agent":"{*_ctx.var.http_user_agent*}"}]] local code, body = t.test('/apisix/admin/routes/1', ngx.HTTP_PUT, string.format(admin_body, req_template:gsub('"', '\\"'), ngx.var.server_port) ) if code >= 300 then ngx.status = code return end ngx.sleep(0.5) local res = httpc:request_uri(uri, opt) assert(res.status == 200) } } === TEST 7: parse body in yaml format --- config location /demo { content_by_lua_block { local core = require("apisix.core") local body = core.request.get_body() local data = core.json.decode(body) if data == nil or data.foobar ~= "hello world" then return ngx.exit(400) end } } location /t { content_by_lua_block { local t = require("lib.test_admin") local core = require("apisix.core") local req_template = [[ {% local yaml = require("lyaml") local body = yaml.load(_body) %} {"foobar":"{{body.foobar.foo .. " " .. body.foobar.bar}}"} ]] local admin_body = [[{ "uri": "/foobar", "plugins": { "proxy-rewrite": { "uri": "/demo", "method": "POST" }, "body-transformer": { "request": { "template": "%s" } } }, "upstream": { "type": "roundrobin", "nodes": { "127.0.0.1:%d": 1 } } }]] local code, body = t.test('/apisix/admin/routes/1', ngx.HTTP_PUT, string.format(admin_body, 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 body = [[ foobar: foo: hello bar: world ]] local uri = "http://127.0.0.1:" .. ngx.var.server_port .. "/foobar" local opt = {method = "POST", body = body} local httpc = http.new() local res = httpc:request_uri(uri, opt) assert(res.status == 200) } } === TEST 8: test _escape_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) if data == nil or data.foobar ~= [[hello "world"]] then return ngx.exit(400) end } } location /t { content_by_lua_block { local t = require("lib.test_admin") local core = require("apisix.core") local req_template = [[{"foobar":{*_escape_json(name)*}}]] local admin_body = [[{ "uri": "/foobar", "plugins": { "proxy-rewrite": { "uri": "/demo", "method": "POST" }, "body-transformer": { "request": { "input_format": "json", "template": "%s" } } }, "upstream": { "type": "roundrobin", "nodes": { "127.0.0.1:%d": 1 } } }]] local code, body = t.test('/apisix/admin/routes/1', ngx.HTTP_PUT, string.format(admin_body, 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 body = [[{"name":"hello \"world\""}]] local uri = "http://127.0.0.1:" .. ngx.var.server_port .. "/foobar" local opt = {method = "POST", body = body} local httpc = http.new() local res = httpc:request_uri(uri, opt) assert(res.status == 200) } } === TEST 9: test _escape_xml --- config location /demo { content_by_lua_block { local core = require("apisix.core") local body = core.request.get_body() local xml2lua = require("xml2lua") local xmlhandler = require("xmlhandler.tree") local handler = xmlhandler:new() local parser = xml2lua.parser(handler) parser:parse(body) assert(handler.root.foobar == "") } } location /t { content_by_lua_block { local t = require("lib.test_admin") local core = require("apisix.core") local req_template = [[{*_escape_xml(name)*}]] local admin_body = [[{ "uri": "/foobar", "plugins": { "proxy-rewrite": { "uri": "/demo", "method": "POST" }, "body-transformer": { "request": { "input_format": "json", "template": "%s" } } }, "upstream": { "type": "roundrobin", "nodes": { "127.0.0.1:%d": 1 } } }]] local code, body = t.test('/apisix/admin/routes/1', ngx.HTTP_PUT, string.format(admin_body, 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 body = [[{"name":""}]] local uri = "http://127.0.0.1:" .. ngx.var.server_port .. "/foobar" local opt = {method = "POST", body = body} local httpc = http.new() local res = httpc:request_uri(uri, opt) assert(res.status == 200) } } === TEST 10: cooperation of proxy-cache plugin --- http_config lua_shared_dict memory_cache 50m; --- config location /demo { content_by_lua_block { ngx.say([[ hello ]]) } } location /t { content_by_lua_block { local t = require("lib.test_admin") local req_template = ngx.encode_base64[[ {{_escape_xml(country)}} ]] local rsp_template = ngx.encode_base64[[ {"result": {*_escape_json(Envelope.Body.CapitalCityResponse.CapitalCityResult)*}} ]] local code, body = t.test('/apisix/admin/routes/1', ngx.HTTP_PUT, string.format([[{ "uri": "/capital", "plugins": { "proxy-rewrite": { "set": { "Accept-Encoding": "identity", "Content-Type": "text/xml" }, "uri": "/demo" }, "proxy-cache":{ "cache_strategy": "memory", "cache_bypass": ["$arg_bypass"], "cache_http_status": [200], "cache_key": ["$uri", "-cache-id"], "cache_method": ["POST"], "hide_cache_headers": true, "no_cache": ["$arg_test"], "cache_zone": "memory_cache" }, "body-transformer": { "request": { "input_format": "json", "template": "%s" }, "response": { "input_format": "xml", "template": "%s" } }, "response-rewrite":{ "headers": { "set": { "Content-Type": "application/json" } } } }, "upstream": { "type": "roundrobin", "nodes": { "127.0.0.1:%d": 1 } } }]], req_template, rsp_template, ngx.var.server_port) ) local core = require("apisix.core") local http = require("resty.http") local uri = "http://127.0.0.1:" .. ngx.var.server_port .. "/capital" local body = [[{"country": "foo"}]] local opt = {method = "POST", body = body} local httpc = http.new() local res = httpc:request_uri(uri, opt) assert(res.status == 200) local data = core.json.decode(res.body) assert(data.result == "hello") assert(res.headers["Apisix-Cache-Status"] == "MISS") local res2 = httpc:request_uri(uri, opt) assert(res2.status == 200) local data2 = core.json.decode(res2.body) assert(data2.result == "hello") assert(res2.headers["Apisix-Cache-Status"] == "HIT") } } === TEST 11: return raw body with _body anytime --- http_config --- config location /demo { content_by_lua_block { ngx.header.content_type = "application/json" ngx.print('{"result": "hello world"}') } } location /t { content_by_lua_block { local t = require("lib.test_admin") local rsp_template = ngx.encode_base64[[ {"raw_body": {*_escape_json(_body)*}, "result": {*_escape_json(result)*}} ]] local code, body = t.test('/apisix/admin/routes/1', ngx.HTTP_PUT, string.format([[{ "uri": "/capital", "plugins": { "proxy-rewrite": { "uri": "/demo" }, "body-transformer": { "response": { "template": "%s" } } }, "upstream": { "type": "roundrobin", "nodes": { "127.0.0.1:%d": 1 } } }]], rsp_template, ngx.var.server_port) ) local core = require("apisix.core") local http = require("resty.http") local uri = "http://127.0.0.1:" .. ngx.var.server_port .. "/capital" local opt = {method = "GET", headers = {["Content-Type"] = "application/json"}} local httpc = http.new() local res = httpc:request_uri(uri, opt) assert(res.status == 200) local data = core.json.decode(res.body) assert(data.result == "hello world") assert(data.raw_body == '{"result": "hello world"}') } } === TEST 12: empty xml value should be rendered as empty string --- config location /demo { content_by_lua_block { ngx.print([[ 33333333333 33333333333 ]]) } } location /t { content_by_lua_block { local t = require("lib.test_admin") local rsp_template = ngx.encode_base64[[ { "KOVKood":"{{Envelope.Body.RR58isikEpiletResponse.response.KOVKood}}" } ]] local code, body = t.test('/apisix/admin/routes/1', ngx.HTTP_PUT, string.format([[{ "uri": "/ws", "plugins": { "proxy-rewrite": { "uri": "/demo" }, "body-transformer": { "response": { "input_format": "xml", "template": "%s" } } }, "upstream": { "type": "roundrobin", "nodes": { "127.0.0.1:%d": 1 } } }]], rsp_template, 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 .. "/ws" local opt = {method = "GET"} local httpc = http.new() local res = httpc:request_uri(uri, opt) assert(res.status == 200) local data1 = core.json.decode(res.body) local data2 = core.json.decode[[{"KOVKood":""}]] 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"}} 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) } } === TEST 16: test for missing Content-Type and skip body parsing --- config location /demo { content_by_lua_block { local core = require("apisix.core") local body = core.request.get_body() assert(body == "{\"message\": \"actually json\"}") } } location /t { content_by_lua_block { local t = require("lib.test_admin") local core = require("apisix.core") local code, body = t.test('/apisix/admin/routes/1', ngx.HTTP_PUT, string.format([[{ "uri": "/foobar", "plugins": { "proxy-rewrite": { "uri": "/demo" }, "body-transformer": { "request": { "input_format": "plain", "template": "{\"message\": \"{* string.gsub(_body, 'not ', '') *}\"}" } } }, "upstream": { "type": "roundrobin", "nodes": { "127.0.0.1:%d": 1 } } }]], ngx.var.server_port) ) if code >= 300 then ngx.status = code return end ngx.sleep(0.5) local http = require("resty.http") local httpc = http.new() local res, err = httpc:request_uri("http://127.0.0.1:" .. ngx.var.server_port .. "/foobar", { method = "POST", body = "not actually json", }) assert(res.status == 200) } } --- no_error_log no input format to parse