diff --git a/lib/resty/healthcheck.lua b/lib/resty/healthcheck.lua index 89d1284a..1567cc66 100644 --- a/lib/resty/healthcheck.lua +++ b/lib/resty/healthcheck.lua @@ -42,6 +42,46 @@ local ngx_worker_pid = ngx.worker.pid local ssl = require("ngx.ssl") local resty_timer = require "resty.timer" +local new_tab +local nkeys +local is_array + +do + local pcall = pcall + local ok + + ok, new_tab = pcall(require, "table.new") + if not ok then + new_tab = function () return {} end + end + + -- OpenResty branch of LuaJIT New API + ok, nkeys = pcall(require, "table.nkeys") + if not ok then + nkeys = function (tab) + local count = 0 + for _, v in pairs(tab) do + if v ~= nil then + count = count + 1 + end + end + return count + end + end + + ok, is_array = pcall(require, "table.isarray") + if not ok then + is_array = function(t) + for k in pairs(t) do + if type(k) ~= "number" or math.floor(k) ~= k then + return false + end + end + return true + end + end +end + -- constants local EVENT_SOURCE_PREFIX = "lua-resty-healthcheck" local LOG_PREFIX = "[healthcheck] " @@ -866,8 +906,42 @@ function checker:run_single_check(ip, port, hostname, hostheader) end + local req_headers = self.checks.active.headers + local headers + if self.checks.active._headers_str then + headers = self.checks.active._headers_str + else + local headers_length = nkeys(req_headers) + if headers_length > 0 then + if is_array(req_headers) then + self:log(WARN, "array headers is deprecated") + headers = table.concat(req_headers, "\r\n") + else + headers = new_tab(0, headers_length) + local idx = 0 + for key, values in pairs(req_headers) do + if type(values) == "table" then + for _, value in ipairs(values) do + idx = idx + 1 + headers[idx] = key .. ": " .. tostring(value) + end + else + idx = idx + 1 + headers[idx] = key .. ": " .. tostring(values) + end + end + headers = table.concat(headers, "\r\n") + end + if #headers > 0 then + headers = headers .. "\r\n" + end + end + self.checks.active._headers_str = headers or "" + end + local path = self.checks.active.http_path - local request = ("GET %s HTTP/1.0\r\nHost: %s\r\n\r\n"):format(path, hostheader or hostname) + local request = ("GET %s HTTP/1.0\r\n%sHost: %s\r\n\r\n"):format(path, headers, hostheader or hostname or ip) + self:log(DEBUG, "request head: ", request) local bytes bytes, err = sock:send(request) @@ -1229,6 +1303,7 @@ local defaults = { concurrency = 10, http_path = "/", https_verify_certificate = true, + headers = {""}, healthy = { interval = 0, -- 0 = disabled by default http_statuses = { 200, 302 }, @@ -1300,6 +1375,7 @@ end -- * `checks.active.concurrency`: number of targets to check concurrently -- * `checks.active.http_path`: path to use in `GET` HTTP request to run on active checks -- * `checks.active.https_verify_certificate`: boolean indicating whether to verify the HTTPS certificate +-- * `checks.active.headers`: one or more lists of values indexed by header name -- * `checks.active.healthy.interval`: interval between checks for healthy targets (in seconds) -- * `checks.active.healthy.http_statuses`: which HTTP statuses to consider a success -- * `checks.active.healthy.successes`: number of successes to consider a target healthy diff --git a/t/20-req-headers.t b/t/20-req-headers.t new file mode 100644 index 00000000..3c2433e2 --- /dev/null +++ b/t/20-req-headers.t @@ -0,0 +1,264 @@ +use Test::Nginx::Socket::Lua 'no_plan'; +use Cwd qw(cwd); + +workers(1); + +my $pwd = cwd(); + +our $HttpConfig = qq{ + lua_package_path "$pwd/lib/?.lua;;"; + lua_shared_dict test_shm 8m; + lua_shared_dict my_worker_events 8m; +}; + +run_tests(); + +__DATA__ + +=== TEST 1: headers: {"User-Agent: curl/7.29.0"} +--- http_config eval +qq{ + $::HttpConfig + + server { + listen 2112; + location = /status { + return 200; + } + } +} +--- config + location = /t { + content_by_lua_block { + local we = require "resty.worker.events" + assert(we.configure{ shm = "my_worker_events", interval = 0.1 }) + local healthcheck = require("resty.healthcheck") + local checker = healthcheck.new({ + name = "testing", + shm_name = "test_shm", + checks = { + active = { + http_path = "/status", + healthy = { + interval = 0.1 + }, + headers = {"User-Agent: curl/7.29.0"} + } + } + }) + ngx.sleep(0.2) -- wait twice the interval + local ok, err = checker:add_target("127.0.0.1", 2112, nil, true) + ngx.say(ok) + ngx.sleep(0.2) -- wait twice the interval + } + } +--- request +GET /t +--- response_body +true +--- error_log +checking healthy targets: nothing to do +checking healthy targets: #1 +GET /status HTTP/1.0 +User-Agent: curl/7.29.0 +Host: 127.0.0.1 + + + +=== TEST 2: headers: {"User-Agent: curl"} +--- http_config eval +qq{ + $::HttpConfig + + server { + listen 2112; + location = /status { + return 200; + } + } +} +--- config + location = /t { + content_by_lua_block { + local we = require "resty.worker.events" + assert(we.configure{ shm = "my_worker_events", interval = 0.1 }) + local healthcheck = require("resty.healthcheck") + local checker = healthcheck.new({ + name = "testing", + shm_name = "test_shm", + checks = { + active = { + http_path = "/status", + healthy = { + interval = 0.1 + }, + headers = {"User-Agent: curl"} + } + } + }) + ngx.sleep(0.2) -- wait twice the interval + local ok, err = checker:add_target("127.0.0.1", 2112, nil, true) + ngx.say(ok) + ngx.sleep(0.2) -- wait twice the interval + } + } +--- request +GET /t +--- response_body +true +--- error_log +checking healthy targets: nothing to do +checking healthy targets: #1 +GET /status HTTP/1.0 +User-Agent: curl +Host: 127.0.0.1 + + +=== TEST 3: headers: { ["User-Agent"] = "curl" } +--- http_config eval +qq{ + $::HttpConfig + + server { + listen 2112; + location = /status { + return 200; + } + } +} +--- config + location = /t { + content_by_lua_block { + local we = require "resty.worker.events" + assert(we.configure{ shm = "my_worker_events", interval = 0.1 }) + local healthcheck = require("resty.healthcheck") + local checker = healthcheck.new({ + name = "testing", + shm_name = "test_shm", + checks = { + active = { + http_path = "/status", + healthy = { + interval = 0.1 + }, + headers = { ["User-Agent"] = "curl" } + } + } + }) + ngx.sleep(0.2) -- wait twice the interval + local ok, err = checker:add_target("127.0.0.1", 2112, nil, true) + ngx.say(ok) + ngx.sleep(0.2) -- wait twice the interval + } + } +--- request +GET /t +--- response_body +true +--- error_log +checking healthy targets: nothing to do +checking healthy targets: #1 +GET /status HTTP/1.0 +User-Agent: curl +Host: 127.0.0.1 + + + +=== TEST 4: headers: { ["User-Agent"] = {"curl"} } +--- http_config eval +qq{ + $::HttpConfig + + server { + listen 2112; + location = /status { + return 200; + } + } +} +--- config + location = /t { + content_by_lua_block { + local we = require "resty.worker.events" + assert(we.configure{ shm = "my_worker_events", interval = 0.1 }) + local healthcheck = require("resty.healthcheck") + local checker = healthcheck.new({ + name = "testing", + shm_name = "test_shm", + checks = { + active = { + http_path = "/status", + healthy = { + interval = 0.1 + }, + headers = { ["User-Agent"] = {"curl"} } + } + } + }) + ngx.sleep(0.2) -- wait twice the interval + local ok, err = checker:add_target("127.0.0.1", 2112, nil, true) + ngx.say(ok) + ngx.sleep(0.2) -- wait twice the interval + } + } +--- request +GET /t +--- response_body +true +--- error_log +checking healthy targets: nothing to do +checking healthy targets: #1 +GET /status HTTP/1.0 +User-Agent: curl +Host: 127.0.0.1 + + + +=== TEST 5: headers: { ["User-Agent"] = {"curl", "nginx"} } +--- http_config eval +qq{ + $::HttpConfig + + server { + listen 2112; + location = /status { + return 200; + } + } +} +--- config + location = /t { + content_by_lua_block { + local we = require "resty.worker.events" + assert(we.configure{ shm = "my_worker_events", interval = 0.1 }) + local healthcheck = require("resty.healthcheck") + local checker = healthcheck.new({ + name = "testing", + shm_name = "test_shm", + checks = { + active = { + http_path = "/status", + healthy = { + interval = 0.1 + }, + headers = { ["User-Agent"] = {"curl", "nginx"} } + } + } + }) + ngx.sleep(0.2) -- wait twice the interval + local ok, err = checker:add_target("127.0.0.1", 2112, nil, true) + ngx.say(ok) + ngx.sleep(0.2) -- wait twice the interval + } + } +--- request +GET /t +--- response_body +true +--- error_log +checking healthy targets: nothing to do +checking healthy targets: #1 +GET /status HTTP/1.0 +User-Agent: curl +User-Agent: nginx +Host: 127.0.0.1