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})
+        }
+    }

Reply via email to