This is an automated email from the ASF dual-hosted git repository.
spacewander pushed a commit to branch master
in repository https://gitbox.apache.org/repos/asf/apisix.git
The following commit(s) were added to refs/heads/master by this push:
new 1d29d4bfc feat(ext-plugin-post-resp): support get response body by
extra_info (#7947)
1d29d4bfc is described below
commit 1d29d4bfc25f756161f160f6cc81e200cd4f8a08
Author: soulbird <[email protected]>
AuthorDate: Fri Sep 23 15:51:16 2022 +0800
feat(ext-plugin-post-resp): support get response body by extra_info (#7947)
Co-authored-by: soulbird <[email protected]>
---
apisix/plugins/ext-plugin-post-resp.lua | 55 ++++----
apisix/plugins/ext-plugin/helper.lua | 22 ++++
apisix/plugins/ext-plugin/init.lua | 50 ++++++-
docs/en/latest/plugins/ext-plugin-post-resp.md | 4 -
docs/zh/latest/plugins/ext-plugin-post-resp.md | 4 -
rockspec/apisix-master-0.rockspec | 2 +-
t/lib/ext-plugin.lua | 162 ++++++++++++++---------
t/plugin/ext-plugin/extra-info.t | 172 +++++++++++++++++++++++++
8 files changed, 374 insertions(+), 97 deletions(-)
diff --git a/apisix/plugins/ext-plugin-post-resp.lua
b/apisix/plugins/ext-plugin-post-resp.lua
index e6156804c..40d3ca450 100644
--- a/apisix/plugins/ext-plugin-post-resp.lua
+++ b/apisix/plugins/ext-plugin-post-resp.lua
@@ -16,6 +16,7 @@
--
local core = require("apisix.core")
local ext = require("apisix.plugins.ext-plugin.init")
+local helper = require("apisix.plugins.ext-plugin.helper")
local constants = require("apisix.constants")
local http = require("resty.http")
@@ -100,36 +101,46 @@ local function get_response(ctx, http_obj)
return res, err
end
+local function send_chunk(chunk)
+ if not chunk then
+ return nil
+ end
-local function send_response(res, code)
- ngx.status = code or res.status
+ local ok, print_err = ngx_print(chunk)
+ if not ok then
+ return "output response failed: ".. (print_err or "")
+ end
+ local ok, flush_err = ngx_flush(true)
+ if not ok then
+ core.log.warn("flush response failed: ", flush_err)
+ end
- local reader = res.body_reader
- repeat
- local chunk, ok, read_err, print_err, flush_err
- -- TODO: HEAD or 304
- chunk, read_err = reader()
- if read_err then
- return "read response failed: ".. (read_err or "")
- end
+ return nil
+end
- if chunk then
- ok, print_err = ngx_print(chunk)
- if not ok then
- return "output response failed: ".. (print_err or "")
- end
- ok, flush_err = ngx_flush(true)
- if not ok then
- core.log.warn("flush response failed: ", flush_err)
+-- TODO: response body is empty (304 or HEAD)
+-- If the upstream returns 304 or the request method is HEAD,
+-- there is no response body. In this case,
+-- we need to send a response to the client in the plugin,
+-- instead of continuing to execute the subsequent plugin.
+local function send_response(ctx, res, code)
+ ngx.status = code or res.status
+
+ local chunks = ctx.runner_ext_response_body
+ if chunks then
+ for i=1, #chunks do
+ local err = send_chunk(chunks[i])
+ if err then
+ return err
end
end
- until not chunk
+ return
+ end
- return nil
+ return helper.response_reader(res.body_reader, send_chunk)
end
-
function _M.check_schema(conf)
return core.schema.check(_M.schema, conf)
end
@@ -157,7 +168,7 @@ function _M.before_proxy(conf, ctx)
core.log.info("ext-plugin will send response")
-- send origin response, status maybe changed.
- err = send_response(res, code)
+ err = send_response(ctx, res, code)
close(http_obj)
if err then
diff --git a/apisix/plugins/ext-plugin/helper.lua
b/apisix/plugins/ext-plugin/helper.lua
index 4d141a7f0..7750bb54a 100644
--- a/apisix/plugins/ext-plugin/helper.lua
+++ b/apisix/plugins/ext-plugin/helper.lua
@@ -56,4 +56,26 @@ function _M.get_conf_token_cache_time()
end
+function _M.response_reader(reader, callback, ...)
+ if not reader then
+ return "get response reader failed"
+ end
+
+ repeat
+ local chunk, read_err, cb_err
+ chunk, read_err = reader()
+ if read_err then
+ return "read response failed: ".. (read_err or "")
+ end
+
+ if chunk then
+ cb_err = callback(chunk, ...)
+ if cb_err then
+ return cb_err
+ end
+ end
+ until not chunk
+end
+
+
return _M
diff --git a/apisix/plugins/ext-plugin/init.lua
b/apisix/plugins/ext-plugin/init.lua
index b575ba45f..0f9e0de14 100644
--- a/apisix/plugins/ext-plugin/init.lua
+++ b/apisix/plugins/ext-plugin/init.lua
@@ -31,6 +31,7 @@ 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 text_entry = require("A6.TextEntry")
local err_resp = require("A6.Err.Resp")
local err_code = require("A6.Err.Code")
@@ -304,7 +305,31 @@ local function handle_extra_info(ctx, input)
if err then
core.log.error("failed to read request body: ", err)
end
-
+ elseif info_type == extra_info.RespBody then
+ local ext_res = ctx.runner_ext_response
+ if ext_res then
+ local info = req:Info()
+ local respbody_req = extra_info_respbody.New()
+ respbody_req:Init(info.byte, info.pos)
+
+ local chunks = {}
+ local err = helper.response_reader(ext_res.body_reader, function
(chunk, chunks)
+ -- When the upstream response is chunked type,
+ -- we will receive the complete response body
+ -- before sending it to the runner program
+ -- to reduce the number of RPC calls.
+ core.table.insert_tail(chunks, chunk)
+ end, chunks)
+ if err then
+ -- TODO: send RPC_ERROR to runner
+ core.log.error(err)
+ else
+ res = core.table.concat(chunks)
+ ctx.runner_ext_response_body = chunks
+ end
+ else
+ core.log.error("failed to read response body: not exits")
+ end
else
return nil, "unsupported info type: " .. info_type
end
@@ -732,9 +757,26 @@ local rpc_handlers = {
return nil, "failed to send RPC_HTTP_RESP_CALL: " .. err
end
- local ty, resp = receive(sock)
- if ty == nil then
- return nil, "failed to receive RPC_HTTP_RESP_CALL: " .. resp
+ local ty, resp
+ while true do
+ ty, resp = receive(sock)
+ if ty == nil then
+ return nil, "failed to receive RPC_HTTP_REQ_CALL: " .. resp
+ end
+
+ if ty ~= constants.RPC_EXTRA_INFO then
+ break
+ end
+
+ local out, err = handle_extra_info(ctx, resp)
+ if not out then
+ return nil, "failed to handle RPC_EXTRA_INFO: " .. err
+ end
+
+ local ok, err = send(sock, constants.RPC_EXTRA_INFO, out)
+ if not ok then
+ return nil, "failed to reply RPC_EXTRA_INFO: " .. err
+ end
end
if ty ~= constants.RPC_HTTP_RESP_CALL then
diff --git a/docs/en/latest/plugins/ext-plugin-post-resp.md
b/docs/en/latest/plugins/ext-plugin-post-resp.md
index f7cfcee37..d47f6f2a5 100644
--- a/docs/en/latest/plugins/ext-plugin-post-resp.md
+++ b/docs/en/latest/plugins/ext-plugin-post-resp.md
@@ -45,10 +45,6 @@ See [External Plugin](../external-plugin.md) to learn more.
Execution of External Plugins will affect the response of the current request.
-External Plugin does not yet support getting request context information.
-
-External Plugin does not yet support getting the response body of an upstream
response.
-
:::
## Attributes
diff --git a/docs/zh/latest/plugins/ext-plugin-post-resp.md
b/docs/zh/latest/plugins/ext-plugin-post-resp.md
index 251e4ca11..429e724ef 100644
--- a/docs/zh/latest/plugins/ext-plugin-post-resp.md
+++ b/docs/zh/latest/plugins/ext-plugin-post-resp.md
@@ -45,10 +45,6 @@ description: 本文介绍了关于 Apache APISIX `ext-plugin-post-resp` 插件
External Plugin 执行的结果会影响当前请求的响应。
-External Plugin 尚不支持获取请求的上下文信息。
-
-External Plugin 尚不支持获取上游响应的响应体。
-
:::
## 属性
diff --git a/rockspec/apisix-master-0.rockspec
b/rockspec/apisix-master-0.rockspec
index 5fa057bb4..6f3150fa0 100644
--- a/rockspec/apisix-master-0.rockspec
+++ b/rockspec/apisix-master-0.rockspec
@@ -68,7 +68,7 @@ dependencies = {
"luasec = 0.9-1",
"lua-resty-consul = 0.3-2",
"penlight = 1.9.2-1",
- "ext-plugin-proto = 0.5.0",
+ "ext-plugin-proto = 0.6.0",
"casbin = 1.41.1",
"api7-snowflake = 2.0-1",
"inspect == 3.1.1",
diff --git a/t/lib/ext-plugin.lua b/t/lib/ext-plugin.lua
index 33bb32b15..6403e74f5 100644
--- a/t/lib/ext-plugin.lua
+++ b/t/lib/ext-plugin.lua
@@ -36,6 +36,7 @@ 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)
@@ -55,6 +56,101 @@ local function build_action(action, ty)
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)
@@ -178,68 +274,7 @@ function _M.go(case)
end
if case.extra_info then
- 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
- end
+ ask_extra_info(sock, case.extra_info)
end
if case.stop == true then
@@ -544,6 +579,9 @@ function _M.go(case)
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
diff --git a/t/plugin/ext-plugin/extra-info.t b/t/plugin/ext-plugin/extra-info.t
index 56b67be0f..e55bb673b 100644
--- a/t/plugin/ext-plugin/extra-info.t
+++ b/t/plugin/ext-plugin/extra-info.t
@@ -181,3 +181,175 @@ GET /hello
--- error_code: 503
--- error_log
failed to receive RPC_HTTP_REQ_CALL: closed
+
+
+
+=== TEST 5: ask response body (not exist)
+--- request
+GET /hello
+--- extra_stream_config
+ server {
+ listen unix:$TEST_NGINX_HTML_DIR/nginx.sock;
+
+ content_by_lua_block {
+ local ext = require("lib.ext-plugin")
+ local actions = {
+ {type = "respbody", result = nil}
+ }
+ ext.go({extra_info = actions})
+ }
+ }
+--- error_log: failed to read response body: not exits
+
+
+
+=== TEST 6: add route with ext-plugin-post-resp
+--- config
+ location /t {
+ content_by_lua_block {
+ local json = require("toolkit.json")
+ local t = require("lib.test_admin")
+
+ local code, message, res = t.test('/apisix/admin/routes/1',
+ ngx.HTTP_PUT,
+ [[{
+ "uri": "/*",
+ "plugins": {
+ "ext-plugin-post-resp": {
+ }
+ },
+ "upstream": {
+ "nodes": {
+ "127.0.0.1:1980": 1
+ },
+ "type": "roundrobin"
+ }
+ }]]
+ )
+
+ if code >= 300 then
+ ngx.status = code
+ ngx.say(message)
+ return
+ end
+
+ ngx.say(message)
+ }
+ }
+--- response_body
+passed
+
+
+
+=== TEST 7: ask var
+--- request
+GET /hello?x=
+--- extra_stream_config
+ server {
+ listen unix:$TEST_NGINX_HTML_DIR/nginx.sock;
+
+ content_by_lua_block {
+ local ext = require("lib.ext-plugin")
+ local actions = {
+ {type = "var", name = "server_addr", result = "127.0.0.1"},
+ {type = "var", name = "remote_addr", result = "127.0.0.1"},
+ {type = "var", name = "route_id", result = "1"},
+ {type = "var", name = "arg_x", result = ""},
+ }
+ ext.go({extra_info = actions})
+ }
+ }
+--- grep_error_log eval
+qr/send extra info req successfully/
+--- grep_error_log_out
+send extra info req successfully
+send extra info req successfully
+send extra info req successfully
+send extra info req successfully
+--- response_body
+hello world
+
+
+
+=== TEST 8: ask response body
+--- request
+GET /hello
+--- extra_stream_config
+ server {
+ listen unix:$TEST_NGINX_HTML_DIR/nginx.sock;
+
+ content_by_lua_block {
+ local ext = require("lib.ext-plugin")
+ local actions = {
+ {type = "respbody", result = "hello world\n"},
+ }
+ ext.go({extra_info = actions})
+ }
+ }
+--- grep_error_log eval
+qr/send extra info req successfully/
+--- grep_error_log_out
+send extra info req successfully
+--- response_body
+hello world
+
+
+
+=== TEST 9: ask response body (chunked)
+--- request
+GET /hello_chunked
+--- extra_stream_config
+ server {
+ listen unix:$TEST_NGINX_HTML_DIR/nginx.sock;
+
+ content_by_lua_block {
+ local ext = require("lib.ext-plugin")
+ local actions = {
+ {type = "respbody", result = "hello world\n"},
+ }
+ ext.go({extra_info = actions})
+ }
+ }
+--- grep_error_log eval
+qr/send extra info req successfully/
+--- grep_error_log_out
+send extra info req successfully
+--- response_body
+hello world
+
+
+
+=== TEST 10: ask request body (empty)
+--- request
+GET /hello
+--- extra_stream_config
+ server {
+ listen unix:$TEST_NGINX_HTML_DIR/nginx.sock;
+
+ content_by_lua_block {
+ local ext = require("lib.ext-plugin")
+ local actions = {
+ {type = "reqbody", result = nil}
+ }
+ ext.go({extra_info = actions})
+ }
+ }
+
+
+
+=== TEST 11: ask request body
+--- request
+POST /hello
+123
+--- extra_stream_config
+ server {
+ listen unix:$TEST_NGINX_HTML_DIR/nginx.sock;
+
+ content_by_lua_block {
+ local ext = require("lib.ext-plugin")
+ local actions = {
+ {type = "reqbody", result = "123"}
+ }
+ ext.go({extra_info = actions})
+ }
+ }