Skip to content

Commit bcec8a7

Browse files
committed
fix(active-checks): use HTTP/1.1 for health check probes
Previously, active health check probes sent HTTP/1.0 requests, which caused upstream servers that only support HTTP/1.1 to respond with 426 Upgrade Required, incorrectly marking healthy targets as unhealthy. Switch the default probe request from HTTP/1.0 to HTTP/1.1 with Connection: close header. HTTP/1.1 is backward-compatible with all servers that accept HTTP/1.0, so this change requires no configuration updates from users. Refactored run_single_check() into focused helpers: - build_http_headers(): builds and caches serialized header string - establish_connection(): TCP connect + optional TLS handshake - probe_http(): sends HTTP/1.1 GET and reports result FTI-7389 Signed-off-by: Walker Zhao <walker.zhao@konghq.com>
1 parent 1566027 commit bcec8a7

3 files changed

Lines changed: 120 additions & 75 deletions

File tree

lib/resty/healthcheck.lua

Lines changed: 90 additions & 55 deletions
Original file line numberDiff line numberDiff line change
@@ -985,60 +985,18 @@ end
985985
--============================================================================
986986

987987

988-
-- Runs a single healthcheck probe
989-
function checker:run_single_check(ip, port, hostname, hostheader)
990-
991-
local sock, err = ngx.socket.tcp()
992-
if not sock then
993-
self:log(ERR, "failed to create stream socket: ", err)
994-
return
995-
end
996-
997-
sock:settimeout(self.checks.active.timeout * 1000)
998-
999-
local ok
1000-
ok, err = sock:connect(ip, port)
1001-
if not ok then
1002-
if err == "timeout" then
1003-
sock:close() -- timeout errors do not close the socket.
1004-
return self:report_timeout(ip, port, hostname, "active")
1005-
end
1006-
return self:report_tcp_failure(ip, port, hostname, "connect", "active")
1007-
end
1008-
1009-
if self.checks.active.type == "tcp" then
1010-
sock:close()
1011-
return self:report_success(ip, port, hostname, "active")
1012-
end
1013-
1014-
if self.checks.active.type == "https" then
1015-
local https_sni, session, err
1016-
https_sni = self.checks.active.https_sni or hostheader or hostname
1017-
if self.ssl_cert and self.ssl_key then
1018-
ok, err = sock:setclientcert(self.ssl_cert, self.ssl_key)
1019-
1020-
if not ok then
1021-
self:log(ERR, "failed to set client certificate: ", err)
1022-
end
1023-
end
1024-
1025-
session, err = sock:sslhandshake(nil, https_sni,
1026-
self.checks.active.https_verify_certificate)
1027-
1028-
if not session then
1029-
sock:close()
1030-
self:log(ERR, "failed SSL handshake with '", hostname or "", " (", ip, ":", port, ")', using server name (sni) '", https_sni, "': ", err)
1031-
return self:report_tcp_failure(ip, port, hostname, "connect", "active")
1032-
end
1033-
988+
-- Builds and caches the serialized user-configured headers string for HTTP/1.x probes.
989+
-- Uses ~= nil so that a cached empty string ("") is also a cache hit.
990+
local function build_http_headers(self)
991+
if self.checks.active._headers_str ~= nil then
992+
return self.checks.active._headers_str
1034993
end
1035994

1036995
local req_headers = self.checks.active.headers
1037996
local headers
1038-
if self.checks.active._headers_str then
1039-
headers = self.checks.active._headers_str
1040-
elseif req_headers == nil then
1041-
headers = ""
997+
998+
if req_headers == nil then
999+
headers = ""
10421000
else
10431001
local headers_length = nkeys(req_headers)
10441002
if headers_length > 0 then
@@ -1065,15 +1023,75 @@ function checker:run_single_check(ip, port, hostname, hostheader)
10651023
headers = headers .. "\r\n"
10661024
end
10671025
end
1068-
self.checks.active._headers_str = headers or ""
10691026
end
10701027

1028+
self.checks.active._headers_str = headers or ""
1029+
return self.checks.active._headers_str
1030+
end
1031+
1032+
1033+
-- Establishes a TCP connection and optionally performs a TLS handshake for
1034+
-- https type. Returns the connected socket, or nil when a failure has
1035+
-- already been reported via report_timeout / report_tcp_failure.
1036+
local function establish_connection(self, ip, port, hostname, hostheader, typ)
1037+
local sock, err = ngx.socket.tcp()
1038+
if not sock then
1039+
self:log(ERR, "failed to create stream socket: ", err)
1040+
return nil
1041+
end
1042+
1043+
sock:settimeout(self.checks.active.timeout * 1000)
1044+
1045+
local ok
1046+
ok, err = sock:connect(ip, port)
1047+
if not ok then
1048+
if err == "timeout" then
1049+
sock:close() -- timeout errors do not close the socket.
1050+
self:report_timeout(ip, port, hostname, "active")
1051+
else
1052+
self:report_tcp_failure(ip, port, hostname, "connect", "active")
1053+
end
1054+
return nil
1055+
end
1056+
1057+
if typ == "https" then
1058+
local https_sni = self.checks.active.https_sni or hostheader or hostname
1059+
if self.ssl_cert and self.ssl_key then
1060+
ok, err = sock:setclientcert(self.ssl_cert, self.ssl_key)
1061+
if not ok then
1062+
self:log(ERR, "failed to set client certificate: ", err)
1063+
end
1064+
end
1065+
1066+
local session
1067+
session, err = sock:sslhandshake(nil, https_sni,
1068+
self.checks.active.https_verify_certificate)
1069+
if not session then
1070+
sock:close()
1071+
self:log(ERR, "failed SSL handshake with '", hostname or "", " (", ip, ":", port, ")', using server name (sni) '", https_sni, "': ", err)
1072+
self:report_tcp_failure(ip, port, hostname, "connect", "active")
1073+
return nil
1074+
end
1075+
end
1076+
1077+
return sock
1078+
end
1079+
1080+
1081+
-- Sends an HTTP/1.1 GET request over an already-connected socket and reports
1082+
-- the result via report_http_status / report_tcp_failure / report_timeout.
1083+
-- Connection: close is injected so the server closes the connection after
1084+
-- responding (health probes are one-shot).
1085+
local function probe_http(self, sock, ip, port, hostname, hostheader)
1086+
local headers = build_http_headers(self)
10711087
local path = self.checks.active.http_path
1072-
local request = ("GET %s HTTP/1.0\r\n%sHost: %s\r\n\r\n"):format(path, headers, hostheader or hostname or ip)
1088+
local host = hostheader or hostname or ip
1089+
1090+
local request = ("GET %s HTTP/1.1\r\nHost: %s\r\nConnection: close\r\n%s\r\n"):format(
1091+
path, host, headers)
10731092
self:log(DEBUG, "request head: ", request)
10741093

1075-
local bytes
1076-
bytes, err = sock:send(request)
1094+
local bytes, err = sock:send(request)
10771095
if not bytes then
10781096
self:log(ERR, "failed to send http request to '", hostname, " (", ip, ":", port, ")': ", err)
10791097
if err == "timeout" then
@@ -1108,10 +1126,27 @@ function checker:run_single_check(ip, port, hostname, hostheader)
11081126
sock:close()
11091127

11101128
self:log(DEBUG, "Reporting '", hostname, " (", ip, ":", port, ")' (got HTTP ", status, ")")
1111-
11121129
return self:report_http_status(ip, port, hostname, status, "active")
11131130
end
11141131

1132+
1133+
-- Runs a single healthcheck probe
1134+
function checker:run_single_check(ip, port, hostname, hostheader)
1135+
local typ = self.checks.active.type
1136+
1137+
local sock = establish_connection(self, ip, port, hostname, hostheader, typ)
1138+
if not sock then
1139+
return -- failure already reported inside establish_connection
1140+
end
1141+
1142+
if typ == "tcp" then
1143+
sock:close()
1144+
return self:report_success(ip, port, hostname, "active")
1145+
end
1146+
1147+
return probe_http(self, sock, ip, port, hostname, hostheader)
1148+
end
1149+
11151150
-- executes a work package (a list of checks) sequentially
11161151
function checker:run_work_package(work_package)
11171152
for _, work_item in ipairs(work_package) do

t/with_resty-events/18-req-headers.t

Lines changed: 15 additions & 10 deletions
Original file line numberDiff line numberDiff line change
@@ -79,9 +79,10 @@ true
7979
--- error_log
8080
checking healthy targets: nothing to do
8181
checking healthy targets: #1
82-
GET /status HTTP/1.0
83-
User-Agent: curl/7.29.0
82+
GET /status HTTP/1.1
8483
Host: 127.0.0.1
84+
Connection: close
85+
User-Agent: curl/7.29.0
8586
8687
8788
@@ -128,9 +129,10 @@ true
128129
--- error_log
129130
checking healthy targets: nothing to do
130131
checking healthy targets: #1
131-
GET /status HTTP/1.0
132-
User-Agent: curl
132+
GET /status HTTP/1.1
133133
Host: 127.0.0.1
134+
Connection: close
135+
User-Agent: curl
134136
135137
136138
=== TEST 3: headers: { ["User-Agent"] = "curl" }
@@ -176,9 +178,10 @@ true
176178
--- error_log
177179
checking healthy targets: nothing to do
178180
checking healthy targets: #1
179-
GET /status HTTP/1.0
180-
User-Agent: curl
181+
GET /status HTTP/1.1
181182
Host: 127.0.0.1
183+
Connection: close
184+
User-Agent: curl
182185
183186
184187
@@ -225,9 +228,10 @@ true
225228
--- error_log
226229
checking healthy targets: nothing to do
227230
checking healthy targets: #1
228-
GET /status HTTP/1.0
229-
User-Agent: curl
231+
GET /status HTTP/1.1
230232
Host: 127.0.0.1
233+
Connection: close
234+
User-Agent: curl
231235
232236
233237
@@ -274,7 +278,8 @@ true
274278
--- error_log
275279
checking healthy targets: nothing to do
276280
checking healthy targets: #1
277-
GET /status HTTP/1.0
281+
GET /status HTTP/1.1
282+
Host: 127.0.0.1
283+
Connection: close
278284
User-Agent: curl
279285
User-Agent: nginx
280-
Host: 127.0.0.1

t/with_worker-events/18-req-headers.t

Lines changed: 15 additions & 10 deletions
Original file line numberDiff line numberDiff line change
@@ -59,9 +59,10 @@ true
5959
--- error_log
6060
checking healthy targets: nothing to do
6161
checking healthy targets: #1
62-
GET /status HTTP/1.0
63-
User-Agent: curl/7.29.0
62+
GET /status HTTP/1.1
6463
Host: 127.0.0.1
64+
Connection: close
65+
User-Agent: curl/7.29.0
6566
6667
6768
@@ -109,9 +110,10 @@ true
109110
--- error_log
110111
checking healthy targets: nothing to do
111112
checking healthy targets: #1
112-
GET /status HTTP/1.0
113-
User-Agent: curl
113+
GET /status HTTP/1.1
114114
Host: 127.0.0.1
115+
Connection: close
116+
User-Agent: curl
115117
116118
117119
=== TEST 3: headers: { ["User-Agent"] = "curl" }
@@ -158,9 +160,10 @@ true
158160
--- error_log
159161
checking healthy targets: nothing to do
160162
checking healthy targets: #1
161-
GET /status HTTP/1.0
162-
User-Agent: curl
163+
GET /status HTTP/1.1
163164
Host: 127.0.0.1
165+
Connection: close
166+
User-Agent: curl
164167
165168
166169
@@ -208,9 +211,10 @@ true
208211
--- error_log
209212
checking healthy targets: nothing to do
210213
checking healthy targets: #1
211-
GET /status HTTP/1.0
212-
User-Agent: curl
214+
GET /status HTTP/1.1
213215
Host: 127.0.0.1
216+
Connection: close
217+
User-Agent: curl
214218
215219
216220
@@ -258,7 +262,8 @@ true
258262
--- error_log
259263
checking healthy targets: nothing to do
260264
checking healthy targets: #1
261-
GET /status HTTP/1.0
265+
GET /status HTTP/1.1
266+
Host: 127.0.0.1
267+
Connection: close
262268
User-Agent: curl
263269
User-Agent: nginx
264-
Host: 127.0.0.1

0 commit comments

Comments
 (0)