Files
2026-07-13 12:32:21 +08:00

283 lines
7.0 KiB
Lua

local https_server = {}
https_server.__index = https_server
local fmt = string.format
local mock_srv_tpl_file = require "spec.fixtures.mock_webserver_tpl"
local ngx = require "ngx"
local pl_dir = require "pl.dir"
local pl_file = require "pl.file"
local pl_template = require "pl.template"
local pl_path = require "pl.path"
local uuid = require "resty.jit-uuid"
local http_client = require "resty.http"
local cjson = require "cjson"
local shell = require "resty.shell"
local Template = require("pl.stringx").Template
-- we need this to get random UUIDs
math.randomseed(os.time())
local HTTPS_SERVER_START_MAX_RETRY = 10
local tmp_root = os.getenv("TMPDIR") or "/tmp"
local host_regex = [[([a-z0-9\-._~%!$&'()*+,;=]+@)?([a-z0-9\-._~%]+|\[[a-z0-9\-._~%!$&'()*+,;=:]+\])(:?[0-9]+)*]]
local function create_temp_dir(copy_cert_and_key)
local tmp_name = fmt("nginx_%s", uuid())
local tmp_path = fmt("%s/%s", tmp_root, tmp_name)
local _, err = pl_path.mkdir(tmp_path)
if err then
return nil, err
end
local _, err = pl_path.mkdir(tmp_path .. "/logs")
if err then
return nil, err
end
if copy_cert_and_key then
local status = pl_dir.copyfile("./spec/fixtures/kong_spec.crt", tmp_path)
if not status then
return nil, "could not copy cert"
end
status = pl_dir.copyfile("./spec/fixtures/kong_spec.key", tmp_path)
if not status then
return nil, "could not copy private key"
end
end
return tmp_path
end
local function create_conf(params)
local tpl, err = pl_template.compile(mock_srv_tpl_file)
if err then
return nil, err
end
local compiled_tpl = Template(tpl:render(params, { ipairs = ipairs }))
local conf_filename = params.base_path .. "/nginx.conf"
local conf, err = io.open (conf_filename, "w")
if err then
return nil, err
end
conf:write(compiled_tpl:substitute(params))
conf:close()
return conf_filename
end
local function count_results(logs_dir)
local results = {
["ok"] = 0,
["fail"] = 0,
["total"] = 0,
["status_ok"] = 0,
["status_fail"] = 0,
["status_total"] = 0
}
local error_log_filename = logs_dir .. "/error.log"
for line in io.lines(error_log_filename) do
local m = ngx.re.match(line, [[^.*\[COUNT\] (.+) (\d\d\d)\,.*\, host: \"(.+)\"$]])
if m then
local location = m[1]
local status = m[2]
local host = m[3]
if host then
local host_no_port = ngx.re.match(m[3], host_regex)
if host_no_port then
host = host_no_port[2]
end
else
host = "nonamehost"
end
if results[host] == nil then
results[host] = {
["ok"] = 0,
["fail"] = 0,
["status_ok"] = 0,
["status_fail"] = 0,
}
end
if location == "slash" then
if status == "200" then
results.ok = results.ok + 1
results[host].ok = results[host].ok + 1
else
results.fail = results.fail + 1
results[host].fail = results[host].fail + 1
end
results.total = results.ok + results.fail
elseif location == "status" then
if status == "200" then
results.status_ok = results.status_ok + 1
results[host].status_ok = results[host].status_ok + 1
else
results.status_fail = results.status_fail + 1
results[host].status_fail = results[host].status_fail + 1
end
results.status_total = results.status_ok + results.status_fail
end
end
end
return results
end
function https_server.clear_access_log(self)
local client = assert(http_client.new())
local uri = string.format("%s://%s:%d/clear_log", self.protocol, self.host, self.http_port)
local res = assert(client:request_uri(uri, {
method = "GET"
}))
assert(res.body == "cleared\n")
end
function https_server.get_access_log(self)
local client = assert(http_client.new())
local uri = string.format("%s://%s:%d/log?do_not_log", self.protocol, self.host, self.http_port)
local res = assert(client:request_uri(uri, {
method = "GET"
}))
return assert(cjson.decode(res.body))
end
function https_server.start(self)
if not pl_path.exists(tmp_root) or not pl_path.isdir(tmp_root) then
error("could not get a temporary path", 2)
end
local err
self.base_path, err = create_temp_dir(self.protocol == "https")
if err then
error(fmt("could not create temp dir: %s", err), 2)
end
local conf_params = {
base_path = self.base_path,
delay = self.delay,
cert_path = "./",
check_hostname = self.check_hostname,
logs_dir = self.logs_dir,
host = self.host,
hosts = self.hosts,
http_port = self.http_port,
protocol = self.protocol,
worker_num = self.worker_num,
disable_ipv6 = self.disable_ipv6,
}
local file, err = create_conf(conf_params)
if err then
error(fmt("could not create conf: %s", err), 2)
end
for _ = 1, HTTPS_SERVER_START_MAX_RETRY do
if shell.run("nginx -c " .. file .. " -p " .. self.base_path, nil, 0) then
return
end
ngx.sleep(1)
end
error("failed starting nginx")
end
function https_server.shutdown(self)
local pid_filename = self.base_path .. "/logs/nginx.pid"
local pid_file = io.open (pid_filename, "r")
if pid_file then
local pid, err = pid_file:read()
if err then
error(fmt("could not read pid file: %s", tostring(err)), 2)
end
local kill_nginx_cmd = fmt("kill -s TERM %s", tostring(pid))
local status = shell.run(kill_nginx_cmd, nil, 0)
if not status then
error(fmt("could not kill nginx test server. %s was not removed", self.base_path), 2)
end
local pidfile_removed
local watchdog = 0
repeat
pidfile_removed = pl_file.access_time(pid_filename) == nil
if not pidfile_removed then
ngx.sleep(0.01)
watchdog = watchdog + 1
if(watchdog > 100) then
error("could not stop nginx", 2)
end
end
until(pidfile_removed)
end
local count, err = count_results(self.base_path .. "/" .. self.logs_dir)
if err then
-- not a fatal error
print(fmt("could not count results: %s", tostring(err)))
end
local _, err = pl_dir.rmtree(self.base_path)
if err then
print(fmt("could not remove %s: %s", self.base_path, tostring(err)))
end
return count
end
-- **DEPRECATED**: please use `spec.helpers.http_mock` instead.
function https_server.new(port, hostname, protocol, check_hostname, workers, delay, disable_ipv6)
local self = setmetatable({}, https_server)
local host
local hosts
if type(hostname) == "table" then
hosts = hostname
host = ""
for _, h in ipairs(hostname) do
host = fmt("%s %s", host, h)
end
else
hosts = {hostname}
host = hostname
end
self.check_hostname = check_hostname or false
self.delay = tonumber(delay) or 0
self.host = host or "localhost"
self.hosts = hosts
self.http_port = port
self.logs_dir = "logs"
self.protocol = protocol or "http"
self.worker_num = workers or 2
self.disable_ipv6 = disable_ipv6
return self
end
return https_server