diff --git a/apisix/plugins/batch-requests.lua b/apisix/plugins/batch-requests.lua index 8927b33b5a50..b6f954978b34 100644 --- a/apisix/plugins/batch-requests.lua +++ b/apisix/plugins/batch-requests.lua @@ -20,6 +20,7 @@ local plugin = require("apisix.plugin") local ngx = ngx local ipairs = ipairs local pairs = pairs +local type = type local str_find = core.string.find local str_lower = string.lower @@ -224,6 +225,50 @@ local function set_common_query(data) end +-- When the request is received on a unix domain socket, $server_port is an +-- empty string, so we fall back to a TCP port from `apisix.node_listen`. +-- The value read from the local conf is not normalized by the CLI, so it can +-- be a plain port number, or an array of port numbers or `{ip, port}` tables. +-- An entry bound to a specific address is only used as a last resort, as the +-- loopback connect below targets 127.0.0.1 and would otherwise be refused. +local function get_loopback_port() + local server_port = ngx.var.server_port + if server_port and server_port ~= "" then + return server_port + end + + local local_conf = core.config.local_conf() + local node_listen = core.table.try_read_attr(local_conf, "apisix", "node_listen") + if type(node_listen) == "number" then + return node_listen + end + + if type(node_listen) == "table" then + local bound_port + for _, value in ipairs(node_listen) do + if type(value) == "number" then + return value + end + + if type(value) == "table" then + -- the port defaults to 9080 if not set, see `apisix/cli/ops.lua` + local port = value.port or 9080 + local ip = value.ip + if ip == nil or ip == "0.0.0.0" or ip == "127.0.0.1" then + return port + end + + bound_port = bound_port or port + end + end + + return bound_port + end + + return nil +end + + local function batch_requests(ctx) local metadata = plugin.plugin_metadata(plugin_name) core.log.info("metadata: ", core.json.delay_encode(metadata)) @@ -269,9 +314,17 @@ local function batch_requests(ctx) } end + local server_port = get_loopback_port() + if not server_port then + return 503, { + error_msg = "this APISIX instance doesn't listen on a TCP port, " .. + "but the batch-requests plugin needs one to loop back requests" + } + end + local httpc = http.new() httpc:set_timeout(data.timeout) - local ok, err = httpc:connect("127.0.0.1", ngx.var.server_port) + local ok, err = httpc:connect("127.0.0.1", server_port) if not ok then return 500, {error_msg = "connect to apisix failed: " .. err} end diff --git a/t/plugin/batch-requests.t b/t/plugin/batch-requests.t index 298f9df11e92..1fe8d66b03b2 100644 --- a/t/plugin/batch-requests.t +++ b/t/plugin/batch-requests.t @@ -1194,3 +1194,133 @@ qr/property \\"path\\" is required/ GET /t --- response_body passed + + + +=== TEST 31: aggregate requests arriving via a unix domain socket +--- config + listen unix:$TEST_NGINX_HTML_DIR/apisix.sock; + + location = /t { + content_by_lua_block { + local core = require("apisix.core") + local json = require("toolkit.json") + local http = require("resty.http") + local httpc = http.new() + httpc:set_timeout(2000) + local ok, err = httpc:connect("unix:$TEST_NGINX_HTML_DIR/apisix.sock") + if not ok then + ngx.say("failed to connect: ", err) + return + end + + local body = core.json.encode({ + pipeline = { + {path = "/uds-b"}, + {path = "/uds-c", method = "PUT"}, + } + }) + + local res, err = httpc:request({ + method = "POST", + path = "/apisix/batch-requests", + headers = { + ["Host"] = "127.0.0.1", + ["Content-Type"] = "application/json", + }, + body = body, + }) + if not res then + ngx.say("request failed: ", err) + return + end + + local resp_body, err = res:read_body() + if not resp_body then + ngx.say("failed to read response body: ", err) + return + end + + if res.status ~= 200 then + ngx.status = res.status + ngx.print(resp_body) + return + end + + local data = json.decode(resp_body) + for _, resp in ipairs(data) do + ngx.say(resp.status, " ", resp.body) + end + } + } + + location = /uds-b { + content_by_lua_block { + ngx.print("B") + } + } + location = /uds-c { + content_by_lua_block { + ngx.status = 201 + ngx.print("C") + } + } +--- request +GET /t +--- response_body +200 B +201 C + + + +=== TEST 32: no TCP port to loop back through +--- yaml_config +apisix: + node_listen: [] + enable_resolv_search_opt: false + trusted_addresses: + - "127.0.0.1" +--- config + listen unix:$TEST_NGINX_HTML_DIR/apisix.sock; + + location = /t { + content_by_lua_block { + local core = require("apisix.core") + local http = require("resty.http") + local httpc = http.new() + httpc:set_timeout(2000) + local ok, err = httpc:connect("unix:$TEST_NGINX_HTML_DIR/apisix.sock") + if not ok then + ngx.say("failed to connect: ", err) + return + end + + local body = core.json.encode({pipeline = {{path = "/uds-b"}}}) + local res, err = httpc:request({ + method = "POST", + path = "/apisix/batch-requests", + headers = { + ["Host"] = "127.0.0.1", + ["Content-Type"] = "application/json", + }, + body = body, + }) + if not res then + ngx.say("request failed: ", err) + return + end + + local resp_body = res:read_body() + ngx.say(res.status, " ", resp_body) + } + } + + location = /uds-b { + content_by_lua_block { + ngx.print("B") + } + } +--- request +GET /t +--- response_body_like +503 .*doesn't listen on a TCP port.*