t/lib/ext-plugin.lua (562 lines of code) (raw):

-- -- 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. -- local json = require("toolkit.json") local ext = require("apisix.plugins.ext-plugin.init") local constants = require("apisix.constants") local flatbuffers = require("flatbuffers") local err_code = require("A6.Err.Code") local err_resp = require("A6.Err.Resp") local prepare_conf_req = require("A6.PrepareConf.Req") local prepare_conf_resp = require("A6.PrepareConf.Resp") local a6_method = require("A6.Method") local text_entry = require("A6.TextEntry") local http_req_call_req = require("A6.HTTPReqCall.Req") local http_req_call_resp = require("A6.HTTPReqCall.Resp") local http_req_call_action = require("A6.HTTPReqCall.Action") local http_req_call_stop = require("A6.HTTPReqCall.Stop") local http_req_call_rewrite = require("A6.HTTPReqCall.Rewrite") local http_resp_call_req = require("A6.HTTPRespCall.Req") local http_resp_call_resp = require("A6.HTTPRespCall.Resp") local extra_info = require("A6.ExtraInfo.Info") local extra_info_req = require("A6.ExtraInfo.Req") local extra_info_var = require("A6.ExtraInfo.Var") local extra_info_resp = require("A6.ExtraInfo.Resp") local extra_info_reqbody = require("A6.ExtraInfo.ReqBody") local extra_info_respbody = require("A6.ExtraInfo.RespBody") local _M = {} local builder = flatbuffers.Builder(0) local function build_extra_info(info, ty) extra_info_req.Start(builder) extra_info_req.AddInfoType(builder, ty) extra_info_req.AddInfo(builder, info) end local function build_action(action, ty) http_req_call_resp.Start(builder) http_req_call_resp.AddActionType(builder, ty) http_req_call_resp.AddAction(builder, action) end local function ask_extra_info(sock, case_extra_info) local data for _, action in ipairs(case_extra_info) do if action.type == "closed" then ngx.exit(-1) return end if action.type == "var" then local name = builder:CreateString(action.name) extra_info_var.Start(builder) extra_info_var.AddName(builder, name) local var_req = extra_info_var.End(builder) build_extra_info(var_req, extra_info.Var) local req = extra_info_req.End(builder) builder:Finish(req) data = builder:Output() local ok, err = ext.send(sock, constants.RPC_EXTRA_INFO, data) if not ok then ngx.log(ngx.ERR, err) return end ngx.log(ngx.WARN, "send extra info req successfully") local ty, data = ext.receive(sock) if not ty then ngx.log(ngx.ERR, data) return end assert(ty == constants.RPC_EXTRA_INFO, ty) local buf = flatbuffers.binaryArray.New(data) local resp = extra_info_resp.GetRootAsResp(buf, 0) local res = resp:ResultAsString() assert(res == action.result, res) end if action.type == "reqbody" then extra_info_reqbody.Start(builder) local reqbody_req = extra_info_reqbody.End(builder) build_extra_info(reqbody_req, extra_info.ReqBody) local req = extra_info_req.End(builder) builder:Finish(req) data = builder:Output() local ok, err = ext.send(sock, constants.RPC_EXTRA_INFO, data) if not ok then ngx.log(ngx.ERR, err) return end ngx.log(ngx.WARN, "send extra info req successfully") local ty, data = ext.receive(sock) if not ty then ngx.log(ngx.ERR, data) return end assert(ty == constants.RPC_EXTRA_INFO, ty) local buf = flatbuffers.binaryArray.New(data) local resp = extra_info_resp.GetRootAsResp(buf, 0) local res = resp:ResultAsString() assert(res == action.result, res) end if action.type == "respbody" then extra_info_respbody.Start(builder) local respbody_req = extra_info_respbody.End(builder) build_extra_info(respbody_req, extra_info.RespBody) local req = extra_info_req.End(builder) builder:Finish(req) data = builder:Output() local ok, err = ext.send(sock, constants.RPC_EXTRA_INFO, data) if not ok then ngx.log(ngx.ERR, err) return end ngx.log(ngx.WARN, "send extra info req successfully") local ty, data = ext.receive(sock) if not ty then ngx.log(ngx.ERR, data) return end assert(ty == constants.RPC_EXTRA_INFO, ty) local buf = flatbuffers.binaryArray.New(data) local resp = extra_info_resp.GetRootAsResp(buf, 0) local res = resp:ResultAsString() assert(res == action.result, res) end end end function _M.go(case) local sock = ngx.req.socket(true) local ty, data = ext.receive(sock) if not ty then ngx.log(ngx.ERR, data) return end ngx.log(ngx.WARN, "receive rpc call successfully") if ty == constants.RPC_PREPARE_CONF then if case.inject_error then ty = constants.RPC_ERROR err_resp.Start(builder) err_resp.AddCode(builder, err_code.BAD_REQUEST) local req = prepare_conf_req.End(builder) builder:Finish(req) data = builder:Output() else local buf = flatbuffers.binaryArray.New(data) local pc = prepare_conf_req.GetRootAsReq(buf, 0) if case.with_conf then local conf = pc:Conf(1) assert(conf:Name(), "foo") assert(conf:Value(), "bar") local conf = pc:Conf(2) assert(conf:Name(), "cat") assert(conf:Value(), "dog") else assert(pc:ConfLength() == 0) end if case.expect_key_pattern then local m = ngx.re.find(pc:Key(), case.expect_key_pattern, "jo") assert(m ~= nil, pc:Key()) else assert(pc:Key() ~= "") end prepare_conf_resp.Start(builder) prepare_conf_resp.AddConfToken(builder, 233) local req = prepare_conf_req.End(builder) builder:Finish(req) data = builder:Output() end elseif case.no_token then ty = constants.RPC_ERROR err_resp.Start(builder) err_resp.AddCode(builder, err_code.CONF_TOKEN_NOT_FOUND) local req = prepare_conf_req.End(builder) builder:Finish(req) data = builder:Output() elseif ty == constants.RPC_HTTP_REQ_CALL then local buf = flatbuffers.binaryArray.New(data) local call_req = http_req_call_req.GetRootAsReq(buf, 0) if case.check_input then assert(call_req:Id() == 0) assert(call_req:ConfToken() == 233) assert(call_req:SrcIpLength() == 4) assert(call_req:SrcIp(1) == 127) assert(call_req:SrcIp(2) == 0) assert(call_req:SrcIp(3) == 0) assert(call_req:SrcIp(4) == 1) assert(call_req:Method() == a6_method.PUT) assert(call_req:Path() == "/hello") assert(call_req:ArgsLength() == 4) local res = {} for i = 1, call_req:ArgsLength() do local entry = call_req:Args(i) local r = res[entry:Name()] if r then res[entry:Name()] = {r, entry:Value()} else res[entry:Name()] = entry:Value() or true end end assert(json.encode(res) == '{\"xx\":[\"y\",\"z\"],\"y\":\"\",\"z\":true}') assert(call_req:HeadersLength() == 5) local res = {} for i = 1, call_req:HeadersLength() do local entry = call_req:Headers(i) local r = res[entry:Name()] if r then res[entry:Name()] = {r, entry:Value()} else res[entry:Name()] = entry:Value() or true end end assert(json.encode(res) == '{\"connection\":\"close\",\"host\":\"localhost\",' .. '\"x-req\":[\"foo\",\"bar\"],\"x-resp\":\"cat\"}') elseif case.check_input_ipv6 then assert(call_req:SrcIpLength() == 16) for i = 1, 15 do assert(call_req:SrcIp(i) == 0) end assert(call_req:SrcIp(16) == 1) elseif case.check_input_rewrite_host then for i = 1, call_req:HeadersLength() do local entry = call_req:Headers(i) if entry:Name() == "host" then assert(entry:Value() == "test.com") end end elseif case.check_input_rewrite_path then assert(call_req:Path() == "/xxx") elseif case.check_input_rewrite_args then assert(call_req:Path() == "/xxx") assert(call_req:ArgsLength() == 1) local entry = call_req:Args(1) assert(entry:Name() == "x") assert(entry:Value() == "z") elseif case.get_request_body then assert(call_req:Method() == a6_method.POST) else assert(call_req:Method() == a6_method.GET) end if case.extra_info then ask_extra_info(sock, case.extra_info) end if case.stop == true then local len = 3 http_req_call_stop.StartBodyVector(builder, len) builder:PrependByte(string.byte("t")) builder:PrependByte(string.byte("a")) builder:PrependByte(string.byte("c")) local b = builder:EndVector(len) local hdrs = { {"X-Resp", "foo"}, {"X-Req", "bar"}, {"X-Same", "one"}, {"X-Same", "two"}, } local len = #hdrs local textEntries = {} for i = 1, len do local name = builder:CreateString(hdrs[i][1]) local value = builder:CreateString(hdrs[i][2]) text_entry.Start(builder) text_entry.AddName(builder, name) text_entry.AddValue(builder, value) local c = text_entry.End(builder) textEntries[i] = c end http_req_call_stop.StartHeadersVector(builder, len) for i = len, 1, -1 do builder:PrependUOffsetTRelative(textEntries[i]) end local vec = builder:EndVector(len) http_req_call_stop.Start(builder) if case.check_default_status ~= true then http_req_call_stop.AddStatus(builder, 405) end http_req_call_stop.AddBody(builder, b) http_req_call_stop.AddHeaders(builder, vec) local action = http_req_call_stop.End(builder) build_action(action, http_req_call_action.Stop) elseif case.rewrite == true or case.rewrite_host == true then local hdrs if case.rewrite_host then hdrs = {{"host", "127.0.0.1"}} else hdrs = { {"X-Delete", nil}, {"X-Change", "bar"}, {"X-Add", "bar"}, } end local len = #hdrs local textEntries = {} for i = 1, len do local name = builder:CreateString(hdrs[i][1]) local value if hdrs[i][2] then value = builder:CreateString(hdrs[i][2]) end text_entry.Start(builder) text_entry.AddName(builder, name) if value then text_entry.AddValue(builder, value) end local c = text_entry.End(builder) textEntries[i] = c end http_req_call_rewrite.StartHeadersVector(builder, len) for i = len, 1, -1 do builder:PrependUOffsetTRelative(textEntries[i]) end local vec = builder:EndVector(len) local path = builder:CreateString("/uri") http_req_call_rewrite.Start(builder) http_req_call_rewrite.AddPath(builder, path) http_req_call_rewrite.AddHeaders(builder, vec) local action = http_req_call_rewrite.End(builder) build_action(action, http_req_call_action.Rewrite) elseif case.rewrite_args == true or case.rewrite_args_only == true then local path = builder:CreateString("/plugin_proxy_rewrite_args") local args = { {"a", "foo"}, {"d", nil}, {"c", "bar"}, {"a", "bar"}, } local len = #args local textEntries = {} for i = 1, len do local name = builder:CreateString(args[i][1]) local value if args[i][2] then value = builder:CreateString(args[i][2]) end text_entry.Start(builder) text_entry.AddName(builder, name) if value then text_entry.AddValue(builder, value) end local c = text_entry.End(builder) textEntries[i] = c end http_req_call_rewrite.StartHeadersVector(builder, len) for i = len, 1, -1 do builder:PrependUOffsetTRelative(textEntries[i]) end local vec = builder:EndVector(len) http_req_call_rewrite.Start(builder) if not case.rewrite_args_only then http_req_call_rewrite.AddPath(builder, path) end http_req_call_rewrite.AddArgs(builder, vec) local action = http_req_call_rewrite.End(builder) build_action(action, http_req_call_action.Rewrite) elseif case.rewrite_bad_path == true then local path = builder:CreateString("/plugin_proxy_rewrite_args?a=2") http_req_call_rewrite.Start(builder) http_req_call_rewrite.AddPath(builder, path) local action = http_req_call_rewrite.End(builder) build_action(action, http_req_call_action.Rewrite) elseif case.rewrite_resp_header == true or case.rewrite_vital_resp_header == true then local hdrs = { {"X-Resp", "foo"}, {"X-Req", "bar"}, {"Content-Type", "application/json"}, {"Content-Encoding", "deflate"}, } local len = #hdrs local textEntries = {} for i = 1, len do local name = builder:CreateString(hdrs[i][1]) local value = builder:CreateString(hdrs[i][2]) text_entry.Start(builder) text_entry.AddName(builder, name) text_entry.AddValue(builder, value) local c = text_entry.End(builder) textEntries[i] = c end http_req_call_rewrite.StartRespHeadersVector(builder, len) for i = len, 1, -1 do builder:PrependUOffsetTRelative(textEntries[i]) end local vec = builder:EndVector(len) local path = builder:CreateString("/plugin_proxy_rewrite_resp_header") http_req_call_rewrite.Start(builder) http_req_call_rewrite.AddRespHeaders(builder, vec) http_req_call_rewrite.AddPath(builder, path) local action = http_req_call_rewrite.End(builder) build_action(action, http_req_call_action.Rewrite) elseif case.rewrite_same_resp_header == true then local hdrs = { {"X-Resp", "foo"}, {"X-Req", "bar"}, {"X-Same", "one"}, {"X-Same", "two"}, } local len = #hdrs local textEntries = {} for i = 1, len do local name = builder:CreateString(hdrs[i][1]) local value = builder:CreateString(hdrs[i][2]) text_entry.Start(builder) text_entry.AddName(builder, name) text_entry.AddValue(builder, value) local c = text_entry.End(builder) textEntries[i] = c end http_req_call_rewrite.StartRespHeadersVector(builder, len) for i = len, 1, -1 do builder:PrependUOffsetTRelative(textEntries[i]) end local vec = builder:EndVector(len) local path = builder:CreateString("/plugin_proxy_rewrite_resp_header") http_req_call_rewrite.Start(builder) http_req_call_rewrite.AddRespHeaders(builder, vec) http_req_call_rewrite.AddPath(builder, path) local action = http_req_call_rewrite.End(builder) build_action(action, http_req_call_action.Rewrite) elseif case.rewrite_request_body == true then local len = 4 http_req_call_rewrite.StartBodyVector(builder, len) builder:PrependByte(string.byte("\n")) builder:PrependByte(string.byte("c")) builder:PrependByte(string.byte("b")) builder:PrependByte(string.byte("a")) local b = builder:EndVector(len) http_req_call_rewrite.Start(builder) http_req_call_rewrite.AddBody(builder, b) local action = http_req_call_rewrite.End(builder) build_action(action, http_req_call_action.Rewrite) else http_req_call_resp.Start(builder) end local req = http_req_call_resp.End(builder) builder:Finish(req) data = builder:Output() elseif ty == constants.RPC_HTTP_RESP_CALL then local buf = flatbuffers.binaryArray.New(data) local call_req = http_resp_call_req.GetRootAsReq(buf, 0) if case.check_input then assert(call_req:Id() == 0) assert(call_req:ConfToken() == 233) assert(call_req:Status() == 200) local len = call_req:HeadersLength() local headers = {} for i = 1, len do local entry = call_req:Headers(i) local r = headers[entry:Name()] if r then headers[entry:Name()] = {r, entry:Value()} else headers[entry:Name()] = entry:Value() or true end end assert(json.encode(headers), '{"Connection":"close","Content-Length":"12",' .. '"Content-Type":"text/plain","Server":"openresty"}') http_resp_call_resp.Start(builder) elseif case.modify_body then local len = 3 http_resp_call_resp.StartBodyVector(builder, len) builder:PrependByte(string.byte("t")) builder:PrependByte(string.byte("a")) builder:PrependByte(string.byte("c")) local b = builder:EndVector(len) http_resp_call_resp.Start(builder) http_resp_call_resp.AddBody(builder, b) elseif case.modify_header then local len = call_req:HeadersLength() local headers = {} for i = 1, len do local entry = call_req:Headers(i) local r = headers[entry:Name()] if r then headers[entry:Name()] = {r, entry:Value()} else headers[entry:Name()] = entry:Value() or true end end if case.same_header then headers["x-same"] = {"one", "two"} else local runner = headers["x-runner"] if runner and runner == "Go-runner" then headers["x-runner"] = "Test-Runner" end headers["Content-Type"] = "application/json" end local i = 1 local textEntries = {} for k, v in pairs(headers) do local name = builder:CreateString(k) if type(v) == "table" then for j = 1, #v do local value = builder:CreateString(v[j]) text_entry.Start(builder) text_entry.AddName(builder, name) text_entry.AddValue(builder, value) local c = text_entry.End(builder) textEntries[i] = c i = i + 1 end else local value = builder:CreateString(v) text_entry.Start(builder) text_entry.AddName(builder, name) text_entry.AddValue(builder, value) local c = text_entry.End(builder) textEntries[i] = c i = i + 1 end end len = #textEntries http_resp_call_resp.StartHeadersVector(builder, len) for i = len, 1, -1 do builder:PrependUOffsetTRelative(textEntries[i]) end local vec = builder:EndVector(len) http_resp_call_resp.Start(builder) http_resp_call_resp.AddHeaders(builder, vec) elseif case.modify_status then local status = call_req:Status() if status == 200 then status = 304 end http_resp_call_resp.Start(builder) http_resp_call_resp.AddStatus(builder, status) elseif case.extra_info then ask_extra_info(sock, case.extra_info) http_resp_call_resp.Start(builder) else http_resp_call_resp.Start(builder) end local resp = http_resp_call_resp.End(builder) builder:Finish(resp) data = builder:Output() end local ok, err = ext.send(sock, ty, data) if not ok then ngx.log(ngx.ERR, err) return end ngx.log(ngx.WARN, "send rpc call response successfully") end function _M.header_too_short() local sock = ngx.req.socket() local ty, data = ext.receive(sock) if not ty then ngx.log(ngx.ERR, data) return end ngx.log(ngx.WARN, "receive rpc call successfully") local ok, err = sock:send({string.char(2), string.char(1)}) if not ok then ngx.log(ngx.ERR, err) return end end function _M.data_too_short() local sock = ngx.req.socket() local ty, data = ext.receive(sock) if not ty then ngx.log(ngx.ERR, data) return end ngx.log(ngx.WARN, "receive rpc call successfully") local ok, err = sock:send({string.char(2), string.char(1), string.rep(string.char(0), 3)}) if not ok then ngx.log(ngx.ERR, err) return end end return _M